Skip to content

feat(docker): support host and bridge supervisor networking - #4016

Merged
drew merged 11 commits into
mainfrom
codex/4015-docker-supervisor-networking
Oct 16, 2026
Merged

drew merged 11 commits into
mainfrom
codex/4015-docker-supervisor-networking

Conversation

@drew

@drew drew commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The Docker supervisor previously required host networking. Add an operator-owned supervisor_network_mode setting so deployments can explicitly use host or bridge networking. Host remains the default, and auto selects host.

Bridge callbacks preserve explicit endpoints and TLS verification. Docker's actual host address is discovered automatically once when the driver starts, so Desktop deployments do not confuse the VM's bridge gateway with the host. All runtime changes are confined to the Docker driver; the supervisor is unchanged.

Related Issue

Related to #4015. Automatic ECI mode selection and ECI sandbox-runtime compatibility remain outside this PR's scope.

Changes

  • Support host and bridge supervisor networking while retaining network=none and the existing isolation requirements for workloads.
  • For bridge mode or a gateway endpoint using host.docker.internal, resolve Docker's host-gateway once during driver construction and cache the IP for all sandbox launches.
  • Use Docker API calls to create a temporary container from the immutable supervisor image, run --help, wait for exit, and download Docker's generated /etc/hosts through the archive endpoint. No shell or docker exec is used.
  • Confine the resolver with no network, all capabilities dropped, no privilege escalation, and a read-only root filesystem. Bound discovery to 30 seconds and perform forced cleanup after success or failure with a separate 10-second timeout.
  • Use the same validated IP for host aliases and the runtime descriptor's host trust pin. An explicit operator-owned host_gateway_ip remains an optional override that skips discovery.
  • Update configuration/runtime docs, the local Docker launcher, and operational guidance, including OTLP endpoint reachability.

Validation

  • Docker driver library tests: 144 passed, 2 opt-in live tests ignored by default.
  • Opt-in Docker API resolver integration test passed on Docker Desktop: discovered 192.168.65.254 and verified no resolver container remained after either successful discovery or a deliberately failed container start.
  • Focused Docker driver Clippy, formatting, Markdown lint, and diff whitespace checks passed.
  • The Docker Desktop sandbox capability probe still fails at seccomp notification qualification under the enabled ECI runtime. Full sandbox E2E remains blocked by that separate runtime limitation.

@copy-pr-bot

copy-pr-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@drew
drew force-pushed the codex/4015-docker-supervisor-networking branch from d633562 to 16bad48 Compare October 5, 2026 04:20
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown

drew added 5 commits October 11, 2026 22:30
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
@drew
drew force-pushed the codex/4015-docker-supervisor-networking branch from 9e2288b to 8f122f7 Compare October 12, 2026 05:34
@drew
drew marked this pull request as ready for review October 12, 2026 23:24
@drew drew added the test:e2e Requires end-to-end coverage label Oct 12, 2026
@github-actions

Copy link
Copy Markdown

Label test:e2e applied for 8f122f7. Open the existing run and click Re-run all jobs to execute with the label set. The run will execute the standard E2E suite after building the required gateway, sandbox, and supervisor images once. The matching required CI gate status on this PR will flip green automatically once the run finishes.

@TaylorMutch TaylorMutch left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One non-blocking telemetry concern: bridge mode changes the reachability of loopback OTLP endpoints. This can be addressed here or in a separate follow-up fix if preferred.

I reviewed the full diff and surrounding code and verified the host-gateway alias mapping in a temporary Docker Desktop bridge container. I did not run the full test suite or validate ECI itself.

