Repository navigation
feat(sandbox): log connection attempts that bypass proxy path #268
Description
Problem
When a process inside a sandbox attempts an outbound connection without going through the configured HTTP proxy path, we currently get poor observability.
In a recent openclaw-local debugging session, OpenClaw's Node fetch() failed with fetch failed / UND_ERR_CONNECT_TIMEOUT while curl to the same https://inference.local/... endpoint succeeded. The difference was that curl honored the sandbox proxy environment automatically, while Node fetch() required NODE_USE_ENV_PROXY=1.
Because the failing path did not produce useful OpenShell connection logs, it was hard to tell whether the app:
- never emitted traffic
- attempted a direct socket connection
- bypassed the proxy path
- was blocked before the request reached the normal logging path
John suggested that seccomp may be a good place to catch the socket attempt before we block it.
Requested change
Add sandbox-level diagnostics for outbound connection attempts that do not go through the expected proxy path.
The main goal is to surface enough information to debug cases where application networking fails before normal proxy / connection logs appear.
What to log
At minimum, when feasible, log:
- process / binary identity
- destination address and port requested by the process
- whether the connection was direct vs proxy-mediated
- reason the attempt was blocked or considered invalid
- enough correlation data to tie the event back to a sandbox and process
Possible implementation direction
Investigate whether seccomp can intercept socket / connect attempts early enough to log them before denial, especially for direct connections that bypass the proxy path.
Acceptance criteria
- A direct connection attempt from inside a sandbox that does not use the proxy path produces an observable diagnostic event
- The diagnostic includes destination and process identity
- The event is visible through existing sandbox / gateway logging surfaces, or a clearly documented new surface
- The logs make it obvious why a request like fetch failed produced no normal connection log
Repro context
Inside sandbox openclaw-local:
- curl https://inference.local/v1/models succeeded
- plain Node fetch(https://inference.local/v1/models) timed out
- NODE_USE_ENV_PROXY=1 node ...fetch(...) succeeded
This suggests we need better observability for connection attempts that never enter the expected proxy/logging path.
Activity
johntmyers commentedon Mar 15, 2026
Implementation Plan
Issue type:
featComplexity: Medium
Confidence: High — the architecture already has all necessary hooks; this is new iptables rules + a log reader task
Summary
When a sandbox process attempts a direct outbound connection without going through the HTTP CONNECT proxy (e.g., Node.js
fetch()withoutNODE_USE_ENV_PROXY=1), the connection currently hangs silently for 30+ seconds until timeout — no log, no error, complete observability blackout. This plan adds iptables rules inside the sandbox network namespace to immediately reject bypass attempts (ECONNREFUSED instead of timeout) and a background/dev/kmsgreader to surface structured diagnostic events through existing logging infrastructure.Architecture Analysis
Why seccomp won't work: In proxy mode,
AF_INET/AF_INET6are allowed (process needs TCP to talk to the proxy). Seccomp BPF cannot dereference thesockaddrpointer inconnect()to distinguish proxy vs. direct destinations.Why iptables LOG + REJECT is the right mechanism:
CAP_BPF, massive complexity for a diagnostic featureCAP_NET_ADMIN, zero new capability requirements, provides both fast-fail UX AND diagnostic loggingPrerequisites
iptablesmust be added to the sandbox container image. It is not currently installed — onlyiproute2is present. The supervisor already has all required capabilities (CAP_NET_ADMIN, root UID).openshell-communityrepoiptablesto sandbox base image Dockerfileiproute2— required for this featureexamples/bring-your-own-container/Dockerfileiptablesto package listarchitecture/sandbox-custom-containers.mdiptablesas optional dependencyThe feature uses the same graceful degradation pattern as
iproute2: ifiptablesis not found at runtime, log a warning and skip rule installation. The namespace still provides isolation via routing — just without fast-fail and diagnostics.Scope
crates/openshell-sandbox/src/sandbox/linux/netns.rs: Add iptables rules during namespace creation;run_iptables_netns()helpercrates/openshell-sandbox/src/bypass_monitor.rs(NEW):/dev/kmsgreader task, LOG line parser, process identity resolution, structured event emissioncrates/openshell-sandbox/src/lib.rs: Registerbypass_monitormodule; spawn monitor task after netns creation; wire up denial_txexamples/bring-your-own-container/Dockerfile: Addiptablesto package listarchitecture/sandbox.md: Document bypass detection subsystemarchitecture/sandbox-custom-containers.md: Documentiptablesas optional dependency for bypass diagnosticsFiles NOT changed:
seccomp.rs— cannot distinguish proxy vs. direct (same socket domain)proxy.rs— never sees bypass trafficopenshell-server/— bypass events flow through existingLogPushLayerandDenialAggregatorpathsopenshell-policy/— no policy schema changesExternal dependency (separate PR to openshell-community repo):
iptablespackageImplementation Steps
Step 1: Add iptables rules to sandbox network namespace
File:
crates/openshell-sandbox/src/sandbox/linux/netns.rsAfter the default route is configured in
NetworkNamespace::create(), add iptables rules inside the sandbox namespace:Key design decisions:
ECONNREFUSEDinstead of a 30-second timeoutiptablesnot found, log warning and skip — namespace still isolates via routingip6tablesStep 2: Implement
/dev/kmsgbypass monitorFile:
crates/openshell-sandbox/src/bypass_monitor.rs(NEW)A background tokio task that:
/dev/kmsg, seeks to end (skip historical messages)openshell:bypass:{namespace_id}:prefixDST=,DPT=,SPT=,UID=,PROTO=procfs::resolve_tcp_peer_identity()(best-effort, TCP only)tracing::warn!()matching existing proxy log conventions (see Example Output below)DenialEventto the denial aggregator withdenial_stage: "bypass"(visibility only — no policy proposals)Graceful degradation: if
/dev/kmsgcan't be opened, log a one-time warning and return.Step 3: Wire up in sandbox lifecycle
File:
crates/openshell-sandbox/src/lib.rsAfter
NetworkNamespace::create()succeeds and beforeProcessHandle::spawn(), spawn the bypass monitor task (Linux only, gated with#[cfg(target_os = "linux")]).Step 4: Update BYOC example and documentation
examples/bring-your-own-container/Dockerfile: Addiptablesto apt-get install linearchitecture/sandbox.md: Add "Bypass Detection" sectionarchitecture/sandbox-custom-containers.md: Documentiptablesas optional dependencyExample Output
The bypass monitor emits structured tracing events that follow the same field conventions as existing proxy log lines (
CONNECT,FORWARD). Thesandbox_idis injected at the transport layer byLogPushLayer— it does not appear in the tracing fields directly.Tracing call (Rust source)
Example 1: Node.js fetch() bypassing proxy (TCP)
The original issue's scenario — Node.js
fetch()withoutNODE_USE_ENV_PROXY=1:Compare with how a normal proxied connection looks:
Example 2: DNS bypass attempt (UDP)
A process tries direct DNS resolution instead of using the proxy's resolver:
Example 3: Process identity unavailable (race condition)
Socket closed before
/proclookup — binary/PID fall back to"-":DenialEvent (fed to aggregator)
Each bypass event also produces a
DenialEventfor theDenialAggregator:This deduplicates by
(host, port, binary)and flushes viaSubmitPolicyAnalysisgRPC to the gateway, same as existing denial stages.Wire format (SandboxLogLine proto)
As seen by the gateway after
LogPushLayerserializes the tracing event:Test Plan
/dev/kmsg— parser correctness for IPv4, IPv6, TCP, UDP, truncated, non-matching lines. VerifyDenialEventwithdenial_stage: "bypass"aggregates correctly.#[ignore], requires root) Create namespace, verify iptables rules present (both TCP and UDP). Direct TCP connect inside namespace gets ECONNREFUSED. Direct UDP send gets ICMP unreachable.Risks & Open Questions
iptablesnot in sandbox image/dev/kmsgrestricted in containerxt_LOGkernel module not loaded--limit 5/sec --limit-burst 10rate limitingResolved questions:
Should bypass events generate policy proposals?No — bypass indicates a proxy config problem in the app, not a missing network policy. Events flow through denial aggregator for visibility only.UDP bypass logging?Yes — included in initial implementation (Step 1 rules 6-7). Covers DNS bypass and other non-TCP protocols.Is iptables available in the sandbox?Not currently. Needs to be added to the sandbox base image (openshell-communityrepo) and BYOC example. Supervisor already hasCAP_NET_ADMINand root — no capability changes needed. Feature degrades gracefully if absent.Documentation Impact
architecture/sandbox.md: Add bypass detection subsystem documentationarchitecture/sandbox-custom-containers.md: Documentiptablesas optional dependency for bypass diagnosticsRevision 4 — add Prerequisites section for iptables package dependency; add BYOC Dockerfile and sandbox-custom-containers.md to scope; resolve iptables availability question
Revision 3 — add example output section: tracing calls, rendered log lines (TCP/UDP/race), DenialEvent struct, SandboxLogLine wire format
Revision 2 — include UDP bypass logging in initial implementation; confirm no policy proposals from bypass events
Revision 1 — initial plan
johntmyers commentedon Mar 15, 2026
Confirmed — bypass events will not generate policy proposals. They'll flow through the denial aggregator for visibility only.
Will include UDP bypass logging (LOG + REJECT) in the initial implementation rather than deferring to a follow-up. This covers DNS bypass scenarios. The iptables rules in Step 1 will be extended with matching UDP rules for both IPv4 and IPv6.
Updating the plan now.
johntmyers commentedon Mar 15, 2026
Correct.
iptablesis not currently in the sandbox base image — onlyiproute2is. But the supervisor already has everything else needed:CAP_NET_ADMINis already granted (crates/openshell-server/src/sandbox/mod.rs:946)So it's the same pattern as
iproute2— a package that needs to be present in the sandbox image for the feature to activate. The plan already includes graceful degradation (check foriptablesavailability, skip with a warning if absent), so existing images won't break.Concrete changes needed:
openshell-communityrepo — addiptablesto the sandbox base image Dockerfile (alongsideiproute2)examples/bring-your-own-container/Dockerfile) — addiptablesto the package listarchitecture/sandbox-custom-containers.md) — documentiptablesas an optional dependency for bypass diagnostics, alongside the existingiproute2requirementUpdating the plan to include these as explicit scope items.
johntmyers commentedon Mar 15, 2026
Confirmed — the sandbox user process cannot modify iptables rules. The supervisor sets up the rules before spawning the child, and the child has no path to modify or remove them.
Primary protection: The child process drops to the
sandboxuser (non-root) viasetuid()(process.rs:423). The kernel clearsCAP_NET_ADMINfrom the effective and permitted capability sets on setuid to non-root. WithoutCAP_NET_ADMIN, the kernel rejects all iptables/netfilter operations regardless of the interface used (netlink sockets,setsockopt(IPT_SO_SET_REPLACE), etc.).Defense-in-depth layers:
run_as_userother than"sandbox"openshell-policy/src/lib.rs:470-481no_new_privsprctl(PR_SET_NO_NEW_PRIVS)prevents escalation via setuid binariesseccomp.rs:25setuid(0)fails after privilege drop (CERT POS37-C)process.rs:436-444/sysnot accessible at all;/procread-only — blocks filesystem-based netfilter attackslandlock.rs:37-65CAP_SYS_ADMINforunshare(CLONE_NEWNET))netns.rs:53-169Nuance: The seccomp profile allows
AF_NETLINKin Proxy mode (needed for legitimate operations). The capability check — not the socket domain — is what gates netfilter access. BlockingNETLINK_NETFILTERspecifically could be a defense-in-depth follow-up but is not required for safety.No plan changes needed — the design is sound.
chore(sandbox): add iptables to base image for bypass diagnostics
chore(sandbox): add iptables to base image for bypass diagnostics (#36)
feat(sandbox): log connection attempts that bypass proxy path
johntmyers commentedon Mar 15, 2026
Implementation Complete
PR: #326
What was built
Added iptables LOG + REJECT rules inside the sandbox network namespace for bypass detection. Applications now get immediate ECONNREFUSED instead of a 30-second timeout when they bypass the proxy, and a
/dev/kmsgmonitor emits structuredBYPASS_DETECTtracing events with destination, protocol, process identity, and actionable hints (e.g., "for Node.js set NODE_USE_ENV_PROXY=1").Tests
Docs updated
architecture/sandbox.md: Bypass detection sectionarchitecture/sandbox-custom-containers.md: iptables as optional dependencyExternal dependency
Base image
iptablespackage: NVIDIA/OpenShell-Community#36 (merged)The issue will auto-close when the PR is merged.
feat(sandbox): log connection attempts that bypass proxy path (#326)
feat(sandbox): log connection attempts that bypass proxy path (#326)
feat(sandbox): log connection attempts that bypass proxy path