Skip to content

fix(policy,network): separate named and hostless destination classes — mixed scopes validate and match correctly - #5198

Closed
PurpleCHOIms wants to merge 3 commits into
NVIDIA:mainfrom
PurpleCHOIms:fix/hostless-endpoint-ambiguity/mschoi
Closed

PurpleCHOIms wants to merge 3 commits into
NVIDIA:mainfrom
PurpleCHOIms:fix/hostless-endpoint-ambiguity/mschoi

Conversation

@PurpleCHOIms

Copy link
Copy Markdown

Summary

Mixed named + hostless (CIDR) endpoint scopes — the common real-world shape for RoE-style policies (named targets plus IP/CIDR ranges) — were rejected by the ambiguity validator and, once validated on a relaxed build, matched incorrectly by the runtime: named traffic inherited the hostless endpoint's allowed_ips interpretation and was kernel-denied. This change separates the two destination classes end to end: validator, admission matcher, and metadata matching.

Related Issue

Closes #5193

Changes

  • crates/openshell-policy (validator) — find_endpoint_ambiguities no longer treats a named and a hostless endpoint as competing when they share ports: they authorize disjoint destination classes (DNS-mediated named requests vs raw-IP connections), mirroring the existing plain-L4 vs L7 guarantee. Hostless-vs-hostless competition is preserved — differing allowed_ips between two hostless endpoints on shared ports is still rejected.
  • crates/openshell-supervisor-network (approval matcher) — the hostless matcher in sandbox-policy.rego now requires a raw-IP destination. Named connections resolve via policy DNS and match named endpoints only; the hostless endpoint owns the raw-IP class exclusively.
  • crates/openshell-supervisor-network (metadata matching) — endpoint_matches_request's hostless branch (metadata/allowed_ips extraction) gets the same raw-IP-only scoping. This was the live-visible failure path: the port-only hostless matcher attached a CIDR endpoint's ranges to every DNS-mediated hostname sharing its ports, and pin validation intersected the named endpoint's resolutions with the CIDR, producing an empty allowlist. IP-shaped destinations are detected without regex (octet/colon shape checks) so the Rego engine stays compatible.

Release Notes

Policies mixing named targets with hostless allowed_ips (IP/CIDR) endpoints on shared ports now validate and enforce correctly in a single policy: named connections follow their named endpoints, raw-IP connections follow the hostless endpoints.

Testing

  • cargo test -p openshell-policy — 298 passed, 0 failed (295 existing unchanged + 3 new: named+hostless pairs are not ambiguous; named metadata conflicts do not spill into hostless pairs; hostless-vs-hostless differing allowed_ips still rejected).
  • cargo test -p openshell-supervisor-network --lib — 1,580 passed, 0 failed (3 former-semantics fixtures updated to the new matcher semantics).
  • E2E suite — not extended; instead a live kernel verification was performed on a docker gateway + supervisor built from this branch (0.1.6, clean Linux VM): a mixed policy (example.com + 10.10.0.0/24 + OSINT named target) creates born-confined with Phase: Ready; in-scope https://example.com → HTTP 200 (the case that failed before the matcher fix); OSINT named target → HTTP 200; raw-IP inside the range (10.10.0.5) → forwarded (connect timeout against a dead target, not kernel-denied); raw-IP outside the range (10.10.1.5), out-of-scope hostname, and IMDS (169.254.169.254) → kernel DENIED with OCSF audit events.

Checklist

…selectors

A hostless allowed_ips endpoint and a named endpoint authorize different
destination classes: the hostless endpoint matches raw-IP connections in
its ranges, the named endpoint matches requests to its hostname. The two
never compete for the same request's connection or request-pipeline
config, so find_endpoint_ambiguities now skips pairs where exactly one
side is hostless. This mirrors the existing
plain_l4_endpoint_does_not_compete_with_l7_endpoint_metadata guarantee —
an IP-scoped selector could never qualify as plain L4 because its
destination selector is the allowed_ips list itself.

Hostless-vs-hostless pairs still compete (both match the same raw-IP
connection class), so differing allowed_ips between two hostless
endpoints remains ambiguous.

This unblocks the common red-team scope shape of named targets plus CIDR
ranges in one policy, which the current check rejects even though the
runtime match sets are disjoint.

Signed-off-by: PurpleCHOIms <31537001+PurpleCHOIms@users.noreply.github.com>
…ions

A hostless allowed_ips endpoint is a destination selector: it authorizes
connections addressed by IP literal in its ranges. Under the previous
matcher it also admitted every DNS-mediated hostname on its ports and
constrained their pinned resolutions to its ranges, which made a policy
mixing named endpoints with CIDR scopes deny every named host that
resolves outside the range — the mixed-scope shape the ambiguity
validator change (previous commit) was meant to unlock.

The matcher now requires the destination to be an IP literal (dotted
quad or anything carrying a colon); DNS-mediated names are governed by
named endpoints, consistent with the named-vs-hostless class separation.
The in-range check still happens in Rust after the pin. Tests updated:
raw-IP admission, named rejection, multi-port on raw-IP.

Signed-off-by: PurpleCHOIms <31537001+PurpleCHOIms@users.noreply.github.com>
…data matching

endpoint_matches_request carried its own hostless matcher (port-only),
so the metadata/allowed_ips extraction path still attached a CIDR
endpoint's ranges to every DNS-mediated hostname sharing its ports. The
policy-DNS pin validation then intersected the named endpoint's
resolutions with the CIDR and produced an empty allowlist — mixed
named+CIDR policies denied every named host resolving outside the range
(live: example.com transparent_tcp_destination_denied). The hostless
branch now requires a raw-IP destination, mirroring the admission
matcher and the validator class separation.

Signed-off-by: PurpleCHOIms <31537001+PurpleCHOIms@users.noreply.github.com>
@copy-pr-bot

copy-pr-bot Bot commented Nov 3, 2026

Copy link
Copy Markdown

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.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Thank you for your submission! We ask that you sign our Developer Certificate of Origin before we can accept your contribution. You can sign the DCO by adding a comment below using this text:


I have read the DCO document and I hereby sign the DCO.


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the DCO Assistant Lite bot.

@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown

Thank you for your interest in contributing to OpenShell, @PurpleCHOIms.

This project uses a vouch system for first-time contributors. Before submitting a pull request, you need to be vouched by a maintainer.

To get vouched:

  1. Open a Vouch Request discussion.
  2. Describe what you want to change and why.
  3. Write in your own words — do not have an AI generate the request.
  4. A maintainer will comment /vouch if approved.
  5. Once vouched, open a new PR (preferred) or reopen this one after a few minutes.

See CONTRIBUTING.md for details.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T08:21:05.065206Z 82b6260 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot closed this Nov 3, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82b6260a1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +96 to +98
if left.endpoint.host.is_empty() != right.endpoint.host.is_empty() {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve ambiguity checks for IP-literal host selectors

For non-TCP proxy endpoints, a nonempty host can still be an IP literal, so an endpoint such as host: 10.10.0.5 is not disjoint from a hostless endpoint on the same port. Both the exact-host and hostless branches of endpoint_matches_request match that raw-IP request, but this new shortcut permits them to disagree on tls, allowed_ips, and L7 metadata. The proxy's query_tls_mode and query_allowed_ips still take the first matching config, so a previously rejected ambiguous policy can now activate and apply the wrong TLS mode or deny an explicitly allowed IP using the other endpoint's CIDR. Preserve the overlap checks for host selectors that can match IP literals.

Useful? React with 👍 / 👎.

object.get(endpoint, "host", "") == ""
count(object.get(endpoint, "allowed_ips", [])) > 0
endpoint.ports[_] == network.port
raw_ip_destination(network.host)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update hostless-policy guidance for raw-IP-only matching

For DNS-named private services, the installable policy-generation skill still recommends an allowed_ips-only endpoint because it permits “any domain on the port” (skills/generate-sandbox-policy/SKILL.md:359), and skills/generate-sandbox-policy/examples.md:646-668 generates exactly that policy. This new guard makes those policies deny hostname requests even when their resolved addresses fall within the CIDR, so following the shipped skill now produces non-working access policies. Update the skill and example, and document the raw-IP-only limitation in the published policy documentation, alongside this behavior change; the AGENTS.md maintenance guidance requires reviewing the affected skills.

AGENTS.md reference: AGENTS.md:L225-L225

Useful? React with 👍 / 👎.

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.

Policy validation rejects mixed named + hostless (CIDR) endpoint scopes even though their destination classes are disjoint

1 participant