TaylorMutch
TaylorMutch previously approved these changes Oct 13, 2026
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
@drew drew changed the title feat(docker): select supervisor networking for ECI workloads feat(docker): support host and bridge supervisor networking Oct 13, 2026
drew added 5 commits October 13, 2026 11:43
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
@drew
drew enabled auto-merge October 16, 2026 01:13
@drew
drew added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit b2069a2 Oct 16, 2026
121 checks passed
@drew
drew deleted the codex/4015-docker-supervisor-networking branch October 16, 2026 01:57
pimlock added a commit that referenced this pull request Oct 16, 2026
Since #4016, host e2e runs omit the Docker driver's grpc_endpoint and let
the driver discover the gateway. The HTTP session hook test required the
key and panicked. Fall back to loopback, which the host-networked
supervisor shares with the gateway, and read the key only from the
Docker driver table.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
opensource-fork-sync Bot pushed a commit to cla7aye15I4nd/opensource-NVIDIA__OpenShell that referenced this pull request Nov 12, 2026
…the middleware (NVIDIA#4359)

* feat(middleware): add HTTP middleware protocol 2

Add EvaluateHttp, a streaming HTTP middleware protocol with BUFFERED and
STREAM body modes, next to the released HTTP protocol 1, which keeps its
engines unchanged and is deprecated for removal in 0.2.0.

- One EvaluateHttp stream per HTTP message and stage, or per uninspectable
  connection. A preflight selects continue, inspect, or reject; late header
  mutations apply before the head commits.
- A service implements one HTTP protocol, chosen per binding with
  http_protocol_version, and a protocol 2 service requires the
  openshell.supervisor-middleware.http-v2 capability. The gateway delivers
  each service's protocol to supervisors.
- Protocol 2 is always fail-closed. The gateway rejects fail_open on
  protocol 2 services and policies whose overlapping selectors mix
  protocols; a mixed chain fails closed at runtime.
- Protocol 2 middleware decides about uninspectable traffic (tls: skip,
  h2c, unsupported tunnels, raw TCP, SQL passthrough) at a preflight, and is
  exempt from the static tls: skip conflict rule.
- Request bodies stream to the upstream unless a later step needs the whole
  body; responses stream with chunked framing or buffer before commit.
- Migrate the content guard example to protocol 2, add a Docker e2e suite,
  and document the protocol.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): select HTTP protocol 2 by binding operation

Replace the http-v2 capability and per-binding protocol fields with new
binding operations, and stop delivering the protocol to supervisors.

- HTTP_REQUEST_V2 and HTTP_RESPONSE_V2 select HTTP protocol 2. Released
  gateways and supervisors already reject unknown operation/phase pairs at
  Describe, so the capability, extension_metadata_with_requirements, and
  MiddlewareBinding.http_protocol_version are removed.
- Drop supported_http_body_modes. The preflight offers every mode the
  message is eligible for, and the stage picks one or continues or
  rejects. A protocol 2 binding with a zero payload limit is offered no
  body modes.
- Move the tls: skip conflict rule out of the shared policy validator into
  the gateway's safety validation, which knows each service's protocol.
  This removes the delivered SupervisorMiddlewareService protocol field,
  the process-wide protocol 2 set in openshell-policy, and the supervisor's
  undescribed-service tracking. An undescribed entry follows on_error,
  which the gateway requires to be fail_closed for protocol 2 services.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): split EvaluateHttp into request and response RPCs

Replace EvaluateHttp with EvaluateHttpRequestV2 and EvaluateHttpResponseV2,
one RPC per binding operation, as for HTTP protocol 1 and WebSocket. Both
keep the shared HttpEvent and HttpResult messages, so the stage pipeline is
unchanged; OpenShell picks the RPC from the preflight subject.

Traffic OpenShell cannot inspect stays on the request RPC. Only services
that bind HTTP_REQUEST_V2 decide about it, so only they are exempt from the
gateway's tls: skip conflict rule; a response-only protocol 2 service keeps
the rule and its fail-closed entry denies such traffic at runtime.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): call the new contract v2 HTTP hooks

Rename "HTTP protocol 1/2" to v1 and v2 HTTP hooks across code, proto
comments, docs, skills, and the example. The docs page moves to
extensibility/supervisor-middleware/http-hooks-v2, the protocol2 modules
become http_v2, HttpProtocol becomes HttpHookVersion, and the mixed-chain
reason becomes middleware_hook_versions_mixed.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): select HTTP session hooks by required capability

