Repository navigation
feat(supervisor): pass the resolved caller identity to the far end of a relayed protocol: tcp endpoint #4271
Description
User Story
I use OpenShell 0.1.1 (moving to 0.1.2) as the sandbox runtime under a host-side policy and telemetry agent (DefenseClaw, Apache-2.0). The agent runs its own egress proxy on the host. Each sandbox reaches it through one network policy rule (host.openshell.internal:<port>, protocol: tcp, tls: skip, binaries /**) with HTTPS_PROXY pointing at it. The proxy decides each destination from the CONNECT target and the TLS server name without terminating TLS, so certificate-pinning clients keep working, and it can run an "open except a deny list" mode (compare #3856). A per-sandbox proxy credential tells it which sandbox a tunnel belongs to.
I directly need to know which process inside the sandbox opened each tunnel: the coding agent itself, a tool it started, or an openshell sandbox exec command. OpenShell resolves exactly that for the connection to our proxy (the NET:OPEN event, ALLOWED /usr/bin/curl(0) -> host.openshell.internal:<port>, names the executable and its ancestors), but the identity never leaves the supervisor.
This affects every per-binary egress rule and every per-process attribution the host proxy could make.
Problem Statement
For a protocol: tcp endpoint with tls: skip, OpenShell relays bytes and tells the far end nothing about the caller:
- The caller identity that crosses the isolation boundary (executable path and digest, ancestor executables;
openshell-isolation-interface/src/contract.rsL854-L883) is used for the policy decision and for the OCSF event, but the far end sees only a TCP connection from the supervisor. - The OCSF event cannot be joined to the far end's socket either. The identity the backend supplies carries no pid, so the decision has none (
proxy.rsL3814) and the actor pid is logged as 0 (openshell-ocsf/src/objects/process.rsL56-L57). The allowed-connection event records the destination, the workload-side source address and the actor, but not the upstream leg's local address and port (proxy.rsL1937-L1965). With several concurrent connections, timing is ambiguous. - Supervisor middleware cannot help: it cannot inspect
tls: skiptraffic (index.mdx L56), with the defaulton_erroran entry cannot select such an endpoint (configure.mdx L178), and its request context does not carry the caller today (originating_process: None,l7/middleware.rsL95; feat(supervisor): populateRequestContext.originating_processfor supervisor middleware #4269). - Upstream proxy chaining is operator-wide, bypasses loopback destinations and sends no caller identity (
upstream_proxy.rsL4-L56).
Impact / Why This Matters
- A host proxy cannot enforce per-binary rules ("only the agent's network binary may reach the package registry"), and its records attribute traffic to the sandbox only.
- The alternatives are to duplicate destination rules per binary in OpenShell policy, which cannot express an open-with-deny-list mode, or to terminate TLS for every destination, which breaks pinning clients.
- The identity already exists inside OpenShell, resolved from trusted runtime mediation; the receiving side has no way to get it.
Proposed Design
Opt-in per endpoint, off by default, so existing receivers see an unchanged byte stream. Either shape below would meet the need; maintainers may prefer one.
- Inline header. Before relaying the first workload byte, OpenShell writes a PROXY protocol v2 header on the upstream connection, with TLVs carrying the sandbox id, the executable path, its SHA-256, the ancestor paths, and the pid and start time when they are meaningful. The workload cannot forge or strip it, because OpenShell writes it before any workload byte and the workload cannot reach the endpoint around OpenShell; a header the workload sends itself arrives after OpenShell's, as payload.
- Join key in the event. The OCSF event for each relayed connection records the upstream leg's local address and port (and a connection id), so a receiver can join its accepted socket's peer address to the event exactly.
Notional policy, for review only:
network_policies:
host_proxy:
endpoints:
- host: host.openshell.internal
port: 3129
protocol: tcp
tls: skip
caller_identity: proxy_protocol_v2 # notional; default off
binaries:
- path: /**Acceptance Criteria
- With the option on, a receiver gets, before the first workload byte, the sandbox id, executable path, executable digest and ancestors that OpenShell logged for that connection.
- With the option off, the relayed byte stream is unchanged.
- Bytes the workload sends cannot replace or alter OpenShell's identity data.
- Each relayed connection's OCSF event carries a key that matches what the receiver observes for that connection.
- Works on the Docker, Podman, Kubernetes and MicroVM drivers.
Alternatives Considered
- Supervisor middleware. Not available for
tls: skipendpoints, HTTP only, and it would require TLS interception for every destination. - Gateway interceptors. They govern control-plane API operations, not individual workload connections.
- Provider credentials or the per-sandbox proxy credential. Both identify the sandbox, not the process, and OpenShell does not rewrite credential placeholders inside a
tls: skiptunnel. - One OpenShell rule per binary and destination. Moves every destination decision into OpenShell policy and cannot express an open mode with a deny list.
- Correlating by timing. Ambiguous with concurrent connections.
- Inspecting the connecting process on the host. The host sees the supervisor process, not the workload process.
Agent Investigation
- feat(supervisor): multi-tenant OpenShell proxy per workspace/namespace or Gateway #2385 states the invariant this proposal keeps: process identity must come from trusted runtime mediation, not a workload assertion. An OpenShell-written header satisfies it; a workload-set HTTP header would not.
- feat: define process identity across supervisor topologies #2291 (closed) describes the process identity contract (PID-reuse protection through start time or pidfd, and no cross-namespace pid in split topologies) that the pid and start time fields would follow.
- The closed MXC port feat(mxc): upstream relay qualification and per-sandbox proxy auth #3326 notes the same gap for its host proxy: per-sandbox proxy authentication does not distinguish individual processes inside a sandbox.
- No existing issue or PR asks for PROXY protocol or caller identity on relayed connections, and the code has no PROXY protocol support (searched issues, PRs and code: "PROXY protocol", "proxy_protocol", "upstream proxy", "identity header", "binary identity", "caller identity", "process identity").
- Verified unchanged in v0.1.1, v0.1.2 and main.
- Related: feat(supervisor): populate
RequestContext.originating_processfor supervisor middleware #4269, feat(policy): allow any public host, so a sandbox can be open except for a deny list #3856, feat(supervisor): multi-tenant OpenShell proxy per workspace/namespace or Gateway #2385, feat: define process identity across supervisor topologies #2291 (closed), Supervisor L7 proxy should support HTTP_PROXY/HTTPS_PROXY for corporate environments #1792 (closed), feat(proxy): supervisor-proxied host-local endpoints — generalize the inference.local pattern for arbitrary host services #1633 (closed), feat(mxc): upstream relay qualification and per-sandbox proxy auth #3326 (closed), feat(observability): include the structured OCSF event in sandbox log lines served by the gateway API #4080.
Checklist
- I've reviewed existing issues and the published docs
- This is a design proposal, not a "please build this" request
Activity
github-actions commentedon Oct 21, 2026
This issue has had no activity for 14 days and is now marked stale. It may be closed in 14 days if there is no further activity. Comment or remove the state:stale label to keep it open.
vineethsai7 commentedon Oct 22, 2026
Rechecked on main at
07b7a74(2026-10-22) and v0.1.5. This still applies: for aprotocol: tcp,tls: skipendpoint, the far end still learns nothing about the caller.BinaryIdentityis unchanged: executable, ancestors and cmdline paths, with no pid (contract.rsL442-L450). The OCSF actor pid still falls back to 0 (objects/process.rsL58-L60).upstream_proxy.rs, 34 lines changed) is still operator-wide, and it sends no caller identity (L1-L12).originating_process: None(l7/middleware.rsL94; feat(supervisor): populateRequestContext.originating_processfor supervisor middleware #4269).proxy.rswas reworked heavily since filing (#4602 bound hostname policy to supervisor DNS, #4701 isolated per-sandbox state). The allowed-connection event still records no upstream-leg local address, so a host-side proxy still cannot join its socket to the OCSF event.