Skip to content

fix(sandbox): exempt host gateway from SSRF block for rootless Podman#1279

Open
maxamillion wants to merge 3 commits intoNVIDIA:mainfrom
maxamillion:fix_podman_link_local
Open

fix(sandbox): exempt host gateway from SSRF block for rootless Podman#1279
maxamillion wants to merge 3 commits intoNVIDIA:mainfrom
maxamillion:fix_podman_link_local

Conversation

@maxamillion
Copy link
Copy Markdown
Collaborator

Summary

Rootless Podman with pasta assigns a link-local IP (169.254.x.x) as the host gateway — previously always-blocked by SSRF guards — preventing sandbox code from reaching host.openshell.internal. This PR fixes the SSRF exemption, hardens it against misuse, and adds a shared is_link_local_ip() helper to openshell-core.

Related Issue

No tracking issue — discovered during rootless Podman networking investigation.

Changes

  • crates/openshell-core/src/net.rs: Add public is_link_local_ip(IpAddr) -> bool covering IPv4 169.254.0.0/16, IPv6 fe80::/10, and IPv4-mapped variants. Refactor the duplicate inline (v6.segments()[0] & 0xffc0) == 0xfe80 bitmask out of is_always_blocked_ip() and is_internal_ip() (was 3 copies, now 1).

  • crates/openshell-sandbox/src/proxy.rs:

    • Read trusted gateway IP from /etc/hosts at proxy startup (before user code runs); exempt only that specific IP from the link-local SSRF block.
    • detect_trusted_host_gateway() rejects cloud metadata IPs, always-blocked non-link-local IPs (loopback, unspecified), and emits a structured warning if /etc/hosts has multiple distinct IPs for the alias.
    • resolve_and_check_trusted_gateway() adds defense-in-depth: rejects cloud metadata, mismatch against the pinned IP, always-blocked non-link-local addresses, and control-plane ports.
    • HOST_GATEWAY_ALIASES covers host.openshell.internal, host.containers.internal, host.docker.internal.
    • CLOUD_METADATA_IPS (169.254.169.254) is never exempted.
  • crates/openshell-sandbox/src/mechanistic_mapper.rs and crates/openshell-server/src/grpc/policy.rs: Add "169.254." to generate_security_notes() host prefix checks (advisory notes were missing link-local).

Testing

  • mise run pre-commit passes (all Rust checks: fmt, clippy, check, helm lint, markdown lint)
  • Unit tests added/updated
    • 9 new tests for is_link_local_ip() in openshell-core
    • 8 new parse_hosts_file_for_host tests covering: single entry, same-IP deduplication, multiple distinct IPs (documents first-wins ordering), multi-alias lines, missing alias, comment stripping
    • 4 new resolve_and_check_trusted_gateway tests using IP literals (deterministic, no DNS) covering: loopback rejection, unspecified rejection, mismatch rejection, cloud metadata rejection
    • 4 new detect_trusted_host_gateway guard predicate tests
    • 666 sandbox tests pass, 130 core tests pass
  • E2E tests added/updated (not applicable — proxy-internal change, no protocol or API surface change)

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable) — no architecture-level change; implementation detail only

pasta assigns a link-local IP (169.254.x.x) as the host gateway in
rootless Podman containers, which was always blocked by SSRF guards.
Read the trusted gateway IP from /etc/hosts at proxy startup (injected
by the driver via host.openshell.internal / host.containers.internal
aliases) and exempt only that specific IP from the link-local block,
while keeping 169.254.169.254 (cloud metadata) hard-blocked regardless.
Extract a public is_link_local_ip(IpAddr) helper in openshell-core::net
covering IPv4 169.254.0.0/16, IPv6 fe80::/10, and IPv4-mapped variants.
Refactor the inline IPv6 fe80::/10 bitmask in is_always_blocked_ip() and
is_internal_ip() to use the new helper, eliminating two copies of the
same pattern.

Harden the trusted-gateway SSRF exemption in proxy.rs: both
detect_trusted_host_gateway() and resolve_and_check_trusted_gateway()
now reject always-blocked non-link-local IPs (loopback, unspecified)
with a warning. The exemption was intended only for link-local addresses
used by rootless Podman with pasta; a /etc/hosts entry mapping
host.openshell.internal to 127.0.0.1 or 0.0.0.0 previously bypassed
the loopback/unspecified SSRF invariant.

Add 169.254. prefix to the generate_security_notes() host checks in
mechanistic_mapper and policy server, closing a gap where link-local
literal hosts were not flagged as internal/private in advisory notes.
…ion tests

When detect_trusted_host_gateway() finds more than one distinct IP for
host.openshell.internal, emit a structured warning so operators can
diagnose unexpected /etc/hosts state. The first entry still wins (and
any runtime mismatch is caught by resolve_and_check_trusted_gateway),
but the ambiguity is now observable.

Replace inline guard-logic tests with real calls to
resolve_and_check_trusted_gateway() using IP literals, which bypass DNS
and give deterministic results. This exercises the actual function
wiring rather than mirroring the predicate logic manually.

Add parse_hosts_file_for_host tests covering: single entry, same-IP
deduplication across lines, multiple distinct IPs (documents first-wins
ordering), multi-alias lines, missing alias, and comment handling.
@maxamillion maxamillion requested review from a team, derekwaynecarr and mrunalp as code owners May 8, 2026 21:34
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 8, 2026

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.

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