Rename the streaming HTTP hook RPCs to EvaluateHttpRequestSession and
EvaluateHttpResponseSession, and drop the unreleased HTTP_REQUEST_V2 and
HTTP_RESPONSE_V2 operations. A service keeps its HTTP_REQUEST and
HTTP_RESPONSE bindings and selects HTTP session hooks for all of them by
listing openshell.supervisor-middleware.http-session in its extension
required_capabilities. Released gateways and supervisors already reject
unmet required capabilities at Describe, so they never call such a
service through the v1 RPCs. Registration rejects a manifest that
supports the capability without requiring it.

Mark the v1 HTTP hook RPCs, the HttpResponsePreReturn service, and the
types only they use deprecated in the protobuf contract. Rename the
docs page, module, and e2e suite to HTTP session hooks, and update RFC
0009 so capability-gated contracts and removing deprecated elements in a
minor release match the release stability policy.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): begin body stages on every preflight mutation

A body stage captured the head right after its own preflight, so its
begin event lacked the preflight mutations of later stages even though
the delivered head carried them. Keep the final preflight head on the
pipeline and begin every stage on it plus earlier stages' late
mutations, as the HttpBegin contract states.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): check the policy generation before every response write

A STREAM response stage can keep emitting output after it consumed the
upstream body, and the session response writer checked the policy
generation only when it committed the head. Output and the terminating
chunk then reached the client under revoked authorization, before the
final check. Check the generation before every write instead, and treat
the response as committed only once a byte was written.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): complete session hook messages only after every stage

With a declared output length, a STREAM stage could emit every byte and
then reject at input_end. The relay had already written a complete
Content-Length message, so the sandbox saw a successful response, and
on uploads the upstream received a complete request it could act on or
answer.

Hold back what would complete the message until every stage returns its
terminal result: the final byte of a declared length, or the whole head
of a declared empty body. The chunked terminator already waited. An
empty response body that is rejected still gets the canonical 403.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): split expanded content guard output within the chunk limit

Redacting a term shorter than the marker makes STREAM output longer than
its input, so one output chunk could exceed the max_chunk_bytes the
preflight offered and fail the message closed. Split every output chunk
within that limit.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): queue HTTP session hooks and explain failures to the sandbox

Address load-test findings on HTTP session hooks:

- Give HTTP session hooks their own admission budgets: 64 slots for
  preflight and BUFFERED bodies, and 256 for STREAM bodies so long-lived
  server-sent events cannot starve short messages. A message waits for a
  slot within the chain deadline instead of failing at once, releases it
  when no stage inspects the body, and frees its BUFFERED slot when the
  last BUFFERED stage ends. Uninspectable decisions use the same budget.
- After an early middleware response (preflight rejection or failure,
  503, mid-stream failure, or 408), shut down writing and discard what
  the sandbox still sends, within time and byte bounds, so the sandbox
  reads the response instead of a connection reset.
- Fill originating_process (binary and ancestors) in request and
  response preflights, as the uninspectable preflight already did.
- Return a platform-owned failure_code from a closed set, and the failed
  stage's policy entry name, in 403, 502, and 503 failure bodies.
- Document raising gRPC message limits for 4 MiB BUFFERED bodies, and
  raise them in the content guard example.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): give each HTTP session hook RPC its own messages

Address the proto review of the HTTP session hooks contract:

- Split the shared HttpEvent/HttpResult into per-RPC messages
  (HttpRequestSessionEvent/Result and HttpResponseSessionEvent/Result)
  with direction-specific preflights, so a request stream cannot carry a
  response subject and the reverse. Body events and results stay shared.
- Add phase to both preflights, as the WebSocket preflight has.
- Add an explicit cannot_inspect result, valid wherever reject is, and
  drop the FAILED_PRECONDITION convention; every gRPC status now maps to
  middleware_unavailable.
- Offer no body mode for a request whose Content-Encoding is not
  identity (ENCODED), as for responses: middleware would only see the
  encoded bytes.
- Send declared_body_bytes, and apply OVER_LIMIT, only to stages before
  the first one that inspects the body, which may replace it.
- Remove the queue limits from HttpBodyLimits; they had no defined
  meaning for a service.
- Clarify comments: BUFFERED fails over its limit instead of falling
  back, finish requires output_start, and unavailable_body_modes is empty
  for a zero payload limit.
- Document that policies stored before a service changed hook version
  must be re-applied.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): include the originating process in response preflights

