jcp-gateway: everything a gated request carries to the server is sealed and flow-checked #339
Merged
dcharlot
merged 1 commit from 2026-10-19 21:21:56 -04:00
fix/jcp-gateway-flow-checks-whole-request into main 2026-10-19
jcp-gateway: everything a gated request carries to the server is sealed and flow-checked
All checks were successful
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 7s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 56s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 5m36s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 6m57s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 1m39s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m14s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m40s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m16s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m38s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m3s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 2m35s
The gateway decided a gated request over its arguments alone, but it forwarded the whole request. So a `_meta` key, a `params` member the method does not define, or a member of the JSON-RPC message beside `params` reached the server without passing the flow ceiling. A value from an untrusted read could leave by any of them. Each of those is now sealed and flow-checked along with the arguments. Two things are left out, and the code documents why: - The protocol's own `_meta` keys: `progressToken`, and keys under a prefix MCP reserves for itself, such as `io.modelcontextprotocol/`. These are machinery written by the client's runtime, and a server names them back in every reply. The flow store admits every payload it labels, so folding these keys would make the first tainted request taint every request after it. - This gateway's own namespace and its legacy witness key. A request that carries nothing else is decided over exactly the bytes it was before. This also fixes a latent false refusal. A `resources/read` was gated over its whole `params`, `_meta` included. Under MCP 2026-07-28 the protocol keys in every request matched the server's keys in every reply, so once one reply had been admitted, every later read under a flow ceiling was refused as tainted. A request's parts (arguments, answers to an interim result, what it carries beside them) are now scanned joined by a separator unique to that request, not wrapped in one JSON object. A wrapper's keys would be the same text in every request of that shape, and the stored admission of one tainted request would carry its taint into all the rest. SPEC §4.10 now says what "the argument" covers for a forwarding enforcement point, and what it may leave out if it says so. Tests: 6 new. All 13 mutations of the new logic are killed. Co-Authored-By: Claude Opus 5.5 <[email protected]>