You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OpenShell can authorize MCP requests and resolve provider credential placeholders immediately before an upstream request is sent, but it has no equivalent trust boundary on the response path. JSON-RPC responses and MCP SSE events are currently relayed to the sandbox unchanged.
That leaves no safe primitive for authenticated discovery such as tools/list when the caller must prove all of the following:
The real credential is resolved and used only inside the trusted OpenShell boundary.
Raw credential-dependent upstream bytes never become sandbox- or agent-visible.
Only an explicitly defined, bounded discovery result is released.
Discovery does not grant permission to invoke a tool.
Malformed, oversized, uncorrelated, stale, or policy-inconsistent responses fail closed.
This is blocking consumers that need to bootstrap an authenticated MCP provider without first giving an agent access to the provider's raw discovery response. One concrete consumer is NVIDIA/NemoClaw: NemoClaw#6901 and the security review on NemoClaw#6904 require authenticated tool discovery without exposing credential-bearing values or trusting unvalidated upstream content.
This is not merely exact-secret redaction. A credential-aware or compromised upstream can encode, transform, or derive a secret into otherwise valid strings, and arbitrary tool descriptions/schemas may contain untrusted instructions. OpenShell needs an explicit isolation and declassification contract.
Proposed Design
Add a provider-bound, protocol-aware MCP discovery boundary owned by the trusted OpenShell runtime. The preferred implementation direction is a supervisor-owned response gate, because the supervisor already owns TLS termination, request policy evaluation, credential injection, and the live MCP session. A host/control-plane operation may initiate the discovery and return the typed result to an authenticated operator, but the protocol parsing and response isolation should remain next to the session in the supervisor.
The design should satisfy these invariants:
Provider-bound operation. Discovery is authorized against an existing sandbox, effective policy endpoint, and attached provider. It must not accept an arbitrary URL, hostname, Authorization value, or credential from the caller.
Explicit trusted recipient. If a host/control-plane API returns discovered names, it does so only to an authenticated, authorized operator under an explicit declassification mode. Raw upstream bytes are never returned and never enter the sandbox.
Protocol-aware correlation. Track the relevant MCP initialization, negotiated protocol version, session, request ID, response ID, pagination state, and SSE events. Do not treat generic HTTP success as valid MCP discovery.
Normalize and re-encode. Parse a complete response, validate it against the selected MCP profile, select only allowed fields, apply bounds, and encode a new typed result. Do not release slices of the original response body.
Separate discovery from invocation authority. Intersect discovered tools with the effective tools/call policy. A discovery result can describe what exists but must never expand what the sandbox may call.
Explicit output modes. At minimum, distinguish:
an operator-authorized declassification mode that may return normalized tool names to the trusted caller; and
a stronger verification mode that compares against credential-independent, operator-managed or signed catalog metadata and returns only a locally sourced catalog or fixed verdict.
Tool names selected by an authenticated server are still credential-dependent content. Validation, exact-secret removal, or a names-only projection does not make those names credential-independent.
Fail closed and bound work. Reject malformed, unsupported, oversized, compressed-but-unsupported, uncorrelated, policy-stale, or incomplete discovery responses. Bound total bytes, JSON depth, field lengths, tool count, schema size, pages/cursors, SSE event size, and time.
Audit safely. Emit OCSF allow/deny/error events and useful dimensions such as provider, endpoint, protocol version, count, validation result, and policy generation without logging credentials, raw response content, descriptions, or schemas.
A policy-declared exact hostname must not be used as the content-trust signal. Today that declaration controls network reachability/SSRF behavior; it does not establish response integrity, MCP authorization scope, or permission to declassify tool definitions.
Current Architecture
At pinned OpenShell main commit a2cd5f8edae6d4587b10f965080f5a9cf0851afe, the current path is:
sandbox request
-> CONNECT identity / SSRF / L4 policy
-> TLS termination
-> MCP JSON-RPC request parsing and method policy
-> PRE_CREDENTIALS request middleware
-> supervisor SecretResolver injects the real credential
-> authenticated upstream MCP server
-> HTTP framing only
-> raw JSON / SSE bytes forwarded to sandbox
The request path has a trusted credential boundary. The response path does not yet have a corresponding validation/declassification boundary.
Technical investigation
crates/openshell-supervisor-network/src/proxy.rs:1181 and :1218 create the L7 evaluation context containing the supervisor-only SecretResolver.
crates/openshell-supervisor-network/src/proxy.rs:1292 and crates/openshell-supervisor-network/src/l7/relay.rs:245 dispatch protocol: mcp to the JSON-RPC relay after TLS termination.
crates/openshell-supervisor-network/src/l7/relay.rs:1108 parses and evaluates the sandbox-to-server request.
crates/openshell-supervisor-network/src/l7/jsonrpc.rs:67 recognizes the SSE receive-stream GET and otherwise buffers only the request body.
crates/openshell-supervisor-network/src/l7/jsonrpc.rs:89 retains calls/batch/stream flags but does not retain the JSON-RPC IDs needed for robust request/response correlation.
crates/openshell-supervisor-network/src/l7/relay.rs:1229 and :1272 pass the request into the shared REST relay with the secret resolver.
crates/openshell-supervisor-network/src/l7/rest.rs:616 and :667 resolve credential placeholders in outbound headers and write the authenticated request upstream.
crates/openshell-supervisor-network/src/l7/rest.rs:947 enters the generic response relay.
crates/openshell-supervisor-network/src/l7/rest.rs:2687-2858 parses HTTP framing, then forwards content-length, chunked, and text/event-stream bodies without MCP-aware validation. The SSE path can remain open until EOF and has no discovery-specific idle bound.
crates/openshell-supervisor-network/src/l7/relay.rs:1267-1278 documents the future response/SSE inspection seam before the generic relay, but no trusted response contract is implemented.
architecture/sandbox.md:52-73, docs/reference/policy-schema.mdx:289-301, and docs/sandboxes/policies.mdx:623-633 describe the same limitation.
The behavior is also present in release v0.0.85; this is not just an unreleased-main regression.
Estimated scope is roughly 10-18 files for a supervisor-local gate and wider if a new gateway/CLI discovery operation or persistent catalog is added. No meaningful SELinux/AppArmor-specific impact is expected for a supervisor-local parser. A new host service, socket, or persisted catalog requires separate LSM and filesystem-label review.
Existing Work and Duplicate Check
No searched issue or PR provides this complete boundary.
#1938, merged, added MCP request governance. It intentionally preserves response frames and server-to-client messages without policy inspection. It establishes the gap but does not close it.
#2174 proposes version-aware MCP wire profiles and discusses response/SSE introspection and trusted annotations. It is a likely prerequisite/partial overlap, but it does not define credential-derived response isolation, catalog declassification, cache scoping, or closure of raw sandbox bypass paths.
#1733 and merged #2027 added HTTP_REQUEST/PRE_CREDENTIALS. Response-body scanning is explicitly outside the v1 middleware scope.
#2011 covers external request middleware transport/authentication, not a response operation or MCP-specific semantics.
#1272 proposed generic request/response filters and was closed in favor of Sandbox egress middleware #1733. Generic content scanning alone cannot provide MCP correlation or a safe declassification contract.
#2169 adds request content guarding, not response isolation.
#1694 proposes a post-credential request hook, not a response boundary.
A safer host-side discovery API does not solve the boundary if a protected sandbox can still obtain equivalent raw content by:
issuing tools/list directly;
opening the MCP SSE receive stream;
selecting an alternate endpoint/path;
using plaintext or tls: skip;
reaching the provider through a non-L7 credential relay;
receiving list-change notifications, server requests, or equivalent tool definitions on a live stream;
reusing a stale session, catalog, credential revision, or policy generation.
For providers using the protected discovery mode, these paths must be intercepted, denied, or routed through the same response gate.
Identity, Cache, and Freshness Requirements
If discovery results are cached or shared, the cache key and authorization check must include at least:
provider instance and endpoint/path, not hostname alone;
credential identity and credential revision;
sandbox, tenant, and principal scope where authorization differs;
negotiated MCP protocol profile and session;
effective tool-call policy generation;
pagination completeness;
catalog freshness and revocation state.
A gateway-owned catalog is viable when discovery is deliberately configuration-time and shared, but it broadens the gateway into an outbound MCP client. That would require explicit decisions for gateway egress, TLS/redirect/SSRF behavior, credential use, lifecycle/session ownership, retries/timeouts, and multi-tenant cache isolation. Prefer keeping live protocol handling in the supervisor and returning only a typed result through the control plane.
Required Tests
Correlate tools/list responses to request IDs; reject missing, duplicate, wrong, null, or stale IDs as appropriate to the selected MCP version.
Cover SSE comments, multiline data, split events, oversized events, idle gaps, notifications, server requests, list-changed flows, and no partial release of an invalid event.
Prove direct tools/list, receive-stream GET, alternate paths, plaintext, tls: skip, and non-L7 credential relay cannot bypass the boundary for a protected provider.
Use a fake authenticated upstream that observes the resolved Authorization header and attempts exact, encoded, and derived credential reflection. Assert the sandbox receives only the defined normalized output and logs contain no secret.
Test credential rotation, policy hot reload, stale sessions/catalogs, sandbox isolation, and cache-key separation.
Keep supported MCP conformance scenarios passing.
Add OCSF allow/deny/error assertions without raw response content.
Acceptance Criteria
An authorized caller can request authenticated MCP discovery only for an existing provider-bound endpoint under an effective sandbox policy.
The real provider credential is resolved and used only inside the trusted OpenShell boundary.
No raw discovery response bytes or partial SSE events reach the sandbox or caller.
The implementation correlates and validates the selected MCP version/session/request and emits a newly encoded, typed result.
The output/declassification mode is explicit, documented, and covered by threat-model tests.
Discovery never grants invocation authority and is intersected with the effective tools/call policy.
Protected providers cannot bypass the gate through direct discovery, SSE, alternate routes, or stale state.
Resource, pagination, and time bounds are enforced and failures are closed.
Credential rotation and policy changes invalidate relevant state.
Audit events contain useful metadata but no raw response, schema, description, or secret.
The policy schema, provider profiles, architecture/security docs, user docs, and MCP conformance/E2E coverage are updated.
Forward the raw authenticated response to the sandbox
Rejected. This is the current behavior and violates the isolation requirement.
Return only tool names
Insufficient by itself. Tool names are still selected by the authenticated upstream and remain credential-dependent content. Names-only can be a valid explicit declassification mode for a trusted operator, but it is not credential-independent verification.
Remove exact credential strings from the response
Rejected as a security boundary. Exact redaction does not cover encoding, derivation, semantic leakage, or tool poisoning.
Trust every response from a policy-declared hostname
Rejected. Network destination authorization is not a content-integrity or declassification decision.
Use generic response middleware only
Useful as a future complement for content scanning, but insufficient to own MCP versioning, request/response correlation, sessions, pagination, SSE framing, policy intersection, and invocation authorization. External middleware would also receive sensitive credential-derived content and therefore needs a stronger trust/authentication contract than the current pre-credential request hook.
Discover in the gateway and cache globally
Possible, but substantially broadens gateway responsibilities and creates egress, session, freshness, revocation, and multi-tenant isolation concerns. If selected, the catalog must remain provider/principal/policy scoped and raw sandbox discovery must still be closed.
Require an operator-managed or signed catalog
This provides the strongest credential-independent verification mode and should remain an available design. It may not fit dynamic providers, so it can coexist with an explicitly authorized operator-declassification mode.
Risks and Open Questions
Which threat model is required: accidental verbatim reflection, compromised upstream, malicious upstream/tool poisoning, or all of them?
Which fields may be declassified in each mode: names, descriptions, input schemas, output schemas, annotations, icons, and metadata?
Is the first consumer an authenticated host operator, a sandbox process, or both?
Should protected discovery be a new policy mode on an MCP endpoint, a provider profile capability, or both?
How are SSE list-change notifications and server-initiated messages handled after initial discovery?
Is a strict locally sourced/verified catalog required for high-assurance use cases?
What are the default byte, count, depth, pagination, and idle-time bounds?
How should policy or credential rotation invalidate live sessions and cached catalogs?
If a generic response middleware operation is later added, what sensitive-data contract and trust model applies to that middleware?
Agent Investigation
Investigation covered current OpenShell main at a2cd5f8edae6d4587b10f965080f5a9cf0851afe, release v0.0.85, request and response relay code, policy/provider protobuf round trips, middleware RFC/protobufs, architecture and policy documentation, MCP conformance/E2E coverage, and adjacent open/merged/closed issues and PRs. The proposed direction was independently reviewed with particular attention to hostname trust, bypass paths, protocol correlation, cache identity, and gateway responsibility.
Complexity: High
Confidence: Medium
Checklist
I searched for existing issues and PRs covering this capability.
I reviewed the current request, credential-injection, HTTP response, JSON-RPC, and SSE paths.
I identified affected policy, provider, supervisor, gateway/control-plane, documentation, and test surfaces.
I documented alternatives, security invariants, bypass paths, risks, and acceptance criteria.
No security vulnerability details or exploit reproduction steps are included; this is a design/feature gap.
Maintainers choose the response trust model and approve the implementation direction.
The OpenShell contributor triage workflow has applied state:triage-needed because the author does not have repository write permission. The investigation is complete and ready for human review; a maintainer can apply the appropriate area labels and state:review-ready after triage. Only a maintainer should apply state:agent-ready after the contract and threat model are approved.
Problem Statement
OpenShell can authorize MCP requests and resolve provider credential placeholders immediately before an upstream request is sent, but it has no equivalent trust boundary on the response path. JSON-RPC responses and MCP SSE events are currently relayed to the sandbox unchanged.
That leaves no safe primitive for authenticated discovery such as
tools/listwhen the caller must prove all of the following:This is blocking consumers that need to bootstrap an authenticated MCP provider without first giving an agent access to the provider's raw discovery response. One concrete consumer is NVIDIA/NemoClaw: NemoClaw#6901 and the security review on NemoClaw#6904 require authenticated tool discovery without exposing credential-bearing values or trusting unvalidated upstream content.
This is not merely exact-secret redaction. A credential-aware or compromised upstream can encode, transform, or derive a secret into otherwise valid strings, and arbitrary tool descriptions/schemas may contain untrusted instructions. OpenShell needs an explicit isolation and declassification contract.
Proposed Design
Add a provider-bound, protocol-aware MCP discovery boundary owned by the trusted OpenShell runtime. The preferred implementation direction is a supervisor-owned response gate, because the supervisor already owns TLS termination, request policy evaluation, credential injection, and the live MCP session. A host/control-plane operation may initiate the discovery and return the typed result to an authenticated operator, but the protocol parsing and response isolation should remain next to the session in the supervisor.
The design should satisfy these invariants:
Provider-bound operation. Discovery is authorized against an existing sandbox, effective policy endpoint, and attached provider. It must not accept an arbitrary URL, hostname, Authorization value, or credential from the caller.
Explicit trusted recipient. If a host/control-plane API returns discovered names, it does so only to an authenticated, authorized operator under an explicit declassification mode. Raw upstream bytes are never returned and never enter the sandbox.
Protocol-aware correlation. Track the relevant MCP initialization, negotiated protocol version, session, request ID, response ID, pagination state, and SSE events. Do not treat generic HTTP success as valid MCP discovery.
Normalize and re-encode. Parse a complete response, validate it against the selected MCP profile, select only allowed fields, apply bounds, and encode a new typed result. Do not release slices of the original response body.
Separate discovery from invocation authority. Intersect discovered tools with the effective
tools/callpolicy. A discovery result can describe what exists but must never expand what the sandbox may call.Explicit output modes. At minimum, distinguish:
Tool names selected by an authenticated server are still credential-dependent content. Validation, exact-secret removal, or a names-only projection does not make those names credential-independent.
Fail closed and bound work. Reject malformed, unsupported, oversized, compressed-but-unsupported, uncorrelated, policy-stale, or incomplete discovery responses. Bound total bytes, JSON depth, field lengths, tool count, schema size, pages/cursors, SSE event size, and time.
Audit safely. Emit OCSF allow/deny/error events and useful dimensions such as provider, endpoint, protocol version, count, validation result, and policy generation without logging credentials, raw response content, descriptions, or schemas.
A policy-declared exact hostname must not be used as the content-trust signal. Today that declaration controls network reachability/SSRF behavior; it does not establish response integrity, MCP authorization scope, or permission to declassify tool definitions.
Current Architecture
At pinned OpenShell main commit
a2cd5f8edae6d4587b10f965080f5a9cf0851afe, the current path is:The request path has a trusted credential boundary. The response path does not yet have a corresponding validation/declassification boundary.
Technical investigation
crates/openshell-supervisor-network/src/proxy.rs:1181and:1218create the L7 evaluation context containing the supervisor-onlySecretResolver.crates/openshell-supervisor-network/src/proxy.rs:1292andcrates/openshell-supervisor-network/src/l7/relay.rs:245dispatchprotocol: mcpto the JSON-RPC relay after TLS termination.crates/openshell-supervisor-network/src/l7/relay.rs:1108parses and evaluates the sandbox-to-server request.crates/openshell-supervisor-network/src/l7/jsonrpc.rs:67recognizes the SSE receive-stream GET and otherwise buffers only the request body.crates/openshell-supervisor-network/src/l7/jsonrpc.rs:89retains calls/batch/stream flags but does not retain the JSON-RPC IDs needed for robust request/response correlation.crates/openshell-supervisor-network/src/l7/relay.rs:1229and:1272pass the request into the shared REST relay with the secret resolver.crates/openshell-supervisor-network/src/l7/rest.rs:616and:667resolve credential placeholders in outbound headers and write the authenticated request upstream.crates/openshell-supervisor-network/src/l7/rest.rs:947enters the generic response relay.crates/openshell-supervisor-network/src/l7/rest.rs:2687-2858parses HTTP framing, then forwards content-length, chunked, andtext/event-streambodies without MCP-aware validation. The SSE path can remain open until EOF and has no discovery-specific idle bound.crates/openshell-supervisor-network/src/l7/relay.rs:1267-1278documents the future response/SSE inspection seam before the generic relay, but no trusted response contract is implemented.architecture/sandbox.md:52-73,docs/reference/policy-schema.mdx:289-301, anddocs/sandboxes/policies.mdx:623-633describe the same limitation.The behavior is also present in release
v0.0.85; this is not just an unreleased-main regression.Affected Components
proto/sandbox.proto:173-210crates/openshell-policy/src/lib.rs:171-204,547-571crates/openshell-providers/src/profiles.rs:210-243,930-1001crates/openshell-supervisor-network/src/l7/mod.rs:95-136,203-267crates/openshell-supervisor-network/src/l7/jsonrpc.rs:18-98,198-295crates/openshell-supervisor-network/src/l7/relay.rs:1108-1293crates/openshell-supervisor-network/src/l7/rest.rs:616-959,2670-2856crates/openshell-supervisor-network/src/proxy.rs:1181-1244,1292-1452proto/openshell.protoand the corresponding gateway/CLI handlersarchitecture/sandbox.md,architecture/security-policy.md,docs/reference/policy-schema.mdx,docs/sandboxes/policies.mdxe2e/mcp-conformance/,e2e/rust/tests/forward_proxy_jsonrpc_l7.rsEstimated scope is roughly 10-18 files for a supervisor-local gate and wider if a new gateway/CLI discovery operation or persistent catalog is added. No meaningful SELinux/AppArmor-specific impact is expected for a supervisor-local parser. A new host service, socket, or persisted catalog requires separate LSM and filesystem-label review.
Existing Work and Duplicate Check
No searched issue or PR provides this complete boundary.
HTTP_REQUEST/PRE_CREDENTIALS. Response-body scanning is explicitly outside the v1 middleware scope.Bypass Paths the Design Must Close
A safer host-side discovery API does not solve the boundary if a protected sandbox can still obtain equivalent raw content by:
tools/listdirectly;tls: skip;For providers using the protected discovery mode, these paths must be intercepted, denied, or routed through the same response gate.
Identity, Cache, and Freshness Requirements
If discovery results are cached or shared, the cache key and authorization check must include at least:
A gateway-owned catalog is viable when discovery is deliberately configuration-time and shared, but it broadens the gateway into an outbound MCP client. That would require explicit decisions for gateway egress, TLS/redirect/SSRF behavior, credential use, lifecycle/session ownership, retries/timeouts, and multi-tenant cache isolation. Prefer keeping live protocol handling in the supervisor and returning only a typed result through the control plane.
Required Tests
tools/listresponses to request IDs; reject missing, duplicate, wrong, null, or stale IDs as appropriate to the selected MCP version.tools/callpolicy.data, split events, oversized events, idle gaps, notifications, server requests, list-changed flows, and no partial release of an invalid event.tools/list, receive-stream GET, alternate paths, plaintext,tls: skip, and non-L7 credential relay cannot bypass the boundary for a protected provider.Acceptance Criteria
tools/callpolicy.Alternatives Considered
Forward the raw authenticated response to the sandbox
Rejected. This is the current behavior and violates the isolation requirement.
Return only tool names
Insufficient by itself. Tool names are still selected by the authenticated upstream and remain credential-dependent content. Names-only can be a valid explicit declassification mode for a trusted operator, but it is not credential-independent verification.
Remove exact credential strings from the response
Rejected as a security boundary. Exact redaction does not cover encoding, derivation, semantic leakage, or tool poisoning.
Trust every response from a policy-declared hostname
Rejected. Network destination authorization is not a content-integrity or declassification decision.
Use generic response middleware only
Useful as a future complement for content scanning, but insufficient to own MCP versioning, request/response correlation, sessions, pagination, SSE framing, policy intersection, and invocation authorization. External middleware would also receive sensitive credential-derived content and therefore needs a stronger trust/authentication contract than the current pre-credential request hook.
Discover in the gateway and cache globally
Possible, but substantially broadens gateway responsibilities and creates egress, session, freshness, revocation, and multi-tenant isolation concerns. If selected, the catalog must remain provider/principal/policy scoped and raw sandbox discovery must still be closed.
Require an operator-managed or signed catalog
This provides the strongest credential-independent verification mode and should remain an available design. It may not fit dynamic providers, so it can coexist with an explicitly authorized operator-declassification mode.
Risks and Open Questions
Agent Investigation
Investigation covered current OpenShell main at
a2cd5f8edae6d4587b10f965080f5a9cf0851afe, releasev0.0.85, request and response relay code, policy/provider protobuf round trips, middleware RFC/protobufs, architecture and policy documentation, MCP conformance/E2E coverage, and adjacent open/merged/closed issues and PRs. The proposed direction was independently reviewed with particular attention to hostname trust, bypass paths, protocol correlation, cache identity, and gateway responsibility.Complexity: High
Confidence: Medium
Checklist
The OpenShell contributor triage workflow has applied
state:triage-neededbecause the author does not have repository write permission. The investigation is complete and ready for human review; a maintainer can apply the appropriate area labels andstate:review-readyafter triage. Only a maintainer should applystate:agent-readyafter the contract and threat model are approved.