The main L7 response path built its middleware request context by hand
with originating_process set to None, so HTTP session hook response
preflights lacked the process that request preflights already carry.
Build it with middleware_request_context, as the other paths do.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): retain response delivery blocking and consolidate docs

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): release the preflight slot while waiting for a stream slot

A message whose stages selected STREAM kept its preflight slot while it
waited, up to the chain deadline, for one of the stream slots. With every
stream slot taken by open server-sent-event responses, waiting messages
could hold all preflight slots and starve BUFFERED messages and
uninspectable-connection decisions.

Take a free stream slot immediately when one is available. Otherwise
release the preflight slot before waiting, and take one again afterwards
only if a BUFFERED stage runs. No message holds a preflight slot while it
waits for a stream slot, so the two waits cannot deadlock.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): protect request Content-Encoding from session hook stages

Response preflight already treats Content-Encoding as read-only. On
requests a stage could still write or remove it. That was not a bypass,
because body-mode offers come from the original head, but a stage could
label a body it replaced as encoded, or drop the label from an encoded
body.

Session hook request heads now protect Content-Encoding. Legacy request
hooks keep their released rules.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(middleware): record cannot_inspect diagnostics

HttpCannotInspect was empty, so a stage could not attach findings or a
reason code for operators the way HttpReject can. Add diagnostics to it.
OpenShell validates and records them like a rejection's, and the sandbox
still sees only failure_code middleware_cannot_inspect.

Also update the HttpPreflightResult comment for the per-RPC split:
requests deny with reject, responses with block_delivery, and either
can return cannot_inspect.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): cover cannot_inspect for uninspectable traffic and gateway restarts

Uninspectable preflights can also return cannot_inspect, which refuses
the connection as a failure.

The gateway reads each service's Describe manifest only at startup, so
re-applying policies after a service changes hook version validated them
against the stale manifest. Tell operators to restart the gateway first.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(middleware): let session hook stages own integrity headers

Response session hooks stripped ETag, Content-MD5, Digest,
Content-Digest, Repr-Digest, Signature, and Signature-Input whenever a
stage replaced or streamed the body, and stages could only remove them.
A STREAM stage that passed the body through unchanged still lost the
upstream's validators, and no stage could supply correct new values.
Requests never stripped them.

Session hooks now leave these headers alone in both directions, as
Envoy's external processor does. A stage that changes the body removes
them or writes new values in buffered_result or output_start; a STREAM
stage can read all input before output_start to compute one. Legacy
response hooks keep their released stripping and remove-only rules.

The content guard example removes them when it replaces a BUFFERED
body, and at output_start when it streams in redact mode.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): list cannot_inspect where proto comments name stage choices

The UninspectableTraffic comment and both preflights'
permitted_body_modes comments still said a stage that finds no suitable
mode continues or denies. It can also return cannot_inspect.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): recreate sandboxes after a service changes hook version

Supervisors also cache each service's Describe manifest and rebuild
their registry only when the set of service registrations changes.
Restarting the gateway and re-applying policies leaves running
sandboxes on the old manifest.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* test(e2e): find the middleware host when Docker discovers the gateway

Since NVIDIA#4016, host e2e runs omit the Docker driver's grpc_endpoint and let
the driver discover the gateway. The HTTP session hook test required the
key and panicked. Fall back to loopback, which the host-networked
supervisor shares with the gateway, and read the key only from the
Docker driver table.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): deny uninspectable messages with reject

The cannot_inspect result replaced a FAILED_PRECONDITION status
convention after review, which asked only for an explicit, typed signal
whose effect is the reason. A separate result went further: it added a
third terminal result to every phase and a failure response (502 on
responses, platform-owned failure_code) for what is a decision the
stage makes.

Remove HttpCannotInspect and the cannot_inspect result field from both
session result messages. They are unreleased, so they are deleted, not
reserved. A stage that cannot inspect a message rejects it, or blocks
delivery, with a reason_code that says so. The content guard example
uses reason code cannot_inspect. The platform failure_code
middleware_cannot_inspect remains for messages OpenShell cannot show to
a stage.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): split HTTP session RPCs into dedicated service

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): decide about uninspectable traffic in a dedicated hook

Uninspectable connections (tls: skip, h2c, unsupported tunnels, raw TCP,
SQL passthrough) are not HTTP, so they no longer use an HTTP session
preflight. A service binds UNINSPECTABLE_NETWORK/PRE_FORWARD and serves
the unary UninspectableNetworkMiddleware.Evaluate on the same endpoint.
The hook carries only connection metadata, can be a service's only
binding, and always fails closed.

Entries without the binding keep their on_error behavior: fail_closed
denies, and fail_open relays with a bypass finding. That fail_open
exception is deprecated for removal in 0.2.0. The gateway tls: skip
conflict rule now exempts middleware that binds the new hook.

The tls: skip decision runs before 200 Connection Established, so a
denial is a plain 403.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): align HTTP denial naming

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): defer inspection decisions until stage input is ready

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): document the UTF-8 rule for HttpHeader values

The L7 parser rejects request heads that are not valid UTF-8 before any
middleware runs. A response head that is not valid UTF-8 fails an HTTP
session hook chain closed with middleware_cannot_inspect, while v1
response hooks follow on_error. Session hooks also fail a message with
trailers that are not valid UTF-8. State this on HttpHeader so service
authors know the string type matches what OpenShell forwards.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): tidy the unreleased HTTP session hook contract

HttpRequestSessionPreflight skipped field 2 after the uninspectable subject
moved to its own hook. These fields were never released, so renumber them to
match HttpResponseSessionPreflight instead of leaving a gap.

Rename UninspectableTrafficReason to UninspectableNetworkReason to match
UninspectableNetworkMiddleware and the other new UninspectableNetwork names.

Document behavior the contract left implicit: a trailer write cannot add a
name, Content-Length framing drops trailers, and Process.pid is 0 when
unknown.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): report the withheld STREAM output cap per route

Routes that hold request output until the chain finishes capped it at 4 MiB,
while the contract said STREAM has no total size limit. A pass-through
STREAM stage on a SigV4 route therefore failed requests between 4 and
10 MiB that the route accepts without middleware, and the stage had no way
to know.

Withhold output up to the route's own buffering limit: 10 MiB for SigV4
body signing, 256 KiB for body credential rewriting, and 4 MiB otherwise.
Request preflights report that limit to STREAM stages as the new
HttpBodyLimits.max_output_body_bytes, so a stage can decide at preflight.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): attach trailer mutation comments to their fields

The trailer mutation rules added in 9f7a04a landed on HttpInputChunk.data
and inside the HttpDenial comment. Move them onto
HttpBufferedResult.trailer_mutations and HttpFinish.trailer_mutations, and
restore the HttpDenial comment.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): ask for identity-coded responses when a session stage can inspect bodies

HTTP session hooks offer no body mode for a response with a content coding
other than identity. The sandbox controls Accept-Encoding, and common clients
(requests, httpx, fetch) send gzip by default, so most compressed responses
reached response stages uninspectable: a guard either blocked them with
cannot_inspect or let them through unread.

When the response chain has an HTTP session hook stage that may select a body
mode, the relay now replaces every Accept-Encoding field with identity. It
does so as the last header step on both the buffered and the streamed request
path, after request-stage mutations and before credential injection, so a
request stage cannot undo it. Request middleware still sees the sandbox's
value. v1 hooks and bindings with max_payload_bytes 0 are unchanged. An
encoded response now means the upstream ignored identity.

The e2e upstream gains a /compressible route that gzips when asked, and the
workload asks for gzip as common clients do. Without the change the guard
answers 403 cannot_inspect; with it, the response is redacted.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(middleware): show response stages the forwarded request headers

A response stage that needs the request's headers, such as a guard that
judges a response against what was asked, had to keep per-request state keyed
by request_id and route both streams to the same replica.
HttpResponsePreflightHead.request_headers now carries the request headers as
the request middleware chain forwarded them: after every request stage's
mutations, including a streamed body's late mutations, and before OpenShell
injects credentials or replaces Accept-Encoding. They have the omissions and
limits of the request preflight's headers, and a value that contains a
credential placeholder reads [REDACTED], as the response target's query
already does.

The relay hands response middleware a request that may already carry token
grants, under header names the request-header sanitizer does not know, so the
head is recorded before injection: by HttpMiddlewareExchange when request
middleware allows the request, and before prepare_inspected_request on the
inspected relay path. A live body applies its late mutations to that record
when its head commits, before the upstream can answer. A response chain with
no recorded head fails closed with middleware_cannot_inspect, as do request
headers over the head limits.

The response preflight's context comment now says request_id matches the
request's preflights.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): hide credential placeholder headers from session hooks

HTTP session hook heads already omit Authorization, Cookie, and other
credential headers, but a credential placeholder in a custom header, such as
X-Api-Key: openshell:resolve:env:API_KEY, reached request stages as-is and
response stages as [REDACTED]. Whether a stage saw a placeholder depended on
which header carried it.

A request header whose value carries a credential placeholder, including one
inside a Basic credential, is now a credential header for HTTP session hooks.
The request preflight, begin, and the response preflight's request_headers
omit every value of that name, and the pipeline rejects a stage mutation that
writes or removes it with header_mutation_protected_header, so a stage cannot
replace or drop a credential reference it cannot see. The [REDACTED] sentinel
is gone. Like Envoy's ext_proc forwarding rules, OpenShell drops headers rather
than masking their values; a typed list of withheld names can be added later if
a stage needs to know they were present.

v1 HTTP hooks keep their released behavior and still receive these headers.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): remove the uninspectable network hook

The hook let middleware allow connections OpenShell cannot inspect
(tls: skip, h2c, unsupported tunnels, raw TCP, SQL passthrough) by
metadata alone. No published middleware uses it, and the mixed-port
cases it served are better handled by selector changes, so remove it
before it ships.

Uninspectable traffic goes back to the chain-level on_error rule: a
fail_closed entry denies the connection, and an all-fail_open chain
relays it with a bypass finding. That fail_open exception is no longer
deprecated. The tls: skip overlap rule returns to shared policy
validation, so supervisors enforce it again alongside the gateway. The
tls: skip gate still runs before 200 Connection Established, so a
denial is a plain 403.

The proto service, operation, phase, and enums are deleted rather than
reserved because they were never released.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(policy): reject ports in middleware selector patterns

Middleware selectors match hostnames on every port, but a pattern such
as api.example.com:443 parsed as a hostname that never matches, so the
entry was silently never selected. Reject ':' in include and exclude
patterns at gateway and supervisor validation instead. IPv6 literals
are still accepted.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(policy): keep IPv6 globs in middleware selector patterns

Rejecting every ':' outside a complete IPv6 literal also rejected IPv6
globs such as 2001:4860:*, which selectors have matched since they
shipped and network endpoints still accept. Treat a pattern containing
':' as IPv6 text instead, and reject only the shapes of a host:port
authority: bracketed literals, characters IPv6 text cannot contain, dots
before the last ':', a single ':' followed by digits, and invalid
literals without wildcards.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(middleware): send the originating process to every middleware hook

v1 HTTP request hooks and WebSocket preflights now receive
RequestContext.originating_process like v1 response hooks and HTTP
session hooks, so every hook sees the same caller. A deep ancestor chain
is trimmed, farthest ancestor first, to keep the context within its
4 KiB limit instead of failing the evaluation.

Closes NVIDIA#4269

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(policy): accept only complete IPv6 addresses in middleware selectors

IPv6 globs such as cafe:* cannot be told apart from a mistyped host:port,
so the heuristic that accepted them let some port patterns through as
IPv6 text that never matches. Middleware selectors now accept ':' only in
a complete IPv6 address. Docs add the upgrade note for policies with a
port or an IPv6 wildcard.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): answer a JSON-RPC or GraphQL request timeout with 408

These paths read the whole body before middleware runs, so a request
timeout should not occur. If it does, send the same 408 request_timeout
as the REST path instead of failing the relay with a generic error.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): map session hook failures to the sandbox failure_code

List request_timeout among the sandbox error codes, and name both the
OCSF reason and the failure_code a sandbox sees in the troubleshooting
skill.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): rename HTTP body modes to WHOLE_BODY and DUPLEX

