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
As an operator, I want attached supervisor middleware to inspect, transform, or block HTTP responses before the sandbox receives them.
Problem statement
Supervisor middleware supports HTTP_REQUEST/PRE_CREDENTIALS, but it has no HTTP_RESPONSE/PRE_RETURN hook. OpenShell therefore forwards upstream responses without running attached response policy. Asynchronous inspection cannot enforce this hook because it cannot recall bytes already delivered to the sandbox.
Operators cannot apply generic response controls such as PII detection, redaction, secret scanning, or compliance filtering in the configured middleware chain. The current workaround requires another proxy and loses OpenShell's policy order, failure behavior, and audit context.
Proposed design
Add an HTTP_RESPONSE/PRE_RETURN binding and a hook-specific bidirectional inspection API. The API models HTTP response semantics directly rather than reusing the request contract.
Each selected stage receives a preflight with the final response status, safe headers, request context, stage-local body-mode choices, and supervisor limits. It may skip, inspect, or block_delivery. An inspecting stage selects headers-only processing, one bounded whole-body value, or normalized response-body units. Every body unit has a stage-local sequence and end_of_stream marks the final result-bearing unit.
Body-inspecting stages that complete normally receive one normalized trailer event, including when the trailer set is empty, and return one trailer result. V1 can modify or remove existing trailer names but cannot create a name absent from the event.
block_delivery is a successful policy decision, not a middleware failure. Before response commitment, OpenShell returns its canonical 403 middleware_denied response. After commitment, OpenShell aborts downstream delivery. The upstream request has already run, so blocking its response does not reject or roll back that request.
STREAM_BYTES is unit-local in V1: every result accounts for its complete input unit, and middleware cannot retain input across units. skip_remaining finalizes the current unit and ends inspection for that stage while later stages continue. Middleware failures still follow on_error. Fail-open continues from the last input OpenShell still owns; fail-closed returns a clean error before commitment and aborts after commitment.
Policy attachment remains unchanged. Attaching middleware activates every operation and phase advertised by its manifest.
This issue includes the raw hook protocol and one response-capable example. A middleware-author SDK or callback adapter is follow-up work.
The build-plan comment defines the complete contract and implementation details.
Acceptance criteria
An attached service can advertise HTTP_RESPONSE/PRE_RETURN and execute it through HttpResponsePreReturn.Evaluate using the finalized V1 message names.
Preflight supports skip, inspect, and block_delivery. Result-envelope diagnostics apply to every action and invalid diagnostics follow on_error.
OpenShell derives permitted_body_modes from the original final response head and each stage's effective payload limit. Different stages may receive different lists.
HTTP request and response middleware can write permitted end-to-end headers without an x-openshell-middleware- namespace restriction. Both paths use one shared validator and atomic applicator with direction-specific protected fields.
Every normally completed body inspection receives one final result-bearing unit with end_of_stream = true, including a present empty sequence-1 unit for an empty body-capable response.
Every body input receives exactly one ordered pass_through, transform, block_delivery, or skip_remaining result with the matching stage-local sequence.
STREAM_BYTES is unit-local. Every result fully accounts for the current input, and V1 does not permit cross-unit retention.
skip_remaining ends body and trailer events only for that stage. The current and later units continue through later stages.
A normally completed whole-body or streaming stage receives one trailer event after its final body result, including for an empty trailer set, and returns one trailer result.
V1 trailer results may modify or remove names present in the trailer event. They cannot introduce a missing name.
OpenShell does not expose HTTP/1 transfer chunks or socket-read boundaries to middleware.
OpenShell repairs downstream framing after transformation and excludes protocol upgrades from generic response-body processing.
OpenShell relays interim 1xx responses unchanged and evaluates only the final non-1xx response head.
Before commitment, block_delivery returns the canonical 403 middleware_denied; HEAD receives the same headers and content length without a body. After commitment, OpenShell aborts delivery and does not reuse upstream.
Before commitment, fail-closed middleware failure returns the canonical 502 response_delivery_failed; HEAD receives the same headers and content length without a body. After commitment, fail-closed aborts delivery.
STREAM_BYTES inputs are at most min(64 KiB, max_payload_bytes). Each result uses the effective binding timeout and the existing 30-second per-unit chain cap. Streaming has no total response-lifetime deadline.
Whole-body capacity failures use whole_body_over_capacity. A dedicated whole-body accumulation deadline and its configuration remain required before the response dispatcher lands.
Shared terminal reasons distinguish sandbox and upstream disconnects.
Tests cover ordered and mixed-profile chains, bodyless responses, content-length, chunked and close-delimited bodies, replacement, whole-body overflow, non-identity content encoding, partial responses, original no-transform, stale integrity metadata, trailers, block delivery, unit-local streaming, skip remaining, timeouts, directional disconnects, and policy reload.
HTTP requests, HTTP responses, and WebSocket messages have different heads, boundaries, mutation rules, commitment points, and denial behavior. A universal event union would move those rules out of the interface and into prose and runtime validation. The implementation may share flow-control machinery, but the public API remains hook-specific.
Observe responses asynchronously
This preserves latency but permits partial disclosure, so it cannot enforce PRE_RETURN.
Add only a unary buffered response RPC
This cannot support SSE, long-lived responses, or later rolling-window processing without introducing another API.
Reuse EvaluateHttpRequest
Requests and responses differ in status, headers, trailers, framing, mutation authority, and commitment. They need separate contracts.
User story
As an operator, I want attached supervisor middleware to inspect, transform, or block HTTP responses before the sandbox receives them.
Problem statement
Supervisor middleware supports
HTTP_REQUEST/PRE_CREDENTIALS, but it has noHTTP_RESPONSE/PRE_RETURNhook. OpenShell therefore forwards upstream responses without running attached response policy. Asynchronous inspection cannot enforce this hook because it cannot recall bytes already delivered to the sandbox.Parent: #1733.
Impact / why this matters
Operators cannot apply generic response controls such as PII detection, redaction, secret scanning, or compliance filtering in the configured middleware chain. The current workaround requires another proxy and loses OpenShell's policy order, failure behavior, and audit context.
Proposed design
Add an
HTTP_RESPONSE/PRE_RETURNbinding and a hook-specific bidirectional inspection API. The API models HTTP response semantics directly rather than reusing the request contract.Each selected stage receives a preflight with the final response status, safe headers, request context, stage-local body-mode choices, and supervisor limits. It may
skip,inspect, orblock_delivery. An inspecting stage selects headers-only processing, one bounded whole-body value, or normalized response-body units. Every body unit has a stage-local sequence andend_of_streammarks the final result-bearing unit.Body-inspecting stages that complete normally receive one normalized trailer event, including when the trailer set is empty, and return one trailer result. V1 can modify or remove existing trailer names but cannot create a name absent from the event.
block_deliveryis a successful policy decision, not a middleware failure. Before response commitment, OpenShell returns its canonical403 middleware_deniedresponse. After commitment, OpenShell aborts downstream delivery. The upstream request has already run, so blocking its response does not reject or roll back that request.STREAM_BYTESis unit-local in V1: every result accounts for its complete input unit, and middleware cannot retain input across units.skip_remainingfinalizes the current unit and ends inspection for that stage while later stages continue. Middleware failures still followon_error. Fail-open continues from the last input OpenShell still owns; fail-closed returns a clean error before commitment and aborts after commitment.Policy attachment remains unchanged. Attaching middleware activates every operation and phase advertised by its manifest.
This issue includes the raw hook protocol and one response-capable example. A middleware-author SDK or callback adapter is follow-up work.
The
build-plancomment defines the complete contract and implementation details.Acceptance criteria
HTTP_RESPONSE/PRE_RETURNand execute it throughHttpResponsePreReturn.Evaluateusing the finalized V1 message names.skip,inspect, andblock_delivery. Result-envelope diagnostics apply to every action and invalid diagnostics followon_error.permitted_body_modesfrom the original final response head and each stage's effective payload limit. Different stages may receive different lists.x-openshell-middleware-namespace restriction. Both paths use one shared validator and atomic applicator with direction-specific protected fields.end_of_stream = true, including a present empty sequence-1 unit for an empty body-capable response.pass_through,transform,block_delivery, orskip_remainingresult with the matching stage-local sequence.STREAM_BYTESis unit-local. Every result fully accounts for the current input, and V1 does not permit cross-unit retention.skip_remainingends body and trailer events only for that stage. The current and later units continue through later stages.block_deliveryreturns the canonical403 middleware_denied;HEADreceives the same headers and content length without a body. After commitment, OpenShell aborts delivery and does not reuse upstream.502 response_delivery_failed;HEADreceives the same headers and content length without a body. After commitment, fail-closed aborts delivery.STREAM_BYTESinputs are at mostmin(64 KiB, max_payload_bytes). Each result uses the effective binding timeout and the existing 30-second per-unit chain cap. Streaming has no total response-lifetime deadline.whole_body_over_capacity. A dedicated whole-body accumulation deadline and its configuration remain required before the response dispatcher lands.no-transform, stale integrity metadata, trailers, block delivery, unit-local streaming, skip remaining, timeouts, directional disconnects, and policy reload.Alternatives considered
Use one universal inspection RPC
HTTP requests, HTTP responses, and WebSocket messages have different heads, boundaries, mutation rules, commitment points, and denial behavior. A universal event union would move those rules out of the interface and into prose and runtime validation. The implementation may share flow-control machinery, but the public API remains hook-specific.
Observe responses asynchronously
This preserves latency but permits partial disclosure, so it cannot enforce
PRE_RETURN.Add only a unary buffered response RPC
This cannot support SSE, long-lived responses, or later rolling-window processing without introducing another API.
Reuse
EvaluateHttpRequestRequests and responses differ in status, headers, trailers, framing, mutation authority, and commitment. They need separate contracts.