Summary
host.openshell.internal is the documented mechanism for sandboxes to reach services on the gateway host (e.g., OTEL collectors, dev servers). In gateway 0.0.86 (podman driver), traffic to host.openshell.internal was allowed by the egress proxy and TCP connections completed successfully. In gateway 0.0.91, the L7 policy engine still allows the request, but the new SSRF engine blocks it because DNS resolution fails for the hostname.
Environment
- Gateway 0.0.86: podman driver, macOS arm64, supervisor
:latest
- Gateway 0.0.91: same setup, downloaded from GitHub release
v0.0.91
- Podman: applehv, arm64, rootless
- Sandbox image:
ghcr.io/nvidia/openshell-community/sandboxes/base:latest
Reproduction
Gateway 0.0.91 (broken)
# Start gateway
OPENSHELL_PODMAN_SOCKET="$(podman machine inspect --format '{{.ConnectionInfo.PodmanSocket.Path}}')" \
openshell-gateway --config gateway-podman.toml
# Create sandbox and add policy for host.openshell.internal
openshell sandbox create --name test-ssrf --provider vertex \
--env OTEL_EXPORTER_OTLP_ENDPOINT=http://host.openshell.internal:9318 \
-- sleep 300
openshell policy update test-ssrf \
--add-endpoint 'host.openshell.internal:9318:read-write:rest:enforce' \
--binary /usr/bin/node \
--add-allow 'host.openshell.internal:9318:POST:/**' \
--wait
# Attempt to reach host.openshell.internal from the sandbox
openshell sandbox exec --name test-ssrf -- \
/usr/bin/node -e "fetch('http://host.openshell.internal:9318/status').then(r=>r.text()).then(console.log).catch(e=>console.log(e.message))"
# Result: hangs (connection never completes)
Sandbox logs show the root cause
[sandbox] HTTP:POST [INFO] ALLOWED /usr/local/bin/claude(72) -> POST http://host.openshell.internal:9318/v1/logs [policy:allow_host_openshell_internal_9318 engine:l7]
[sandbox] HTTP:POST [MED] DENIED /usr/local/bin/claude(72) -> POST http://host.openshell.internal:9318/v1/logs [policy:allow_host_openshell_internal_9318 engine:ssrf] [reason:DNS resolution failed for host.openshell.internal:9318: failed to lookup address information: Name does not resolve]
The L7 engine correctly matches the policy and allows the request. The SSRF engine then blocks it because host.openshell.internal cannot be resolved via DNS inside the sandbox.
/etc/hosts inside the sandbox (0.0.91)
# hosts file (comment from base image, sandbox is running on podman not K8s)
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.129.3.208 default--test-ssrf
No host.openshell.internal entry.
Gateway 0.0.86 (worked)
The same test with gateway 0.0.86 on the same podman setup (same config, same base image) successfully delivered OTEL data. The sandbox's /etc/hosts also did NOT contain host.openshell.internal, which means the 0.0.86 proxy resolved the hostname internally without relying on DNS. Evidence:
- OTEL receiver on the host received 1,308 bytes at
POST /v1/logs from Claude Code running inside the sandbox
- Sandbox logs showed
ALLOWED with no SSRF denial
Root cause analysis
In 0.0.86, the egress proxy handled host.openshell.internal as a special hostname (resolved to the gateway host IP internally, similar to how inference.local is handled). The 0.0.91 SSRF engine introduced a DNS validation step that runs after the L7 policy check. Since host.openshell.internal is a proxy-internal name (not in DNS), the SSRF engine rejects it even though the L7 engine approved it.
Impact
Any use case that relies on sandbox-to-host communication via host.openshell.internal is broken on 0.0.91:
- OTEL/telemetry forwarding from sandboxes to a host-side collector
- Development servers running on the host
- Any service exposed on the gateway host that sandboxes need to reach
Expected behavior
host.openshell.internal should be treated as a trusted proxy-internal hostname by the SSRF engine (same as inference.local). The SSRF engine should either:
- Skip DNS validation for known proxy-internal hostnames (
host.openshell.internal, inference.local), or
- Resolve
host.openshell.internal to the gateway host IP before the SSRF check runs
Additional context: K8s egress question
On the Kubernetes driver (workload cluster), we observed a related pattern: the L7 engine allows traffic to policy-approved external endpoints (ClusterIPs, OpenShift routes, NodePort IPs), but TCP connections hang indefinitely. The proxy appears unable to establish outbound TCP connections from the sandbox's network namespace.
Is this by design (all external access on K8s is gateway-mediated, like inference.local)? Or should policy-approved endpoints be reachable via direct TCP on K8s as well? The answer determines whether an OTEL relay endpoint (routed through the gateway gRPC channel) is needed for K8s sandboxes, or whether fixing the TCP connectivity is the right path.
K8s egress logs (for reference)
# Policy allows, but TCP hangs:
[sandbox] NET:OPEN [INFO] ALLOWED /usr/bin/node(66) -> 172.30.216.186:9318 [policy:allow_172_30_216_186_9318 engine:opa]
[sandbox] HTTP:GET [INFO] ALLOWED GET http://172.30.216.186:9318/status [policy:allow_172_30_216_186_9318 engine:l7]
# Connection never completes. Same behavior for ClusterIP, NodePort, and HTTP routes.
Summary
host.openshell.internalis the documented mechanism for sandboxes to reach services on the gateway host (e.g., OTEL collectors, dev servers). In gateway 0.0.86 (podman driver), traffic tohost.openshell.internalwas allowed by the egress proxy and TCP connections completed successfully. In gateway 0.0.91, the L7 policy engine still allows the request, but the new SSRF engine blocks it because DNS resolution fails for the hostname.Environment
:latestv0.0.91ghcr.io/nvidia/openshell-community/sandboxes/base:latestReproduction
Gateway 0.0.91 (broken)
Sandbox logs show the root cause
The L7 engine correctly matches the policy and allows the request. The SSRF engine then blocks it because
host.openshell.internalcannot be resolved via DNS inside the sandbox./etc/hostsinside the sandbox (0.0.91)No
host.openshell.internalentry.Gateway 0.0.86 (worked)
The same test with gateway 0.0.86 on the same podman setup (same config, same base image) successfully delivered OTEL data. The sandbox's
/etc/hostsalso did NOT containhost.openshell.internal, which means the 0.0.86 proxy resolved the hostname internally without relying on DNS. Evidence:POST /v1/logsfrom Claude Code running inside the sandboxALLOWEDwith no SSRF denialRoot cause analysis
In 0.0.86, the egress proxy handled
host.openshell.internalas a special hostname (resolved to the gateway host IP internally, similar to howinference.localis handled). The 0.0.91 SSRF engine introduced a DNS validation step that runs after the L7 policy check. Sincehost.openshell.internalis a proxy-internal name (not in DNS), the SSRF engine rejects it even though the L7 engine approved it.Impact
Any use case that relies on sandbox-to-host communication via
host.openshell.internalis broken on 0.0.91:Expected behavior
host.openshell.internalshould be treated as a trusted proxy-internal hostname by the SSRF engine (same asinference.local). The SSRF engine should either:host.openshell.internal,inference.local), orhost.openshell.internalto the gateway host IP before the SSRF check runsAdditional context: K8s egress question
On the Kubernetes driver (workload cluster), we observed a related pattern: the L7 engine allows traffic to policy-approved external endpoints (ClusterIPs, OpenShift routes, NodePort IPs), but TCP connections hang indefinitely. The proxy appears unable to establish outbound TCP connections from the sandbox's network namespace.
Is this by design (all external access on K8s is gateway-mediated, like
inference.local)? Or should policy-approved endpoints be reachable via direct TCP on K8s as well? The answer determines whether an OTEL relay endpoint (routed through the gateway gRPC channel) is needed for K8s sandboxes, or whether fixing the TCP connectivity is the right path.K8s egress logs (for reference)