You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug(network): v4-mapped loopback flows (AF_INET connect to dual-stack listener) get no OCSF DENY event — silent RST or hold-then-timeout #3735
OpenShell emits an excellent explicit denial surface for policy-denied egress — NET:REFUSE [MED] DENIED host [reason:policy_dns_ineligible] + NET:DENIED <binary> -> host:port [reason:transparent_tcp_policy_denied] OCSF events, with the client receiving a clean EACCES. However, one flow class falls entirely outside that coverage:
An AF_INET connection to a dual-stack ([::], IPV6_V6ONLY=0) loopback listener — i.e. the default bind of many runtimes (python http.server, node, etc.) — is reset or silently held with no OCSF event and no deny reason.
This flow class is on the critical path of every external access mechanism, because service expose, forward start (ssh relay), and forward service (gRPC) all connect to the target via native 127.0.0.1.
Environment
OpenShell 0.1.1 (CLI + gateway, Homebrew tap nvidia/openshell)
macOS 15 (Apple Silicon), local Docker compute driver
Sandbox image: nvcr.io/nvidia/base/ubuntu:24.04 and python:3.13-alpine
Reproduction
Create a workload whose server binds dual-stack (default):
/proc/net/tcp6 shows [::]:8080 LISTEN; /proc/net/tcp has no v4 listener.
In-sandbox client to http://127.0.0.1:8080/ → ECONNRESET. openshell logs shows NET:OPEN/NET:CLOSE only — no DENIED event.
openshell service expose web 8080 → the printed URL hangs (relay bridges, TCP opens, zero payload, timeout). Same signature via forward start and forward service.
Contrast — policy-denied egress from the same sandbox:
Bind the workload natively AF_INET: python3 -m http.server 8080 --bind 0.0.0.0 → in-sandbox 127.0.0.1, the service expose URL, and forwards all return 200.
Expected
Denied v4-mapped flows should behave like other denied flow classes: emit the same OCSF DENIED event with a reason code (e.g. reason:v4_mapped_flow_denied) and close with an explicit error, instead of a silent RST/hold. Alternatively, if v4-mapped flows are intended to work (docs say only loopback/link-local/unspecified destinations are always blocked, yet a native --bind 127.0.0.1 listener on 127.0.0.1:8081 is reachable — so the block is flow-family-specific, not destination-specific), the docs and enforcement should be aligned.
Impact
Any workload using a runtime default dual-stack bind is unreachable via all three external access paths, with an error signature that looks like a routing/relay failure rather than a policy denial — costly to diagnose.
Before choosing "allow" versus "deny", I think this needs one normalization invariant:
an IPv4 client connecting to a dual-stack listener through a v4-mapped IPv6 address should reach the same policy decision and audit path as the semantically equivalent native IPv4 flow.
Right now the address-family representation appears to change which enforcement/audit path is exercised.
A useful matrix would be:
127.0.0.1 -> native IPv4 listener;
127.0.0.1 -> [::] dual-stack listener;
::1 -> IPv6 listener;
v4-mapped ::ffff:127.0.0.1.
For each one, assert:
allow/deny result;
reason code;
OCSF event presence;
normalized destination identity.
If loopback is intentionally denied, every representation should fail explicitly and emit the same class of denial evidence.
If this particular loopback path is intended to support service forwarding, normalization should happen before policy classification so the wire representation cannot silently change the decision.
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.
Activity
kvnloo commentedon Sep 27, 2026
Before choosing "allow" versus "deny", I think this needs one normalization invariant:
an IPv4 client connecting to a dual-stack listener through a v4-mapped IPv6 address should reach the same policy decision and audit path as the semantically equivalent native IPv4 flow.
Right now the address-family representation appears to change which enforcement/audit path is exercised.
A useful matrix would be:
127.0.0.1-> native IPv4 listener;127.0.0.1->[::]dual-stack listener;::1-> IPv6 listener;::ffff:127.0.0.1.For each one, assert:
If loopback is intentionally denied, every representation should fail explicitly and emit the same class of denial evidence.
If this particular loopback path is intended to support service forwarding, normalization should happen before policy classification so the wire representation cannot silently change the decision.
github-actions commentedon Oct 15, 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.