Conversation
JSON-RPC and MCP rules apply to each HTTP request, but the proxy could forward a request that also carried upgrade headers. After an upstream answered 101, route selection and the forward proxy relayed the connection without inspection. Refuse any request that carries an Upgrade header on JSON-RPC-family endpoints before the L7 policy decision, in every enforcement mode. Share the check with the existing h2c refusal and call it from relay_jsonrpc as well. Record the refusal as a policy denial and answer with the unsupported_l7_protocol error, because no policy rule can allow the request. If a JSON-RPC-family endpoint still receives 101, close the connection instead of relaying raw bytes. Document the refusal and the WebSocket alternative. Signed-off-by: Shiju <shiju@nvidia.com>
| ``` | ||
|
|
||
| The `error` field is a short machine-readable code (`policy_denied`, `middleware_denied`, `middleware_failed`, `ssrf_denied`, `upstream_unreachable`). The `detail` field is a human-readable explanation suitable for display in an agent transcript. The optional `reason` field, when present, provides the specific denial cause from the policy engine (for example, which binary was not allowed or which rule was missing). | ||
| The `error` field is a short machine-readable code (`policy_denied`, `middleware_denied`, `middleware_failed`, `ssrf_denied`, `upstream_unreachable`, `unsupported_l7_protocol`). `unsupported_l7_protocol` means the request used a protocol or upgrade that the endpoint cannot inspect, such as h2c or an upgrade on an MCP or JSON-RPC endpoint; no policy rule can allow it. The `detail` field is a human-readable explanation suitable for display in an agent transcript. The optional `reason` field, when present, provides the specific denial cause from the policy engine (for example, which binary was not allowed or which rule was missing). |
There was a problem hiding this comment.
Re-defining what the new error here means seems redundant to the table above and also inconsistent as the other error codes are also not re-defined here.
|
Label |
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
This localized proxy hardening is project-valid, and the initial code review found no blocking defects. The implementation rejects JSON-RPC and MCP upgrade requests before upstream forwarding, preserves the supported non-upgrade paths, and includes matching architecture and Fern documentation.
Blocking findings:
- No blocking findings remain
Carried findings:
- None
Non-blocking suggestions:
- None
Gator metadata
- Validation: Localized network-proxy security and correctness fix from a repository collaborator with write permission
- Docs: Architecture and relevant Fern policy and observability docs updated; navigation change not needed
- Checks: Existing branch, Helm, Trivy, and DCO checks are green; required E2E rerun is queued
- E2E:
test:e2eapplied and current-head Branch E2E Checks rerun queued - Head SHA:
529b65729bbcce6e6381669047b011458758f704 - Base SHA:
d1a19c70ee3ed730e6e38ab66b76666a57b5e984 - Merge base SHA:
f37d89b5844a50d79726788989b1a5b27aa157e5 - Patch ID:
73bc65d654777c521da1ba73baf1004c2453ca20 - Gator payload:
9 - Review mode:
initial - Previous reviewed SHA: none
- Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:watch-pipeline
Summary
JSON-RPC and MCP endpoints apply their rules to each HTTP request, but the proxy could forward a request that also asked to switch protocols. After an upstream accepted, the connection was relayed without inspection. JSON-RPC-family endpoints now refuse any request that carries an
Upgradeheader with403before anything reaches the server, and close the connection if an upstream switches protocols anyway.Related Issue
No issue required: this is a localized fix to the proxy's upgrade handling.
Changes
unsupported_upgrade_detailnext to the h2c check. Every inspected protocol still refuses h2c. JSON-RPC and MCP endpoints also refuse any request that carries anUpgradeheader. Both relay checks that can lead to a protocol switch require that header, so the refusal covers everything the relay would treat as an upgrade.deny_h2c_upgrade_if_requestedtodeny_unsupported_upgrade_if_requestedand call it fromrelay_jsonrpcas well as route selection, REST and GraphQL. The refusal runs before the L7 policy decision and ignores the enforcement mode, like the existing h2c refusal.PolicyDeniedfor the endpoint and answers{"error":"unsupported_l7_protocol","detail":…}, the same body the forward proxy already used. On the relay paths this replaces the policy-denial body for h2c refusals as well, which told agents to add an allow rule that could not help.handle_upgradewhen a JSON-RPC-family endpoint still receives101, asrelay_jsonrpcalready did.architecture/security-policy.md,docs/how-it-works/policies/network-rules.mdx,docs/how-it-works/policies/schema.mdxanddocs/observability/logging.mdx, and addunsupported_l7_protocolto the denial table indocs/how-it-works/policies/manage-policies.mdx.Streamable HTTP traffic is unchanged: POST requests and receive-stream GETs without upgrade headers still pass, and REST and WebSocket endpoints on the same host and port still upgrade as before. Single-endpoint JSON-RPC and MCP now refuse h2c with
403, matching the other L7 paths.openshell policy updatealready rejects an MCP endpoint that shares a host and port with a differently inspected endpoint, and the MCP docs advise against that layout. A server that also accepts WebSocket needs a separateprotocol: websocketendpoint, on a different host or port for MCP, or on a different path for JSON-RPC.Testing
mise run pre-commitpassesChecklist