Repository navigation
feat(supervisor): add WebSocket message introspection middleware #2428
Description
Problem Statement
Supervisor middleware can inspect the HTTP request that upgrades a connection to WebSocket, but it cannot inspect messages exchanged after the upgrade. This leaves a gap for applications that carry sensitive or governed content in post-upgrade messages.
Subscription Codex uses WebSocket mode and sends prompts in post-upgrade messages. OpenShell can parse client-to-upstream text messages for built-in WebSocket policy and credential rewriting, but the current supervisor middleware contract exposes only HTTP_REQUEST/PRE_CREDENTIALS. Privacy Guard and other operator-run middleware therefore cannot inspect, redact, block, or annotate those prompts.
The same capability is needed by #2166 for realtime media carried in WebSocket text or binary messages. That issue also covers gRPC and broader media governance; this issue is limited to WebSocket message-level supervisor middleware.
Proposed Design
Add typed post-upgrade WebSocket message operations to supervisor middleware, corresponding to the WebSocketMessage/before_forward and WebSocketMessage/before_return hooks outlined by RFC 0009.
The middleware boundary should operate on complete logical messages, not raw RFC 6455 frames:
- OpenShell remains responsible for the WebSocket protocol, including masking, fragmentation and reassembly, compression, control frames, message limits, ordering, backpressure, and reframing.
- Each evaluation identifies the admitted destination and session, direction, text or binary message type, selected subprotocol, middleware configuration, and complete message content.
- Middleware returns allow or deny, optional replacement content, audit-safe findings, and metadata using semantics aligned with the existing HTTP request hook.
- A denied or invalid message terminates the WebSocket session with an appropriate policy-violation close instead of silently dropping the message.
- Upstream-to-client messages can be inspected or transformed but never receive credential injection or credential placeholder rewriting.
- Middleware selection and configuration remain policy-controlled. The gateway and supervisor validate that a registered middleware is authorized for the new operation and phase.
- The session uses one consistent policy and middleware snapshot. Reload behavior must not mix generations within an active session.
- OCSF events record decisions, limits, failures, and findings without logging message payloads, prompts, binary media, or credentials.
The implementation may use a bidirectional streaming gRPC operation so one middleware invocation can preserve session context, ordering, cancellation, and backpressure across messages. The exact protobuf shape should be finalized with the implementation, but it must keep WebSocket session processing distinct from the one-shot HTTP upgrade request.
Alternatives Considered
Inspect only the HTTP upgrade
The upgrade request does not contain prompts, media messages, or other application data sent after 101 Switching Protocols, so upgrade admission cannot govern message content.
Rely only on built-in WebSocket L7 policy
The existing policy path can evaluate supported client text messages, but it does not provide the external middleware contract, transformations, findings, or binary and return-path coverage needed by Privacy Guard and realtime media use cases.
Expose raw WebSocket frames to middleware
This leaks protocol mechanics into every middleware implementation and makes fragmentation, compression, control frames, and partial disclosure inconsistent. OpenShell should expose reconstructed logical messages and retain ownership of wire correctness.
Use opaque L4 passthrough
L4 passthrough carries bytes but cannot inspect, transform, deny, rate-limit, or audit individual WebSocket messages.
Agent Investigation
- Reviewed the Slack thread where the subscription Codex and Privacy Guard gap was identified.
- Reviewed feat: governed realtime media membrane #2166 and separated its WebSocket requirement from its gRPC and broader media scope.
- Confirmed RFC 0009 defines only
HTTP_REQUEST/PRE_CREDENTIALSin v1 and identifies futureWebSocketMessage/before_forwardandWebSocketMessage/before_returnphases. - Confirmed the current WebSocket relay reconstructs and evaluates client text messages, while binary messages and upstream-to-client traffic do not pass through supervisor middleware.
- Searched existing OpenShell issues and found no focused issue for WebSocket message-level supervisor middleware.
Definition of Done
- Supervisor middleware defines typed post-upgrade WebSocket message operation and phase contracts.
- The runtime evaluates complete logical text and binary messages without exposing raw frame mechanics to middleware.
- Policy can select middleware for WebSocket message processing and validation rejects unsupported or unauthorized bindings.
- Allow, deny, transform, findings, metadata, timeout, fail-open, and fail-closed behavior are defined for message processing.
- Denials and invalid transformations close the session safely without forwarding the rejected message.
- Session ordering, backpressure, cancellation, size limits, and reload snapshot behavior are deterministic.
- Automated tests cover fragmented and compressed messages, text and binary messages, both directions, transformations, denials, middleware failures, and no-middleware behavior.
- OCSF tests verify useful decision and failure events without payload or credential leakage.
- RFC 0009, architecture documentation, published middleware documentation, and policy reference documentation describe the new hook.
Checklist
- I've reviewed existing issues and the architecture docs.
- This is a design proposal, not a "please build this" request.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsPlanning
Activity
pimlock commentedon Jul 24, 2026
Implementation Plan
Issue type:
featComplexity: High
Confidence: Medium — all design decisions resolved; residual risk is concentrated in the RFC 6455 parser refactor
Summary
Add policy-selected supervisor middleware hooks for complete post-upgrade WebSocket messages in both directions, via a new bidirectional streaming gRPC operation. OpenShell keeps full ownership of RFC 6455 mechanics; middleware sees only complete logical messages. Delivered as two pull requests split at the return path, so the contract lands and gets exercised before the parser refactor begins.
Phase naming: this plan uses
WEBSOCKET_MESSAGE/PRE_CREDENTIALSandWEBSOCKET_MESSAGE/PRE_RETURNrather than the RFC 0009 working namesbefore_forward/before_return, to match the existing phase vocabulary and make the credential boundary explicit in the name. RFC 0009 gets updated to match.Scope
PR 1 — contract and forward path (runs on the existing, already-reviewed client parser; no RFC 6455 work)
proto/supervisor_middleware.proto: bidirectional streamingEvaluateWebSocket, typedpreflight/session_start/message/session_endrequest variants and their result variantscrates/openshell-supervisor-middleware/: binding-aware chain description, direction-scoped session runner, concurrency admission, deadline propagationcrates/openshell-supervisor-network/src/l7/: forward-direction text integration and preflight resolution at the upgrade-admission pointcrates/openshell-server/src/grpc/,crates/openshell-supervisor-network/src/opa.rs: generalize manifest validation from the single hard-codedHTTP_REQUEST/PRE_CREDENTIALSpair to an allowlistPR 2 — return path and binary
permessage-deflatestripping for inspected sessions, and the new extension mode it requiresNo changes to the policy YAML schema,
MiddlewareServiceFileConfig, orproto/sandbox.proto. Every limit introduced here is a platform constant; every option that would have required an operator field was deliberately deferred.Implementation Steps
max_message_bytes, deadline propagation, and the streaming RPCTest Plan
on_errorsettings, permit contention, chain budget expirymise run e2e:docker, since this changes sandbox network infrastructureKey Design Decisions
Resolved during review. Each of these was a genuine fork:
permessage-deflatewhen any stage inspects, rather than teaching the proxy to decompress and recompress. An upstream that selects a non-offered extension hard-fails the upgrade — every tolerant alternative hands the upstream a unilateral inspection bypass1008. Distinguishing them lets a workload detect when inspection is degraded, which underfail_openis exactly when its traffic goes unexamined.1002/1009/1012stay distinct because conforming clients need themreason_codeis recorded, never surfaced to the workload1009regardless ofon_error. Routing it through fail-open would let a workload escape inspection by exceeding a known thresholdRisks & Open Questions
1009once a WebSocket middleware is attached. Needs release-note treatment. Fix if anyone hits it is an operator-configured cap, which is additivefail_closedguarantees traffic is not forwarded when inspection fails. It does not guarantee every message is inspected, because a service may decline at preflight or allow a subprotocol it cannot parse. This needs to be stated plainly in the docsDocumentation Impact
rfc/0009-supervisor-middleware/— phase naming and the WebSocket contractarchitecture/sandbox.md,architecture/security-policy.md,architecture/gateway.mddocs/extensibility/supervisor-middleware.mdx,docs/reference/policy-schema.mdx,docs/sandboxes/policies.mdx,docs/security/best-practices.mdxdocs/reference/gateway-config.mdxchange — no gateway TOML keys, driver options, or defaults change/procaccess, or inter-process visibilitysync-agent-inframaintenance map:generate-sandbox-policy,openshell-cli,debug-openshell-clusterRevision 1 — initial plan
pimlock commentedon Jul 25, 2026
PR1 is implemented in #2477.
Highlights:
response.createevent; the relay test proves the upstream never receives the original secret-bearing payload.Validation completed:
mise run pre-commitmise run cimise run e2e:websocket-conformancemise run e2e:dockergithub-actions commentedon Aug 8, 2026
This issue has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. Comment or remove the state:stale label to keep it open.