Skip to content

feat(supervisor): pass caller identity to middleware - #5134

Open
vineethsai7 wants to merge 2 commits into
NVIDIA:mainfrom
vineethsai7:feat/4269-v1
Open

vineethsai7 wants to merge 2 commits into
NVIDIA:mainfrom
vineethsai7:feat/4269-v1

Conversation

@vineethsai7

@vineethsai7 vineethsai7 commented Nov 2, 2026 •

Copy link
Copy Markdown

Summary

Populate the existing RequestContext.originating_process field from the caller identity resolved when the supervisor admits an egress connection. Middleware attached to a credential-bound endpoint can now decide on request content and caller before OpenShell injects provider credentials.

#5135 builds on this PR and adds upstream socket audit correlation.

This is a draft design proposal. Issue #4269 is still state:triage-needed; maintainer feedback on the identity and truncation semantics is welcome.

Related Issue

Closes #4269.

Design

The L7 relay already carries the binary path and nearest-first ancestor paths used by network policy. This change also retains the resolved PID and passes that connection identity through the existing middleware request context for HTTP requests, HTTP response preflight, and WebSocket preflight. All requests on a persistent connection use the identity captured at admission. No protobuf field or configuration key is added.

The encoded request context remains limited to 4 KiB. OpenShell preserves the leaf binary and as many nearest ancestors as fit, dropping distant ancestors first. If the leaf binary itself cannot fit, originating_process is absent. A missing identity also leaves the field absent; a backend-supplied identity without a PID uses pid: 0. When a socket has multiple owners with the same policy identity, the resolver reports pid: 0 because it cannot identify the sender; it denies owners with different identities before middleware runs.

This uses the supervisor middleware hook because it is already on the HTTP data path before credential injection and already has the optional originating_process contract. Binary-scoped network policy can gate destinations and methods but cannot decide on request content. Gateway interceptors run on control-plane RPCs, while an upstream proxy sees the request after credential injection and lacks the sandbox caller identity.

Changes

  • Carry the admitted PID into the shared L7 evaluation context and populate process identity for the current HTTP request, HTTP response preflight, and WebSocket preflight hooks.
  • Bound the encoded process context by keeping a prefix of the ancestor chain in policy order.
  • Document field presence, absence, PID semantics, and ancestor truncation in Supported Operations and RFC 0009.
  • Add tests for the production decision-to-L7 handoff, each hook path, and a deep ancestor chain.

An existing registered middleware can use a policy attachment such as:

network_middlewares:
  model-guard:
    middleware: model-guard
    on_error: fail_closed
    endpoints:
      include: ["aiplatform.googleapis.com"]

Apply a reviewed complete policy with openshell policy set my-sandbox --policy policy.yaml --wait. For a curl process started by a coding agent, the service receives a context with this shape:

{
  "originating_process": {
    "binary": "/usr/bin/curl",
    "pid": 42,
    "ancestors": ["/usr/bin/claude"]
  }
}

The same identity reaches the response and WebSocket preflight for that connection. This is an additive value in an existing optional field, so the wire contract and existing policy files remain valid. The field is absent in endpoint-only proxy mode or when the supervisor cannot attribute a process. Existing middleware that assumed the field was always absent should handle the populated value.

Release Notes

Supervisor middleware can use the admitted process binary and ancestry when evaluating HTTP requests before credentials are injected, HTTP responses, and WebSocket preflight.

Testing

  • cargo fmt --check -p openshell-supervisor-middleware -p openshell-supervisor-network through dcb.
  • cargo clippy --locked -p openshell-supervisor-middleware -p openshell-supervisor-network --all-targets -- -D warnings through dcb.
  • cargo test --locked -p openshell-supervisor-middleware -p openshell-supervisor-network through dcb: 129 middleware unit tests, 1,584 network unit tests, and local integration tests passed. LocalStack tests remain ignored by their own fixture requirement.
  • uv run tasks/scripts/check_docs_nav.py, fern-api@5.112.0 check, and markdownlint-cli2@0.22.0 through dcb: zero errors. Fern reported three warnings outside the changed page.
  • Unit tests added and updated for caller propagation and the context limit.
  • Driver E2E tests were not run; no test host was authorized for this work.

Follow-ups

  • The community request to record which binary or ancestor satisfied the network policy requires the policy result to expose match provenance, then an additive audit and middleware contract. This patch reports the resolved caller and full available ancestry but does not infer the matching rule.
  • An executable SHA-256 field would require an additive protocol field and retaining the verified digest in the egress decision.
  • Coordinate this context plumbing with the proposed HTTP session hooks in feat(middleware): introduce improved HTTP request/response hooks for the middleware #4359 if that PR lands first.

Checklist

  • Follows Conventional Commits.
  • Commits are signed off (DCO).
  • Architecture and published operation docs updated.

Review follow-up

  • P2, shared-socket PID attribution — fixed in e1a3fc756. The resolver now leaves the PID unavailable when multiple processes with the same policy identity hold the socket. Middleware receives pid: 0.
  • P2, lossy executable paths — fixed in e1a3fc756. Middleware identity now uses exact UTF-8 paths. The process field is absent when a leaf or ancestor path cannot be represented exactly.
  • P3, contributor acceptance gate — not applicable to this code follow-up. AGENTS.md:184 requires an accepted issue for an author without upstream write access. GitHub reports no upstream push access for vineethsai7, while issue feat(supervisor): populate RequestContext.originating_process for supervisor middleware #4269 remains state:triage-needed. A maintainer must address the issue state or access; this task prohibits issue and PR edits.

