Skip to content

feat(supervisor): correlate relayed TCP events with upstream sockets - #5135

Open
vineethsai7 wants to merge 6 commits into
NVIDIA:mainfrom
vineethsai7:feat/4271-v1
Open

vineethsai7 wants to merge 6 commits into
NVIDIA:mainfrom
vineethsai7:feat/4271-v1

Conversation

@vineethsai7

@vineethsai7 vineethsai7 commented Nov 2, 2026 •

Copy link
Copy Markdown

Depends on #5134. The first 2 commits are from #5134; review only e1a3fc756..903a54eda.

Summary

Record the immediate upstream TCP socket tuple and the authorized executable digest in allowed supervisor NetworkActivity events. A directly connected receiver can match its accepted socket to the event and read the sandbox process identity. This is the audit correlation slice of the broader caller identity request.

Related Issue

Part of #4271. The issue is still state:triage-needed; design feedback is welcome.

Changes

  • Capture the upstream socket's local and peer addresses after a successful dial for CONNECT and transparent TCP relays.
  • Add unmapped.upstream_socket_local and unmapped.upstream_socket_peer to their allowed OCSF events. Carry the executable SHA-256 used for authorization into unmapped.binary_sha256 when identity evidence is available. Transparent TCP events now include the resolved ancestor executable paths in actor.process.
  • Document the event fields and their correlation limits.

Design

The supervisor already has the trusted caller identity and emits an allowed connection event. This change attaches the connected socket tuple at that point, using the existing OCSF unmapped extension path. It does not add a policy field or change the relay. The receiver's accepted socket sees the supervisor's upstream local address as its peer when the direct path has no address translation.

An opt-in PROXY protocol v2 header would deliver identity before workload bytes and support an access decision at the receiver, but it requires a new endpoint contract and receiver support. Supervisor middleware cannot inspect tls: skip TCP, gateway interceptors do not see workload connections, and provider credentials identify a sandbox rather than a process. The header belongs in a follow-up design review.

Example

Existing policy and JSON export settings suffice for this slice:

network_policies:
  host_proxy:
    endpoints:
      - host: host.openshell.internal
        port: 3129
        protocol: tcp
        tls: skip
    binaries:
      - path: "/**"
openshell settings set my-sandbox --key ocsf_json_enabled --value true

Relevant fields from an allowed event:

{
  "class_uid": 4001,
  "container": {"uid": "sandbox-id"},
  "actor": {"process": {"name": "/usr/bin/curl", "pid": 57,
    "parent_process": {"name": "/usr/bin/sh", "pid": 0}}},
  "unmapped": {
    "upstream_socket_local": "127.0.0.1:41001",
    "upstream_socket_peer": "127.0.0.1:3129",
    "binary_sha256": "<SHA-256 hex>"
  }
}

For a direct connection without address translation, compare the receiver's peer address to upstream_socket_local and its local address to upstream_socket_peer.

Compatibility

The workload byte stream, network policy, and CLI are unchanged. The JSON fields are additive; binary_sha256 is omitted when identity evidence is unavailable. If the local socket address lookup fails, transparent TCP events retain their existing upstream_socket_peer field but omit upstream_socket_local; a peer lookup failure omits both. A configured corporate upstream proxy becomes the immediate peer in this tuple. Address translation may change the tuple before the final receiver sees it. OCSF delivery is best-effort, and a backend that supplies no PID still records actor.process.pid as 0. The new tuple, digest, and ancestor values are absent from the default shorthand log and require the opt-in JSON export. This slice supports attribution where the tuple is observable; it does not provide synchronous per-process enforcement at the receiver.

Release Notes

Allowed CONNECT and transparent TCP JSON events include the upstream socket tuple for direct receiver correlation and the authorized executable digest when available.

Testing

  • cargo fmt --check -p openshell-ocsf -p openshell-supervisor-middleware -p openshell-supervisor-network through dcb.
  • cargo clippy --locked -p openshell-ocsf -p openshell-supervisor-middleware -p openshell-supervisor-network --all-targets -- -D warnings through dcb.
  • cargo test --locked -p openshell-ocsf -p openshell-supervisor-middleware -p openshell-supervisor-network through dcb: 176 OCSF, 129 middleware, and 1,588 network unit tests plus local integration tests passed; fixture-dependent tests remain ignored.
  • uv run tasks/scripts/check_docs_nav.py through dcb.
  • fern-api@5.112.0 check (0 errors; 3 warnings outside the changed page)
  • markdownlint-cli2@0.22.0 on the changed README and MDX page (0 errors)
  • Unit tests added for concurrent receiver socket correlation, the preserved payload, digest presence and absence, transparent TCP event fields, partial lookup compatibility, and default shorthand privacy.
  • E2E tests added/updated (not applicable to this additive JSON event change; no test host used)

Follow-ups

  • Specify and implement an opt-in per-endpoint PROXY protocol v2 identity header, including sandbox ID, executable digest, ancestor chain, and PID/start time only where the backend can provide them. This must precede workload bytes and fail closed if the header cannot be written.
  • Carry a shared connection ID in the header and OCSF event if receivers need correlation across address translation or port reuse. Validate the final design on Docker, Podman, Kubernetes, and MicroVM drivers with an authorized test environment.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (crate README and published OCSF documentation)

Review follow-up

