Skip to content

ENG-965: mask mcp lane results with the lane's mask rules - #2125

Draft
luanlorenzo wants to merge 10 commits into
luan/eng-984from
luan/eng-965
Draft

luanlorenzo wants to merge 10 commits into
luan/eng-984from
luan/eng-965

Conversation

@luanlorenzo

@luanlorenzo luanlorenzo commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

📝 Description

The sidecar mcp lane masks tool and resource results with its own mask rules, through mcpproxy's redactor. Stacked on #2120. Stays draft until mcpproxy v0.2.0 is tagged; the pin is the pseudo-version of mcpproxy 7d089fd.

🔗 Related Issue

Part of ENG-965

🚀 Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

📋 Changes Made

  • gate.MaskSupported accepts mcp; Validate stops refusing mask on an mcp listener.
  • The engine builds mcpproxy's Redactor per result with Hooks.Redact = the session masker (the hook gets no session). RecordMasked takes its count from the mcp.redacted event. No result walker of our own.
  • A masker error, a closed session or an unrecorded masked result withholds the result (-32010). The session stays open.
  • New value capability mcp_mask: the plane does not serve mask rules on an mcp lane to a sidecar without it. 1.290.0 refuses a lane mask block there and ignores inherited rules.
  • A mask rule that names columns never fires on an mcp lane. Validate refuses one in the lane's own mask block, and the plane refuses to bind one to an mcp listener (422, same daemon check). The served document never puts one in an mcp lane's own block, whatever route bound it, and an import does not bind a top-level one to an mcp lane. An inherited one is skipped with a -validate note and a startup warning, so configs valid on 1.290 still start. The lane's own rules or mask: {rules: []} replace inherited ones.
  • mcpproxy v0.1.5 to 7d089fd in agent, gateway, client, sidecar/cmd and sidecar/mcpgateway. Behaviour change for the agent: the hoop binary links agent and sidecar, so the agent MCP gateway gets:
    • fail-closed masking (-32010, session stays open): a Redact hook error or cancel, a repeated or case-folded result key, structuredContent past 128 levels, a result or read field of an unexpected type; it now also masks structuredContent, nested resource.text, resources/read contents and tasks/result;
    • ENG-1037: a JSON-RPC field spelled in another case is refused; a tools/call is judged by its exact name and arguments keys, and invalid params are refused before the pipeline (no budget spent); a failed initialize gets no session;
    • v0.1.6: a killed session is audited as killed, with its reason; the first reason stands when kills race;
    • ENG-961: replies to server requests carry the session caller's context, so the token source resolves that caller;
    • no change: the sse per-caller refusal was reverted; the pre-pipeline deny count (ENG-984) needs an Observer, which the agent does not set.
  • Follow-ups, not here: webapp masking gate for mcp (LANE_FEATURE_BLOCKS, ENG-959: mcp lanes in the sidecar form, rule editor and session view #2121), the mcp lane docs page (ENG-965: docs(sidecar): masking and the ai analyzer on the mcp lane documentation#258), the libhoop Redact hook logging a cause.

🧪 Testing

Result: an agent behind the mcp lane gets emails and SSNs masked in every result field; a result the masker cannot read is withheld, not leaked; a config with a column mask rule still boots. Independent QA PASS on 2416c49c6 (4 rounds) against the base a56f2b8dc, test data only.

What it solves: teams with an MCP server behind the sidecar mcp lane get their mask rules applied to what the agent reads.

  • sidecar/mcpgateway/mask_test.go: TestAToolResultTextIsMasked, TestAResourceReadIsMasked, TestANestedResourceTextIsMasked, TestEveryStructuredContentStringIsMasked, TestAMaskerErrorWithholdsTheResultAndTheSessionSurvives, TestAMaskedResultThatIsNotRecordedIsWithheld, TestALaneWithoutMaskRulesForwardsResultsAsSent, TestAResultOfAnUnexpectedTypeIsWithheld.
  • sidecar/daemon/mcpgateway_test.go: TestAnMCPLaneMasksWithItsMaskRules, TestAnMCPLaneRefusesColumnMaskRules, TestMCPLaneSessionMasksAndRecordsTheCount, TestAnMCPLaneWithMaskRulesNeedsTheCapability.
  • gateway/services/sidecarrules_test.go: TestValidateSpecForLane (mcp cases), TestAServedMCPLaneNeverHoldsAColumnMaskRule; gateway/services/sidecarimport_test.go: TestImportDoesNotBindAColumnMaskRuleToAnMCPLane.
  • cd sidecar && go vet ./... && go test ./...
  • cd sidecar/mcpgateway && go vet ./... && go test -race ./...
  • cd sidecar/cmd && go vet ./... && CGO_ENABLED=0 go test ./...
  • cd agent && go test ./controller/..., cd gateway && go test ./api/connections/ -run 'MCP|Mcp', cd gateway && go test ./services ./api/sidecar ./api/sidecarbind, cd client && go build ./...
  • TestTheObserverCountsMessagesByToolAndDecision: since mcpproxy v0.1.7, invalid tools/call params are refused before the pipeline. They still count as a deny with no tool (mcpproxy 826f2e6), but spend no budget, so the test budget is 3.

@luanlorenzo luanlorenzo added the minor Bumps the minor version on release (new features) label Nov 6, 2026
@github-actions

github-actions Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

📋 API Changelog

API Changelog unknown vs. unknown

No changes detected

@sandromello

Copy link
Copy Markdown
Contributor

✅ Build Completed with Success, Version=2125.0.0-g2416c49

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

minor Bumps the minor version on release (new features)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants