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.
Summary
Plan mode treats some
bashcommands as read-only by prefix match only. Commands such asecho,printf, andnode -eare 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 thatbashis available for read-only commands. Ifecho SIDE_EFFECT > file,printf ... >> file, ornode -e "fs.writeFileSync(...)"is allowed, Plan mode no longer enforces that boundary.Evidence
src/tool/planModeConstraints.ts:11tosrc/tool/planModeConstraints.ts:26includesbashin the Plan mode allowed tool set.src/tool/planModeConstraints.ts:32tosrc/tool/planModeConstraints.ts:35describes Plan modebashas read-only.src/tool/execution/ToolRuntime.ts:48tosrc/tool/execution/ToolRuntime.ts:57blocks tools not in the Plan mode allowlist;bashpasses this gate.src/tool/builtin/bash/permissions.ts:35tosrc/tool/builtin/bash/permissions.ts:45marksprintf,echo, andnode -eas safe read patterns by prefix.src/tool/builtin/bash/permissions.ts:95tosrc/tool/builtin/bash/permissions.ts:97implementsisReadOnlyShellCommandas “any safe pattern matches.”src/tool/builtin/bash.ts:59tosrc/tool/builtin/bash.ts:60exposes that classifier as the bash tool’sisReadOnly.src/permission/decision/PermissionRuntime.ts:94tosrc/permission/decision/PermissionRuntime.ts:99allows read-only tools in Plan mode even when the tool permission would otherwise ask.src/permission/decision/PermissionRuntime.ts:184tosrc/permission/decision/PermissionRuntime.ts:185allows Plan mode whentool.isReadOnly(input)is true.src/permission/decision/PermissionRuntime.ts:373tosrc/permission/decision/PermissionRuntime.ts:377builds the Plan mode bash deny message only on the non-read-only path.src/tool/builtin/bash.ts:86tosrc/tool/builtin/bash.ts:93passes the command to the shell runner.src/tool/builtin/bash/commandRunner.ts:30tosrc/tool/builtin/bash/commandRunner.ts:34runs commands withshell: true, so shell redirection is effective.Validation
Validation level: dynamic permission/runtime reproduction.
Repro approach: evaluate
echo SIDE_EFFECT > plan-mode-write.txtthrough the bash read-only classifier and Plan mode permission runtime, then execute the bash command.Key output:
isReadOnlyShellCommandclassified the command as read-only,PermissionRuntimereturned 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 -escripts 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 -esnippets 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 > fileis rejected.printf SIDE_EFFECT >> fileis rejected.node -e "fs.writeFileSync('file','x')"is rejected.pwd,ls, andgit statusstill pass.Submitted with Codex.