Local review: REVIEW-5135.md. GitHub had no inline review comments or submitted reviews at the time of this follow-up.

  • P2-1 — fixed in c4aa964f940e: Preserve each resolved ancestor path as one OCSF process entry, including paths containing ->.
  • P2-2 — fixed in c4aa964f940e: Keep authorized relays running when an upstream socket address lookup fails; omit the incomplete audit tuple.
  • P2-3 — fixed in 903a54eda0d1: Preserve the preexisting transparent TCP upstream_socket_peer field when only the new local address lookup fails. A regression failed before the fix and passed after it.
  • P3-1 — fixed in c4aa964f940e: Use the production socket-inspection helper in the receiver-correlation test.
  • P3-2 — fixed in c4aa964f940e: Explain IPv6 tuple syntax and IPv4-mapped IPv6 comparison.
  • P3-3 — fixed in c4aa964f940e: Explain that an upstream proxy bypass records the direct receiver as the peer.

The default shorthand log omits the new socket tuple, executable digest, and ancestor chain; the opt-in JSON export includes them. A regression checks that projection.

The runner-vetting and DCO issue comments raised no code finding. Issue #4271 remains state:triage-needed, and the PR body says Part of #4271; the repository's issue-acceptance and closing-reference requirements remain for a maintainer to resolve. The issue and PR were not edited in this follow-up.

Manual verification

PR: NVIDIA/OpenShell #5135 (feat/4271-v1), head c4aa964f940e910dced3e653d791727fe5a5b413.
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.
  • Sandbox image: ubuntu:24.04 plus curl (sha256 of /usr/bin/curl in the image: 1ccbd3dae948374ba8eef1ebda26c64b783ede50b463196e53666e0880bdac60).
  • Receiver: a small Python HTTP server on the host (port 28125) that prints the peer address (ip:port) of every connection it accepts. The sandbox reaches it as host.openshell.internal.

Steps

# policy (policy-5135.yaml): TCP endpoint with tls: skip, any binary
network_policies:
  host_proxy:
    endpoints: [{host: host.openshell.internal, port: 28125, protocol: tcp, tls: skip}]
    binaries: [{path: "/**"}]

openshell -g <gw> sandbox create --name tcp1 --policy policy-5135.yaml --no-tty -- sleep 900
openshell -g <gw> settings set tcp1 --key ocsf_json_enabled --value true
openshell -g <gw> sandbox exec tcp1 -- bash -c 'curl -p -s http://host.openshell.internal:28125/probe-5135-b'
docker logs <tcp1 supervisor container> | grep 'CONNECT allowed'      # JSON OCSF events go to the supervisor console

Before (main)

The allowed CONNECT event (class_uid 4001, "CONNECT allowed host.openshell.internal:28125") has src_endpoint, dst_endpoint, proxy_endpoint, actor.process (/usr/bin/curl, parent /usr/bin/bash) and firewall_rule, and no unmapped object at all: no upstream socket and no executable digest. (grep -c 'upstream_socket\|binary_sha256' on the captured event: 0.)

After (PR head)

Two probes sent from the sandbox, with the receiver's own log next to the event:

receiver:  RECV accepted-from=127.0.0.1:51396 request=GET /probe-5135-b
event   :  "message":"CONNECT allowed host.openshell.internal:28125",
           "unmapped":{"upstream_socket_local":"127.0.0.1:51396","upstream_socket_peer":"127.0.0.1:28125",
                       "binary_sha256":"1ccbd3dae948374ba8eef1ebda26c64b783ede50b463196e53666e0880bdac60"},
           "dst_endpoint":{"domain":"host.openshell.internal","port":28125},
           "actor":{"process":{"name":"/usr/bin/curl","pid":0,"parent_process":{"name":"/usr/bin/bash","pid":0}}},
           "firewall_rule":{"name":"host_proxy","type":"opa"}, "action":"Allowed"

receiver:  RECV accepted-from=127.0.0.1:53374 request=GET /probe-5135-json
event   :  "unmapped":{"upstream_socket_local":"127.0.0.1:53374","upstream_socket_peer":"127.0.0.1:28125",
                       "binary_sha256":"1ccbd3da...bdac60"}

The upstream_socket_local of each event equals the address the receiver saw as the connecting peer (51396, 53374), and upstream_socket_peer is the receiver's listening address. binary_sha256 equals the sha256 of /usr/bin/curl in the workload image. A receiver that records its accepted-socket peers can therefore map a connection back to the sandbox process that opened it.

Only the CONNECT relay with tls: skip was exercised. The transparent-TCP relay named in the PR (ancestor paths in actor.process) was not run.

Observation

actor.process.pid is 0 in both the main and the PR event (a pre-existing value in this Docker deployment, not caused by the PR).

Result

PASS: the new unmapped.upstream_socket_local, upstream_socket_peer and binary_sha256 fields appear in a real allowed TCP event, the socket tuple matches the receiver's accepted socket, and the digest matches the executable. None of them exist on main.

Signed-off-by: Vineeth Sai Narajala <vnarajal@cisco.com>
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>
Keep resolved ancestor executable paths as separate OCSF entries so a path
containing the display delimiter cannot alter the reported process chain.
Treat upstream socket address queries as optional audit metadata after the
connection is authorized, preserving the relay if either lookup fails.

Cover both event classes, direct and TLS proxy sockets, and clarify receiver
tuple comparisons in the docs.

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

# Conflicts:
#	crates/openshell-supervisor-network/src/proxy.rs
#	crates/openshell-supervisor-network/src/proxy/egress.rs
…ilure

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:28:06.966120Z 903a54e 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.

1 participant