Skip to content

bug(plan-mode): bash read-only classifier allows redirection writes #258

Description

@MicroMilo

Summary

Plan mode treats some bash commands as read-only by prefix match only. Commands such as echo, printf, and node -e are classified as read-only even when they contain shell redirection or obvious file-writing JavaScript, so Plan mode can be bypassed to write arbitrary files.

Why it matters

Plan mode promises a constrained, read-only exploration phase, with writes limited to plan markdown files under .pilotdeck/plans/. The tool schema tells the model that bash is available for read-only commands. If echo SIDE_EFFECT > file, printf ... >> file, or node -e "fs.writeFileSync(...)" is allowed, Plan mode no longer enforces that boundary.

Evidence

  • src/tool/planModeConstraints.ts:11 to src/tool/planModeConstraints.ts:26 includes bash in the Plan mode allowed tool set.
  • src/tool/planModeConstraints.ts:32 to src/tool/planModeConstraints.ts:35 describes Plan mode bash as read-only.
  • src/tool/execution/ToolRuntime.ts:48 to src/tool/execution/ToolRuntime.ts:57 blocks tools not in the Plan mode allowlist; bash passes this gate.
  • src/tool/builtin/bash/permissions.ts:35 to src/tool/builtin/bash/permissions.ts:45 marks printf, echo, and node -e as safe read patterns by prefix.
  • src/tool/builtin/bash/permissions.ts:95 to src/tool/builtin/bash/permissions.ts:97 implements isReadOnlyShellCommand as “any safe pattern matches.”
  • src/tool/builtin/bash.ts:59 to src/tool/builtin/bash.ts:60 exposes that classifier as the bash tool’s isReadOnly.
  • src/permission/decision/PermissionRuntime.ts:94 to src/permission/decision/PermissionRuntime.ts:99 allows read-only tools in Plan mode even when the tool permission would otherwise ask.
  • src/permission/decision/PermissionRuntime.ts:184 to src/permission/decision/PermissionRuntime.ts:185 allows Plan mode when tool.isReadOnly(input) is true.
  • src/permission/decision/PermissionRuntime.ts:373 to src/permission/decision/PermissionRuntime.ts:377 builds the Plan mode bash deny message only on the non-read-only path.
  • src/tool/builtin/bash.ts:86 to src/tool/builtin/bash.ts:93 passes the command to the shell runner.
  • src/tool/builtin/bash/commandRunner.ts:30 to src/tool/builtin/bash/commandRunner.ts:34 runs commands with shell: true, so shell redirection is effective.

Validation

Validation level: dynamic permission/runtime reproduction.

Repro approach: evaluate echo SIDE_EFFECT > plan-mode-write.txt through the bash read-only classifier and Plan mode permission runtime, then execute the bash command.

Key output: isReadOnlyShellCommand classified the command as read-only, PermissionRuntime returned allow in Plan mode, and shell execution created the file.

Boundary: this did not require a full agent loop. The permission runtime and bash tool are the same core path used by agent tool execution.

Expected behavior

Plan mode should reject bash commands that can write or mutate files, even if they start with a command that is sometimes read-only. Redirection, append redirection, shell command composition, and obvious file-writing node -e scripts should not be allowed under the read-only classifier.

Existing coverage checked

Coverage review checked existing Plan mode hardening work.

Coverage label: partially covered. Merged PR #216 and PR #220 strengthen Plan mode enforcement, tool filtering, error formatting, and UI presentation. They cover adjacent Plan mode hardening, but not this root cause: the bash read-only classifier still allows write-capable commands such as echo > file.

Suggested fix

Make the shell read-only classifier conservative. Reject commands containing redirection operators, write-oriented pipes, command separators that compose writes, and node -e snippets containing obvious filesystem write APIs. Alternatively, use a stricter Plan mode bash allowlist that only permits known read-only commands without shell metacharacters.

Suggested tests

  • echo SIDE_EFFECT > file is rejected.
  • printf SIDE_EFFECT >> file is rejected.
  • node -e "fs.writeFileSync('file','x')" is rejected.
  • Command chaining that writes after a read-looking prefix is rejected.
  • Known read-only commands such as pwd, ls, and git status still pass.

Submitted with Codex.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions