NVIDIA / NVIDIA/OpenShell

feat: normalize trailing-dot hosts in the sandbox supervisor

Offen
#2,201 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

area:supervisor state:stale
Vorherrschende Sprache
Rust
Sterne
8.7k
Forks
1.3k
Ø Merge
2 T. 11 Std.
Gemergte PRs (30 T.)
253

Beschreibung

Problem Statement

A destination host with a trailing dot (api.example.com., the root-anchored FQDN form from RFC 1034) is treated inconsistently by the sandbox supervisor because the host string parsed from the wire flows verbatim into every decision layer:

  • Endpoint admission (both the exact and glob branches of endpoint_allowed in sandbox-policy.rego) never matches the dotted form, so a CONNECT to an allowed host fails closed when spelled with the trailing dot.
  • Hostless allowed_ips endpoints admit any hostname on the port, so the dotted form is admitted there, but supervisor middleware selectors (including the universal **) never match a host containing an empty DNS label, so middleware such as openshell/secrets is silently skipped for that traffic. Middleware ships in #2027 (unreleased).
  • Dynamic credential key matching (token grant injection) also fails against the dotted form.

The dotted spelling is rare but legitimate: in Kubernetes, curl https://api.example.com. is a standard way to opt out of resolver search-domain expansion under ndots:5. It is also a cheap probe for a sandboxed agent looking for policy gaps.

Severity note: filed as regular hardening rather than via SECURITY.md because shipped behavior fails closed (host-matched endpoints deny the dotted form). The only permissive path, the middleware skip on hostless allowed_ips endpoints, exists only in the unreleased middleware feature from #2027.

Follow-up from the #2027 host matching review.

Proposed Design

Normalize the host once at the wire parse points, and keep the original string for DNS resolution:

  1. Add a helper in openshell-core next to host_pattern (e.g. normalize_request_host) that strips exactly one trailing dot. example.com.. still contains an empty label, stays invalid, and stays denied.
  2. Apply it in openshell-supervisor-network where the CONNECT authority and Host header are parsed into host and port, before the host is stored in the connection context. ctx.host and every NetworkInput construction site then inherit the canonical form, so Rego admission, middleware selection, credential matching, and OCSF logs can never disagree with each other.
  3. Resolve DNS with the original (dotted) string. The trailing dot's one operational meaning is "do not apply resolver search domains"; stripping before resolution would silently re-enable search expansion in Kubernetes. allowed_ips and cloud-metadata SSRF checks validate resolved IPs after resolution either way, so nothing is weakened.
  4. Do not normalize inside HostPattern::matches. That would fix middleware selection but leave Rego admission seeing the dotted form, recreating the cross-layer divergence #2027 eliminated.
  5. While implementing, check any CONNECT-host vs TLS SNI consistency logic: RFC 6066 forbids the trailing dot in SNI, so clients may keep it in the CONNECT authority while stripping it from SNI.

Safety argument: api.example.com. and api.example.com are the same DNS name, and a sandboxed agent can always send the undotted form, so normalization grants no new network access; it only removes the inconsistent treatment of the dotted spelling. Precedent: Envoy's strip_trailing_host_dot, and nginx strips the dot for server_name matching.

Testing: a proxy-level test that a CONNECT to api.example.com.:443 yields the same admission decision and middleware chain as api.example.com:443, a case pinning that api.example.com.. stays denied, and a resolution-path check that the original form reaches the resolver.

Alternatives Considered

  • Normalize inside HostPattern::matches only: fixes middleware selection but admission still sees the dotted form, so the layers diverge again.
  • Reject trailing-dot hosts outright at the proxy: closes the middleware gap and is simple, but breaks legitimate root-anchored requests that Kubernetes users rely on, with no benefit over normalization.
  • Normalize everywhere including DNS resolution: simpler (one string), but changes which address a root-anchored request reaches in search-domain environments, silently overriding client intent.

Agent Investigation

Findings from the #2027 host matching review:

  • normalize_host_lookup_key in crates/openshell-supervisor-network/src/proxy.rs only strips IPv6 brackets, and only for specific alias lookups; nothing on the request path handles trailing dots.
  • openshell_core::host_pattern::HostPattern::matches rejects any host containing an empty DNS label, so even ** does not match api.example.com.; pinned by the universal_wildcard_matches_any_host test.
  • Rego endpoint_allowed (exact branch lower(endpoint.host) == lower(network.host), glob branch glob.match(lower(endpoint.host), ["."], lower(network.host))) also fails against the dotted form; verified against the regorus 0.9.1 globset transformation.
  • The hostless allowed_ips branch in sandbox-policy.rego matches any hostname on the port, making it the only admission path for dotted hosts today.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit den Host-Parsing-Einstiegspunkten in openshell-supervisor-network und dem host_pattern-Helper in openshell-core, und untersuche anschließend sandbox-policy.rego und proxy.rs. Verfolge, wie der ursprüngliche Host die DNS-Auflösung erreicht, während der kanonische Host Admission, Middleware und Credential-Matching erreicht. Als abgeschlossen gilt die Änderung, wenn Hosts mit und ohne abschließenden Punkt dasselbe Admission- und Middleware-Verhalten aufweisen, zwei abschließende Punkte weiterhin abgelehnt werden und ein Test für den Auflösungspfad die ursprüngliche Form beibehält.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
rust
Bereich
networking, security
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Klar beschrieben
Anfängerfreundlichkeit
68/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.