From 3d43d2e1750f660a523b6ecf2700fdba4dc35571 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 30 Apr 2026 21:31:43 +0200 Subject: [PATCH] fix(coding-agent): stop tool argument injection closes #4018 --- packages/coding-agent/CHANGELOG.md | 1 + packages/coding-agent/src/core/tools/find.ts | 2 +- packages/coding-agent/src/core/tools/grep.ts | 2 +- packages/coding-agent/test/tools.test.ts | 26 ++++++++++++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e29be22d..f9498030 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixed +- Fixed `grep` and `find` tool argument injection for flag-like search patterns ([#4018](https://github.com/badlogic/pi-mono/issues/4018)). - Updated `@mariozechner/clipboard` to an attested release so package managers with trust policies do not reject installs ([#3946](https://github.com/badlogic/pi-mono/issues/3946)). - Fixed project context discovery to load `AGENTS.MD` files in addition to `AGENTS.md` ([#3949](https://github.com/badlogic/pi-mono/issues/3949)). - Fixed `/handoff` to use compacted session context instead of pre-compaction raw messages ([#3945](https://github.com/badlogic/pi-mono/issues/3945)). diff --git a/packages/coding-agent/src/core/tools/find.ts b/packages/coding-agent/src/core/tools/find.ts index ed2c8fcf..d61c10b8 100644 --- a/packages/coding-agent/src/core/tools/find.ts +++ b/packages/coding-agent/src/core/tools/find.ts @@ -246,7 +246,7 @@ export function createFindToolDefinition( effectivePattern = `**/${pattern}`; } } - args.push(effectivePattern, searchPath); + args.push("--", effectivePattern, searchPath); const child = spawn(fdPath, args, { stdio: ["ignore", "pipe", "pipe"] }); const rl = createInterface({ input: child.stdout }); diff --git a/packages/coding-agent/src/core/tools/grep.ts b/packages/coding-agent/src/core/tools/grep.ts index eb2b4d9a..bb876ab7 100644 --- a/packages/coding-agent/src/core/tools/grep.ts +++ b/packages/coding-agent/src/core/tools/grep.ts @@ -215,7 +215,7 @@ export function createGrepToolDefinition( if (ignoreCase) args.push("--ignore-case"); if (literal) args.push("--fixed-strings"); if (glob) args.push("--glob", glob); - args.push(pattern, searchPath); + args.push("--", pattern, searchPath); const child = spawn(rgPath, args, { stdio: ["ignore", "pipe", "pipe"] }); const rl = createInterface({ input: child.stdout }); diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 56f607b1..f93bb94f 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -617,6 +617,23 @@ describe("Coding Agent Tools", () => { // Ensure second match is not present expect(output).not.toContain("match two"); }); + + it("should treat flag-like patterns as search text", async () => { + const marker = join(testDir, "grep-injection-marker"); + const payload = join(testDir, "payload.sh"); + const testFile = join(testDir, "target.txt"); + writeFileSync(payload, `#!/bin/sh\necho executed > ${marker}\ncat "$1"\n`); + chmodSync(payload, 0o755); + writeFileSync(testFile, "target\n"); + + const result = await grepTool.execute("test-call-grep-injection", { + pattern: `--pre=${payload}`, + path: testDir, + }); + + expect(getTextOutput(result)).toContain("No matches found"); + expect(existsSync(marker)).toBe(false); + }); }); describe("find tool", () => { @@ -663,6 +680,15 @@ describe("Coding Agent Tools", () => { }), ).rejects.toThrow(/error parsing glob|fd exited with code 1|fd error/i); }); + + it("should treat flag-like patterns as search text", async () => { + const result = await findTool.execute("test-call-find-flag-pattern", { + pattern: "--help", + path: testDir, + }); + + expect(getTextOutput(result)).toContain("No files found matching pattern"); + }); }); describe("ls tool", () => {