The GitHub review APIs had no actionable reviewer comments. The only conversation comments were the automated workflow vetting notice and the DCO acknowledgement. Formatting, Clippy with -D warnings, full crate tests (129 middleware and 1,584 network unit tests plus local integration tests), docs navigation, Fern, and Markdown lint passed through dcb; the final test run passed after one transient ETXTBSY failure in an existing hot-swap fixture on an intermediate run. No test host was used.

Manual verification

PR: NVIDIA/OpenShell #5134 (feat/4269-v1), head e1a3fc7561ce27c55dcf6ba3b839f5db081a1bc3.
Before: upstream main be5e49499 (openshell 0.1.7-dev.40+gbe5e49499). After: the PR head.

Environment

  • Host: RHEL 9.8 x86_64 (EC2), Docker Engine 29.8.1 (root daemon), one disposable local account, private HOME, plaintext local-development gateway on a non-default loopback port. The host's own OpenShell service was not used.
  • Binaries built on an Ubuntu 24.04 box (cargo build --release); supervisor and sandbox runtime packaged as local images and set as supervisor_image / sandbox_runtime_image. Gateway and CLI ran on RHEL 9 through an Ubuntu 24.04 glibc loader.
  • Middleware: the repository's examples/supervisor-middleware-content-guard service, built from the PR tree with one extra line (eprintln!("MW-REQ-CONTEXT {:?}", request.context) at the top of evaluate_http_request) so each request's RequestContext is printed. Registered in the gateway TOML as [[openshell.supervisor.middleware]] name = "content-guard-example", grpc_endpoint = "http://127.0.0.1:28110", allow_insecure_transport = true. The same service process served both the main and the PR gateway.
  • Sandbox image: ubuntu:24.04 plus curl. Upstream: a small Python HTTP server on the host (port 28120) reached as host.openshell.internal.

Steps

# policy = the example's policy.yaml with the upstream port changed: network_middlewares.prototype-content-guard
#   (middleware: content-guard-example, include: host.openshell.internal) and a rest endpoint
#   host.openshell.internal:28120 allowing GET /clean and /sensitive for /usr/bin/curl
openshell -g <gw> sandbox create --name mw1 --policy policy.yaml --no-tty -- \
  bash -c 'curl -s http://host.openshell.internal:28120/clean; true'
# the middleware service log shows the RequestContext of each request

Before (main supervisor)

MW-REQ-CONTEXT Some(RequestContext { request_id: "6fff6286-...", sandbox_id: "4b803616-...",
  originating_process: None, sandbox: "mwb1", workspace: "default" })

The middleware sees no caller: originating_process is absent.

After (PR supervisor)

Three sandboxes on the PR gateway, each running curl through different shells (requests allowed by policy, response ok, HTTP 200):

mw1 (bash -c 'curl ...; curl ...'):
  originating_process: Some(Process { binary: "/usr/bin/curl", pid: 0, ancestors: ["/usr/bin/bash"] })
  originating_process: Some(Process { binary: "/usr/bin/curl", pid: 0, ancestors: [] })        <- second curl was the shell's last command (exec'd), so bash is gone
mw2 (curl --proxytunnel, then plain curl, both under bash):
  originating_process: Some(Process { binary: "/usr/bin/curl", pid: 0, ancestors: ["/usr/bin/bash"] })   x2
mw3 (bash -c 'dash -c "curl ..."'):
  originating_process: Some(Process { binary: "/usr/bin/curl", pid: 0, ancestors: ["/usr/bin/dash", "/usr/bin/bash"] })

The caller binary and its ancestry (nearest parent first) reach the middleware before credential injection, for both the forwarded-HTTP and the CONNECT-tunnel request styles. Each request on the connection carries the identity captured at admission.

Observation (not a failure of the documented contract)

pid was 0 on every request in this Docker deployment, although each connection was held by a single curl process. The PR documents pid: 0 when the owner is ambiguous (several processes holding the socket), so this is within the documented contract, but the PID was never populated in a real run; PID attribution is covered only by the unit tests (which set binary_pid = Some(42)). The main supervisor's own network events show pid 0 for the same traffic in this deployment, so this is not introduced by the PR. I did not find out why the PID is not resolved here.

Result

PASS for the stated purpose: originating_process with the caller binary and ancestor paths is populated in a real middleware request on a real sandbox, and absent on main. The PID part was not demonstrated (pid: 0 in all runs).

Signed-off-by: Vineeth Sai Narajala <vnarajal@cisco.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@vineethsai7

Copy link
Copy Markdown
Author

I have read the DCO document and I hereby sign the DCO.

Signed-off-by: Vineeth Sai Narajala <vnarajal@cisco.com>
@vineethsai7
vineethsai7 marked this pull request as ready for review November 3, 2026 04:23
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T04:27:34.784865Z e1a3fc7 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(supervisor): populate RequestContext.originating_process for supervisor middleware

1 participant