Skip to content

Remote-proxied tool calls don't enforce the origin session's tool-surface policy or workspace boundary #696

Description

@timkjr

Follow-up from your latest review on PR #649 (findings #2 and #3's remote half) — opening this separately since it's a design question about the multi-server/federation protocol, not a bug in that PR's diff.

The gap

When a tool call routes to a remote gortex daemon (Router.RouteToolCall → proxy path, reached from internal/server/handler.go's handleToolCall, internal/mcp/streamable/transport.go's tryRouteToolCall, and the daemon dispatcher's tryProxyToolCall), two things about the origin session are not currently transmitted to, or enforced by, the remote:

  1. The origin session's tool-surface policy. A tool hidden by the calling session's facade-v1/hide-mode policy is checked locally by checkToolGate inside the wrapped handler — but a proxied request never reaches that handler locally. Router.RouteToolCall only gates on remoteEnabledIn/IsEffectful (whether the remote is enabled at all, and whether the tool is read-only) before forwarding — nothing checks whether this session is even allowed to call the tool in the first place. The remote executes under its own default policy, whatever that happens to be.

  2. A trusted, enforceable workspace/cwd ceiling. The origin resolves a cwd/workspace for routing purposes (peekRouteContext, RouteInputs.Cwd), but that's used only to pick a remote destination — it isn't carried as an authoritative boundary the remote must honor once it receives the request. A remote hosting multiple repositories could, in principle, answer using a different repo than the one the caller's resolved cwd pointed to.

Neither of these is a bug in a single line — the origin-side gate simply doesn't exist on the remote-proxy branch at all, by construction: the local branch relies entirely on the downstream handler enforcing policy, and there is no equivalent downstream handler on the remote-proxy branch to rely on.

Why I'm asking rather than guessing at a fix

This needs an actual protocol decision, not just a code patch:

  • Where does the origin-policy check belong? The natural shared choke point is ProxyDecision.Decide (used by all three call sites above), but it doesn't currently have access to the MCP Server's session-policy machinery (IsToolEnabledForSession etc.) — that'd mean threading a callback/dependency into daemon.Router/ProxyDecision the way localExecute is already wired in.
  • For the workspace ceiling: does the origin refuse to route at all once it knows the resolved cwd doesn't match anything the target remote can honor, or does the remote need a new authenticated field (a resolved workspace identity) it's expected to enforce against its own tracked repos? The latter is a real trust-boundary addition to the wire protocol between daemons, not just an origin-side check.
  • Whatever the shape, it should apply uniformly across all three entry points (HTTP /v1/tools/*, the Streamable HTTP /mcp transport, and the stdio daemon dispatcher's proxy path) rather than being patched into each independently — that's the mistake that let the ctx-timing bug in Promote deferred MCP tools on a registry-miss instead of 404ing #649 hide in one of three near-identical call sites for as long as it did.

Happy to implement once there's a direction — just don't want to unilaterally invent a trust-boundary design for the federation protocol without your steer, especially given how much back-and-forth #649 already took on narrower issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions