fix(policy): classify advisory private-IP notes with the canonical is_internal_ip (#1777)#1824
Conversation
PR Review StatusValidation: this PR is project-valid because it directly addresses validated issue #1777 with a small, concentrated fix in the policy advisory note paths. Review findings:
Docs: not needed; this corrects advisory text accuracy and does not change a CLI/API/workflow surface or documented configuration. Checks: DCO, vouch-gate, Branch Checks, Helm Lint, Rust, Python, license, and markdown are passing for this head. No Next state: |
|
I would suggest to also modify Ipv4Addr in core::net - Rust RFC 1918 (private ips) missing function from the stable standard library that would need to be implemented manually in Ipv4Addr in core::net - Rust RFC 2544 (benchmarking) |
Re-check After Maintainer UpdateI re-evaluated latest head Disposition: not resolved yet. There have been no author commits after the prior gator review, and the independent reviewer confirmed the implementation direction is correct but the review feedback still needs an author update or maintainer waiver. Remaining items:
Checks remain green for this head. No Next state: |
…_internal_ip (NVIDIA#1777) generate_security_notes() flagged a destination as internal/private using naive string prefixes. host.starts_with("172.") matched all of 172.0.0.0/8 even though only 172.16.0.0/12 is private (RFC 1918), so the advisory flagged public addresses (172.15/172.32) and missed CGNAT (100.64.0.0/10), IPv6 ULA, and the other reserved ranges; it also flagged non-IP hostnames that merely start with 10./172.. The two copies of the check (openshell-sandbox mechanistic_mapper.rs and openshell-server grpc/policy.rs) now parse the host as an IP and defer to openshell-core net::is_internal_ip, the same RFC-accurate, unit-tested classifier the runtime SSRF enforcement path uses, so the proposed-rule advisory matches actual enforcement. Per review, is_internal_v4 now uses the stable Ipv4Addr predicates as far as they go: is_documentation() (RFC 5737) replaces the two manual TEST-NET-2/3 checks and also closes a gap, since 192.0.2.0/24 (TEST-NET-1) was previously unflagged, and is_broadcast() (RFC 919) is added. Manual checks remain only for the ranges without a stable predicate: CGNAT 100.64.0.0/10, IETF protocol assignments 192.0.0.0/24, and benchmarking 198.18.0.0/15. Adds regression coverage: both note suites now assert hostname-prefix false positives (10.example.com, 172.example.com) are not flagged and IPv6 ULA (fd00::1) is flagged; net.rs covers the newly classified TEST-NET-1 and broadcast addresses. Closes NVIDIA#1777 Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
8c0f70e to
eeec4f5
Compare
|
Thanks @alangou, @johntmyers — applied.
Added regression coverage: both note suites now assert hostname-prefix false positives ( Cheers! |
Summary
When OpenShell proposes a network-policy rule it annotates the rule with a security note flagging whether the destination looks like an internal/private address. That check used naive string prefixes - notably
host.starts_with("172."), which matches all of172.0.0.0-172.255.255.255even though only172.16.0.0/12is private. So the advisory flagged public addresses (172.15.x,172.32.x) as internal, missed ranges like CGNAT (100.64.0.0/10) and IPv6 ULA entirely, and even flagged non-IP hostnames that merely start with10./172.. OpenShell already has a canonical, RFC-accurate, unit-tested classifier for exactly this (openshell-corenet::is_internal_ip), and the runtime SSRF enforcement path already uses it. This routes the advisory's two copies ofgenerate_security_notes()through that same classifier, so the proposed-rule note matches what enforcement actually does.Related Issue
Closes #1777
Changes
crates/openshell-sandbox/src/mechanistic_mapper.rsandcrates/openshell-server/src/grpc/policy.rs, replace the duplicatedhost.starts_with("10.") || host.starts_with("172.") || ...block ingenerate_security_notes()withhost.parse::<IpAddr>().is_ok_and(is_internal_ip)(the"localhost"hostname is not an IP literal, so it stays a separate check). This fixes the172.16.0.0/12boundary, adds CGNAT / IPv6 ULA / other reserved coverage, and stops flagging hostnames that merely begin with a private prefix.172.15/172.32are not flagged,172.16and100.64(CGNAT) are, the classic ranges pluslocalhoststill are, and a public IP (8.8.8.8) is not.Testing
Ran the full gate in a container matching CI (rust 1.95.0 per
rust-toolchain.tomlplus the mise toolchain):mise run pre-commitpasses (rust format/lint/check, test:rust, python lint/typecheck, helm, markdown, license).Checklist