BUFFERED becomes WHOLE_BODY and STREAM becomes DUPLEX, before the HTTP
session hooks ship. WHOLE_BODY matches the v1 WHOLE_BODY_BYTES name, and
DUPLEX leaves room for a later PER_CHUNK mode with one result per input
chunk, like Envoy ext_proc STREAMED, without two modes both called
streaming.

Renames the proto enum values, HttpWholeBodyMode, HttpDuplexMode,
HttpWholeBody, HttpWholeBodyResult, the whole_body and
whole_body_result fields, and max_whole_body_bytes, along with the Rust
names, the content-guard body_mode values, tests, and docs.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): rename http-session capability to http-hooks

The capability that selects the streaming HTTP RPCs is now
openshell.supervisor-middleware.http-hooks. "Session" read as a
cookie-style HTTP session and clashed with the WebSocket session RPC.

Docs, comments, and errors call the new contract "HTTP hooks" and the
deprecated contract "legacy HTTP hooks" instead of "v1". HttpHookVersion
and ChainHttpHookVersion rename V1 to Legacy, with the audit label
legacy_http_hook; the new contract's label is http_hook.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): rename HttpSessionMiddleware to HttpHooks

Finish the http-hooks rename so no HTTP hook name still says "session":

- Service HttpSessionMiddleware is now HttpHooks; Http{Request,Response}
  Session{Event,Result,Preflight} messages are now Http*Hook*.
- http_session modules, the e2e test, and the mise task and CI suite are
  now http_hooks / middleware-http-hooks.
- HttpHookVersion and ChainHttpHookVersion rename Session to Current;
  HeaderAuthority renames Session{Request,Response} to Hook*.
- HttpRequestSession and HttpResponsePipelineSession are now
  HttpRequestPipeline and HttpResponsePipeline, carried in a `pipeline`
  field; MAX_CONCURRENT_HTTP_MIDDLEWARE_SESSIONS is now ..._MESSAGES.

Shared stream names (MiddlewareSessionEnd, session_capacity_exhausted)
and WebSocket session names are unchanged.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): name HTTP hook budgets after their slots

"Messages" and "streams" read as gRPC messages and streams. The two
budgets are buffering slots (preflight and WHOLE_BODY) and streaming
slots (DUPLEX or deferred stages), as the docs already call them:

- MAX_CONCURRENT_HTTP_MIDDLEWARE_MESSAGES -> MAX_HTTP_HOOK_BUFFERING_SLOTS
- MAX_CONCURRENT_HTTP_MIDDLEWARE_STREAMS -> MAX_HTTP_HOOK_STREAMING_SLOTS
- http_{hooks,stream}_admission -> http_{buffering,streaming}_admission,
  with matching reserve helpers and debug fields.

The streaming budget comment now says deferred stages also take a slot,
matching may_stream(). The docs limits row counts HTTP requests and
responses instead of messages.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(middleware): align HTTP hooks contract wording with review feedback

State how zero-limit HTTP hooks bindings interact with the operator payload limit, align the HTTP/1.0 DUPLEX rule with the proto, document the 403 for a DUPLEX request denied after upstream output starts, and make failure_code wording consistent across the docs.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* test(middleware): cover zero-limit HTTP hooks bindings and late DUPLEX request denial

Pin that a zero-limit HTTP hooks binding is excluded from the operator payload limit comparison, and that a DUPLEX request denied after its output started upstream still gets the canonical 403 and an unterminated upstream body.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): give HTTP hook RPCs package-unique names

Rename HttpHooks.EvaluateRequest and EvaluateResponse to
EvaluateHttpRequestHook and EvaluateHttpResponseHook, and document in
supervisor_middleware.proto that RPC names must be unique across the
package's services, with the naming pattern for future hook services.
Python and Go servers name handler methods after the RPC alone, so one
object implementing several services needs distinct names.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): mark HTTP hooks response denials in OCSF findings

Set ActionId::Denied on the HTTP hooks http_response_blocked finding and
on the after-commit delivery failure when a stage stopped delivery, so
both hook versions emit the same OCSF for the same decision. Assert that
a DUPLEX request denied after upstream start leaves the chunked upstream
body unterminated.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

---------

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants