From decdddbe3eee5f783db80e436afd6b25cce1f8a2 Mon Sep 17 00:00:00 2001 From: zeroaltitude Date: Sat, 7 Mar 2026 10:49:04 -0700 Subject: [PATCH] security: disable tools for LLM slug generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The slug generator embeds up to 2000 chars of raw conversation content in its prompt. Without disableTools, the embedded agent inherits the full tool set (exec, file write, messaging), meaning a crafted conversation could prompt-inject the slug call into executing arbitrary side-effects before the slug text is extracted. Slug generation is pure text — it never needs tool access. Add disableTools: true to close this injection surface. --- src/hooks/llm-slug-generator.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/hooks/llm-slug-generator.ts b/src/hooks/llm-slug-generator.ts index eb355fc3289..cbfeaf032cf 100644 --- a/src/hooks/llm-slug-generator.ts +++ b/src/hooks/llm-slug-generator.ts @@ -50,6 +50,14 @@ Reply with ONLY the slug, nothing else. Examples: "vendor-pitch", "api-design", const provider = parsed?.provider ?? DEFAULT_PROVIDER; const model = parsed?.model ?? DEFAULT_MODEL; + // Security: disable tools for this one-shot call. The prompt embeds + // up to 2 000 chars of raw conversation content, which is attacker- + // controllable. Without disableTools the embedded agent inherits the + // full tool set (exec, file write, messaging, …), so a crafted + // conversation could prompt-inject the slug-generation call into + // executing arbitrary side-effects *before* the (well-sanitised) slug + // text is extracted. Slug generation is pure text — it never needs + // tool access. const result = await runEmbeddedPiAgent({ sessionId: `slug-generator-${Date.now()}`, sessionKey: "temp:slug-generator", @@ -61,6 +69,7 @@ Reply with ONLY the slug, nothing else. Examples: "vendor-pitch", "api-design", prompt, provider, model, + disableTools: true, timeoutMs: 15_000, // 15 second timeout runId: `slug-gen-${Date.now()}`, });