Skip to content

feat(sandbox): allow egress to private IP space via allowed_ips policy field - #60

Merged
johntmyers merged 5 commits into
mainfrom
feat/59-allow-private-ip-policy
Mar 3, 2026
Merged

johntmyers merged 5 commits into
mainfrom
feat/59-allow-private-ip-policy

Conversation

@johntmyers

Copy link
Copy Markdown
Collaborator

Summary

Closes #59

Adds an allowed_ips field to NetworkEndpoint that accepts CIDR notation and exact IPs, enabling sandboxes to reach private IP space under policy control while maintaining SSRF protections.

  • Proto: Added repeated string allowed_ips = 8 to NetworkEndpoint in sandbox.proto
  • Proxy: New is_always_blocked_ip(), resolve_and_check_allowed_ips(), parse_allowed_ips() functions; modified SSRF check in handle_tcp_connection() to branch on allowed_ips presence
  • OPA/Rego: Added hostless endpoint_allowed clause, refactored matched_endpoint_config with endpoint_matches_request and endpoint_has_extended_config helpers to flow allowed_ips through to the proxy
  • YAML policy parsing: Added allowed_ips field to NetworkEndpointDef in navigator-policy crate, made host optional for hostless endpoints
  • Tests: 23 new unit tests (proxy + OPA), 4 new E2E tests (SSRF-4 through SSRF-7)
  • Docs: Updated architecture/security-policy.md with three-mode behavior, updated generate-sandbox-policy skill with private IP examples

Three Modes

Mode Configuration Behavior
Default host only, no allowed_ips Standard SSRF: all private IPs blocked
Host + allowlist host + allowed_ips Domain must resolve to an IP in allowlist
Hostless allowlist allowed_ips only (no host) Any domain allowed if resolved IP matches

Loopback (127.0.0.0/8) and link-local (169.254.0.0/16) are always blocked regardless of allowed_ips.

Test Plan

  • 23 new Rust unit tests covering is_always_blocked_ip, parse_allowed_ips, resolve_and_check_allowed_ips, OPA allowed_ips query across all 3 modes, and proto round-trip
  • 4 new Python E2E tests (SSRF-4 through SSRF-7) covering host+allowlist, hostless allowlist, always-blocked loopback, and blocked-out-of-range scenarios
  • All 160+ existing tests continue to pass
  • mise run pre-commit passes (format, lint, test, python)

…y field

Add an allowed_ips field to NetworkEndpoint that accepts CIDR notation and
exact IPs, enabling sandboxes to reach private IP space under policy control
while maintaining SSRF protections for loopback and link-local addresses.

Three modes are supported:
- Default (no allowed_ips): all private IPs blocked (existing behavior)
- Host + allowlist: domain must resolve to an IP in allowed_ips
- Hostless allowlist (no host): any domain allowed if resolved IP matches

Closes #59
Adds a new skill that investigates a plain-language problem description
by deeply exploring the codebase, then creates a structured GitHub issue
with technical findings. Serves as a prequel to build-from-issue —
maps vague ideas to concrete, buildable issues.
…E2E verification

- Add explicit guardrail: agent-ready label must only be applied by humans
- Add Phase 2 E2E verification: cluster:deploy + test:e2e:sandbox with
  retry loop, triggered when e2e/ files are modified
- Add E2E attestation comment on PRs since CI does not yet run E2E tests
@johntmyers johntmyers added the test:e2e Requires end-to-end coverage label Mar 2, 2026
@johntmyers

Copy link
Copy Markdown
Collaborator Author

E2E Test Attestation

Commit: 111a9fb41d69e80f4bfc725cd5d6096acc0cac68
Date: 2026-03-02 23:09:50 UTC
Result: 41 passed, 0 failed

Test Summary

All 41 E2E tests pass, including:

  • 4 new allowed_ips SSRF tests (SSRF-4 through SSRF-7)
  • All existing L4, L7, SSRF, inference routing, provider, and mTLS tests

Full Test List

Test Status
test_route_refresh_picks_up_route_created_after_sandbox_start PASS
test_inference_call_routed_to_backend PASS
test_non_inference_request_denied PASS
test_inference_anthropic_messages_protocol PASS
test_inference_route_filtering_by_allowed_routes PASS
test_sandbox_api_crud_and_exec PASS
test_exec_python_accepts_bound_methods PASS
test_exec_python_surfaces_callable_errors PASS
test_policy_applies_to_exec_commands PASS
test_policy_blocks_unauthorized_proxy_connect PASS
test_l4_no_policy_denies_all PASS
test_l4_wildcard_binary_allows_any_binary PASS
test_l4_binary_restricted_denies_wrong_binary PASS
test_l4_wrong_port_denied PASS
test_l4_cross_policy_denied PASS
test_l4_non_connect_method_rejected PASS
test_l4_log_fields PASS
test_ssrf_blocks_loopback_despite_policy_allow PASS
test_ssrf_blocks_metadata_endpoint_despite_policy_allow PASS
test_ssrf_log_shows_internal_address_block PASS
test_ssrf_allowed_ips_permits_private_ip PASS
test_ssrf_allowed_ips_hostless_permits_private_ip PASS
test_ssrf_private_ip_blocked_without_allowed_ips PASS
test_ssrf_loopback_blocked_even_with_allowed_ips PASS
test_l7_tls_full_access_allows_all PASS
test_l7_tls_read_only_denies_post PASS
test_l7_tls_audit_mode_allows_but_logs PASS
test_l7_tls_explicit_path_rules PASS
test_l7_tls_ca_trust_store_injected PASS
test_l7_tls_deny_response_format PASS
test_l7_tls_log_fields PASS
test_live_policy_update_and_logs PASS
test_provider_credentials_available_as_env_vars PASS
test_generic_provider_credentials_available_as_env_vars PASS
test_nvidia_provider_injects_nvidia_api_key_env_var PASS
test_create_sandbox_rejects_unknown_provider PASS
test_credentials_not_in_persisted_spec_environment PASS
test_authenticated_client_succeeds PASS
test_no_client_cert_rejected PASS
test_wrong_client_cert_rejected PASS
test_plaintext_connection_rejected PASS

Notes

Initial E2E failures were caused by k3s containerd caching a stale sandbox image (imagePullPolicy: IfNotPresent). After clearing the containerd image cache, all tests pass cleanly.

…etch

Render allowed_ips CIDR entries under each network endpoint, show
hostless endpoints as *:port, annotate rules with 'private IP' when
allowed_ips is present, and switch fetch_sandbox_detail to use the
GetSandboxPolicy RPC so the TUI shows the current versioned policy
instead of the immutable creation-time snapshot.

Also add private-ip-routing example and cluster_pods dev policy entry
for demonstrating private IP egress from sandboxes.
@johntmyers
johntmyers merged commit 9ffc51f into main Mar 3, 2026
8 of 10 checks passed
@johntmyers
johntmyers deleted the feat/59-allow-private-ip-policy branch March 3, 2026 03:29
drew pushed a commit that referenced this pull request Mar 16, 2026
…y field (#60)

* feat(sandbox): allow egress to private IP space via allowed_ips policy field

Add an allowed_ips field to NetworkEndpoint that accepts CIDR notation and
exact IPs, enabling sandboxes to reach private IP space under policy control
while maintaining SSRF protections for loopback and link-local addresses.

Three modes are supported:
- Default (no allowed_ips): all private IPs blocked (existing behavior)
- Host + allowlist: domain must resolve to an IP in allowed_ips
- Hostless allowlist (no host): any domain allowed if resolved IP matches



Co-authored-by: John Myers <[email protected]>
Bobbins228 pushed a commit to Bobbins228/OpenShell that referenced this pull request Sep 30, 2026
Signed-off-by: red-hat-konflux <126015336+red-hat-konflux[bot]@users.noreply.github.com>
Co-authored-by: red-hat-konflux[bot] <126015336+red-hat-konflux[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Allow sandbox egress to private IP space via networking policy

1 participant