Skip to content

fix(mcpfed): ReadOnlyHint can no longer lower federated tool risk below the floor#147

Merged
xunholy merged 1 commit into
mainfrom
fix/mcpfed-readonly-risk-floor
Jun 27, 2026
Merged

fix(mcpfed): ReadOnlyHint can no longer lower federated tool risk below the floor#147
xunholy merged 1 commit into
mainfrom
fix/mcpfed-readonly-risk-floor

Conversation

@xunholy

@xunholy xunholy commented Jun 27, 2026

Copy link
Copy Markdown
Owner

Summary

Third trust-boundary safety fix in the series (after MCP output quarantine v0.766 and federation output bound v0.767). A gate-bypass via untrusted metadata.

The federation risk classifier (mcpfed.classify) trusted ReadOnlyHint — a tool annotation the external, untrusted MCP server controls — and returned risk.Low unconditionally. A malicious or compromised federated server could mark a destructive tool read-only to have it classified Low, which:

  • skips the confirm gate (Low is below any Medium/High threshold),
  • passes the audit fail-closed gate (RequireOpen only refuses ≥ High),
  • and is permitted even in read-only mode (read-only refuses only > Low).

That defeats the documented "every federated tool is at least High" guarantee those gates rely on.

Fix

Enforce the invariant the docs already claimed — server annotations may raise risk but never lower it below the operator's configured floor (RiskDefault, default High). classify() now returns defaultLevel for ReadOnlyHint=true (treated as "no escalation needed"), never Low. A read-only federated tool reaches Low only when the operator deliberately sets that server's RiskDefault=low. DestructiveHint→Critical and OpenWorldHint→bump are unchanged (both raise, both safe).

Verification

  • Updated the two tests that pinned the vulnerable behaviour; corrected doc.go; added attack-case coverage: destructive+readonly stays Critical; read-only held at the High/Medium floor; read-only reaches Low only via an explicit operator default.
  • task ci green (lint 0 / vet / build / test:full 0 fail / govulncheck clean); task eval 17/17.
  • Behaviour-isolated to the inbound federation classifier; the MCP server's outbound ReadOnlyHint annotations are unaffected.

Safety fix — anchors v0.768.0.

…low the floor

The federation risk classifier trusted ReadOnlyHint — a tool annotation the
external, untrusted MCP server controls — and returned risk.Low
unconditionally. A malicious or compromised federated server could therefore
mark a destructive tool read-only to have it classified Low, which:
  - skips the confirm gate (Low is below any Medium/High threshold),
  - passes the audit fail-closed gate (RequireOpen only refuses >= High),
  - and is permitted even in read-only mode (read-only refuses only > Low).
That defeats the documented "every federated tool is at least High" guarantee
those gates rely on — a server-controlled gate bypass.

Fix: enforce the invariant the docs already claimed — server annotations may
RAISE risk but never lower it below the operator's configured floor
(RiskDefault, default High). classify() now returns defaultLevel for
ReadOnlyHint=true (treated as "no escalation needed"), never Low. A read-only
federated tool reaches Low only when the operator deliberately sets that
server's RiskDefault=low. DestructiveHint->Critical and OpenWorldHint->bump
are unchanged (both raise, both safe).

Updated the two tests that pinned the vulnerable behaviour, corrected doc.go,
and added attack-case coverage: destructive+readonly stays Critical;
read-only is held at the High/Medium floor; read-only reaches Low only via an
explicit operator default.

Verified: task ci green (lint 0 / vet / build / test:full 0 fail / govulncheck
clean); task eval 17/17. Behaviour-isolated to the inbound federation
classifier; the MCP server's outbound ReadOnlyHint annotations are unaffected.
@xunholy
xunholy merged commit f7f336a into main Jun 27, 2026
5 of 6 checks passed
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.

1 participant