Skip to content

feat(policy): ✨ Allow shell in plan mode via approval policy with read-only constraint - #89

Merged
jorben merged 4 commits into
masterfrom
feat/plan-mode-shell
Apr 20, 2026
Merged

jorben merged 4 commits into
masterfrom
feat/plan-mode-shell

Conversation

@HayWolf

@HayWolf HayWolf commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add shell tool to plan_read_only tool profile so it is available in plan mode
  • Shell in plan mode follows the user's configured approval policy instead of being hard-denied
  • Prompt constrains shell to read-only commands only (git log, npm ls, command -v, etc.)
  • New PLAN_HARD_DENY_TOOLS constant excludes shell from plan mode hard-deny

Changes

  • agent_session.rs: Move shell tool definition out of DEFAULT_FULL_TOOL_PROFILE-only block into shared section
  • policy_engine.rs: Add PLAN_HARD_DENY_TOOLS (all mutating tools except shell). Plan mode check uses PLAN_HARD_DENY_TOOLS instead of MUTATING_TOOLS, so shell falls through to normal approval policy
  • providers.rs: Plan mode prompt adds "Shell tool: shell — use ONLY for read-only commands" constraint line; removes shell from "Do NOT use" list
  • m1_6_tool_gateway.rs: Remove shell from mutating_tools test vecs; add test_plan_mode_shell_follows_approval_policy test
  • agent_session.rs tests: Rename test to plan_read_only_profile_includes_shell_excludes_mutating_terminal_tools, add shell presence assertion

Test Plan

  • cargo test — all 245 unit tests pass including new test_plan_mode_shell_follows_approval_policy
  • test_plan_mode_blocks_mutating_tools — still blocks write/edit/patch/etc but NOT shell
  • plan_read_only_profile_includes_shell_excludes_mutating_terminal_tools — shell IS in plan profile
  • npm run typecheck — passes

🤖 Generated with TiyCode

Previously, the shell tool was hard-denied in plan mode, which
prevented agents from running read-only commands (git log, npm ls,
command -v, etc.) needed for evidence gathering during planning.

This commit moves shell out of the plan-mode hard-deny list so it
falls through to the normal approval policy instead. Key changes:

- Extract PLAN_HARD_DENY_TOOLS from MUTATING_TOOLS, excluding shell
- Make shell tool available in both plan and full tool profiles
- Update plan-mode prompt to clarify shell is read-only only
- Add test confirming shell is not hard-denied in plan mode and
  follows normal approval policy
@github-actions

github-actions Bot commented Apr 19, 2026 •

Copy link
Copy Markdown

AI Code Review Summary

PR: #89 (feat(policy): ✨ Allow shell in plan mode via approval policy with read-only constraint)
Preferred language: English

Overall Assessment

Detected 1 actionable findings, prioritize CRITICAL/HIGH before merge.

Major Findings by Severity

  • MEDIUM (1)
    • src-tauri/src/core/policy_engine.rs:233 - No enforcement mechanism for read-only shell commands in plan mode

Actionable Suggestions

  • Consider whether technical enforcement (e.g., command pattern blocking) is needed for shell in plan mode, or if prompt-based guidance is sufficient
  • Add 'edit' tool to the test coverage in m1_6_tool_gateway.rs for completeness

Potential Risks

  • LLM could execute mutating shell commands in plan mode if it misinterprets prompt instructions
  • Users could be confused if they expect plan mode to be completely safe from any mutations

Test Suggestions

  • Add explicit test that 'edit' tool is blocked in plan mode
  • Consider adding a test with a mutating shell command (e.g., 'rm test.txt') in plan mode to verify the approval policy behavior
  • Test edge case: shell command that is technically read-only but has side effects (e.g., 'npm cache clean')
  • Test shell in plan mode with auto-approve approval policy to verify the fallback policy actually blocks mutating commands
  • Test shell in plan mode with commands that perform file mutation (e.g. 'touch', 'rm', 'echo > file') under different approval policy settings
  • Add integration test verifying that dangerous shell commands are still caught by the dangerous pattern hard-deny rule even in plan mode
  • Test that the 'edit' tool is still properly blocked when called through shell in plan mode (e.g. 'sed -i')
  • Add a test for plan mode shell with an allow-list configured to ensure allow-list takes effect correctly

File-Level Coverage Notes

  • src-tauri/src/core/policy_engine.rs: ok_with_notes (Clean refactoring from MUTATING_TOOLS to PLAN_HARD_DENY_TOOLS. The exclusion of shell from hard-deny is intentional and well-documented. The change correctly allows shell to fall through to the normal approval policy.)
  • src-tauri/src/core/agent_session.rs: ok (Correctly moves shell tool registration outside the DEFAULT_FULL_TOOL_PROFILE conditional, making it available for both full and plan-read-only profiles. The updated test verifies shell is included in plan mode tools.)
  • src-tauri/tests/m1_6_tool_gateway.rs: ok_with_notes (Good test coverage for the new behavior. The new test_plan_mode_shell_follows_approval_policy test correctly verifies that shell is not hard-denied in plan mode. Minor gap: 'edit' tool not explicitly tested in hard-deny list.)
  • src-tauri/src/core/prompt/providers.rs: ok (Clear and helpful prompt updates that guide the LLM to use shell only for read-only commands in plan mode. The explicit examples (git log, npm ls, command -v) are good practical guidance.)

Inline Downgraded Items (processed but not inline)

  • None

Coverage Status

  • Target files: 4
  • Covered files: 4
  • Uncovered files: 0
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • None

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 1
  • Executed batches: 1
  • Sub-agent runs: 2
  • Planner calls: 1
  • Reviewer calls: 3
  • Model calls: 4/64
  • Structured-output summary-only degradation: NO

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 3
  • Findings with unknown confidence: 0
  • Inline comments attempted: 3
  • Target files: 4
  • Covered files: 4
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

"market_install",
];

/// Tools that are hard-denied in plan mode.

This comment was marked as outdated.

/// Shell is intentionally excluded — it follows the normal approval policy so that
/// read-only commands (git log, npm ls, command -v, skill CLIs, etc.) remain
/// available for information gathering in plan mode.
const PLAN_HARD_DENY_TOOLS: &[&str] = &[

This comment was marked as outdated.

Comment thread src-tauri/tests/m1_6_tool_gateway.rs Outdated
}

// Shell is not in the hard-deny list; it follows the normal approval policy.
let blocked = mutating_tools.contains(&"shell");

This comment was marked as outdated.

jorben added 2 commits April 20, 2026 07:40
Address PR review #3: test variable name was misleading since shell
is now excluded from the plan-mode hard-deny list. Renamed to
hard_deny_tools and renamed test function to
test_plan_mode_blocks_hard_deny_tools.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 2
  • Findings with unknown confidence: 0
  • Inline comments attempted: 2
  • Target files: 4
  • Covered files: 2
  • Uncovered files: 2
    See the summary comment for detailed analysis and coverage details.

/// Shell is intentionally excluded — it follows the normal approval policy so that
/// read-only commands (git log, npm ls, command -v, skill CLIs, etc.) remain
/// available for information gathering in plan mode.
const PLAN_HARD_DENY_TOOLS: &[&str] = &[

This comment was marked as outdated.


#[test]
fn plan_read_only_profile_does_not_expose_mutating_terminal_tools() {
fn plan_read_only_profile_includes_shell_excludes_mutating_terminal_tools() {

This comment was marked as outdated.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 1
  • Findings with unknown confidence: 0
  • Inline comments attempted: 1
  • Target files: 4
  • Covered files: 4
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

if run_mode == "plan" && MUTATING_TOOLS.contains(&tool_name) {
// 4. Run mode restriction (plan mode blocks hard-deny mutations;
// shell is excluded so it falls through to the normal approval policy)
if run_mode == "plan" && PLAN_HARD_DENY_TOOLS.contains(&tool_name) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] No enforcement mechanism for read-only shell commands in plan mode

Shell is excluded from hard-deny in plan mode with the expectation that it will only be used for read-only commands. However, there's no technical enforcement preventing a user from running mutating commands via shell in plan mode - the policy relies entirely on prompt instructions to guide the LLM behavior.

Suggestion: Consider whether the approval policy should have a plan-mode-specific check for shell commands that attempts to detect mutating operations (e.g., via command pattern matching for rm, mv, git push, npm install, etc.) and either denies them or requires stricter approval. Alternatively, document this as an intentional trust boundary.

Risk: LLM could potentially execute mutating shell commands in plan mode if it misinterprets the instructions or if the user explicitly requests them.

Confidence: 0.85

[From SubAgent: general]

@jorben
jorben merged commit 6f615fa into master Apr 20, 2026
5 checks passed
@jorben
jorben deleted the feat/plan-mode-shell branch April 20, 2026 00:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants