Skip to content

[Bug] Add server-side dispatcher guard refusing mutating tool calls in disallowed stages #7

Description

@sjohnston1972

Part of the stage-authorization parent issue.

Background

Even once the tool list is stage-filtered (sibling sub-issue), the dispatcher should independently refuse a mutating tool call that arrives in a disallowed stage — belt-and-braces, so a caching bug, a stale prefix, or a hand-crafted request can't slip a mutation through.

Problem / Goal

Add a server-side authorization check in the tool dispatch path: given the active stage and the tool name, refuse mutating tools in read-only stages (and deploy-vs-destroy cross-stage) before any cloud call runs.

Where to look

  • backend/src/claude/tool-bridge.ts:169-207 (callMcpTool dispatcher) and custom-tools.ts:callCustomTool.
  • backend/src/routes/chat.ts:337-367 — the per-tool dispatch loop where callMcpTool(t.name, t.input) is invoked; stage is in scope here.

Suggested approach

  1. Add a small pure predicate, e.g. isToolAllowedInStage(name, stage): boolean, reusing the mutating-tool classification from the sibling sub-issue (factor it into a shared module so both use one source of truth).
  2. Thread stage into callMcpTool (or check in the chat.ts loop just before calling it). If not allowed, skip the call and push a tool_result with is_error: true and a message like "<tool> is not permitted in stage <stage>".
  3. Ensure the loop still emits a tool_result for that tool_use_id so the Anthropic message sequence stays valid.

Acceptance criteria

  • A destroy_azure/deploy_bicep call in a read-only stage returns is_error: true, spawns no container, and makes no cloud call.
  • The chat loop continues cleanly (valid tool_result posted; no dangling tool_use).
  • Allowed calls (deploy in push, destroy in teardown) are unaffected.

Testing

  • Unit test isToolAllowedInStage for the mutating tools across all five stages.
  • Integration test: dispatch a destroy_azure with stage='build' and assert the refusal result and that spawnAndCapture/docker was never invoked (mock it).

Out of scope

  • Tool-list filtering (sibling sub-issue).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions