You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Review: SSRF outbound policy for serverless requests
Solid design overall: centralizing the check in a DNS resolver + redirect-policy hook (GuardedResolver / redirect_policy in engine/packages/outbound-guard/src/client.rs) closes the classic TOCTOU/DNS-rebinding gap that a pre-connect-only check would have, and re-validating on every redirect hop is the right call. The config-write-time check in runner_config/upsert.rs plus the connect-time re-check ("re-gates configs stored before this policy existed") is a good belt-and-suspenders approach. Test coverage in outbound-guard/tests/policy.rs is thorough for the address-classification logic (private ranges, embedded-IPv4 forms, deny-over-allow precedence, credentials, scheme).
Potential issue: 0.0.0.0/8 (other than the literal 0.0.0.0) is not blocked
AddressClass::of_ipv4 (engine/packages/outbound-guard/src/policy.rs:558) only special-cases the exact unspecified address via addr.is_unspecified(). Addresses like 0.1.2.3–0.255.255.255 fall through every check and return None, i.e. "globally routable" → allowed by default even with allow_private_networks: false.
This is the exact class of bypass behind the "0.0.0.0 Day" SSRF research (many OS network stacks, including Linux and macOS, route the whole 0.0.0.0/8 block to the local host under certain conditions, not just the single address). Since this crate already goes out of its way to special-case IPv4-mapped/compatible/NAT64 IPv6 forms specifically to prevent smuggling a private destination past the filter, it seems worth closing this one too — e.g. treat octets()[0] == 0 as AddressClass::Unspecified (or a dedicated ThisNetwork class) rather than only the exact-zero address.
Suggestion: no test coverage for client.rs
All the tests exercise Policy directly; nothing exercises GuardedResolver, redirect_policy, or block_reason against a real (or mock) HTTP client/server. In particular, block_reason() depends on reqwest::Error's source() chain actually carrying the boxed BlockReason through from the custom DNS resolver / redirect callback — that plumbing is easy to silently break in a future reqwest upgrade or refactor, and it's exactly the part that determines whether a blocked destination gets correctly reported as ServerlessDestinationBlocked vs. a generic ServerlessConnectionError. A small integration test spinning up a local server that redirects to (or DNS-resolves to) a blocked address would cover the part unit tests on Policy can't.
Minor: Config-parameterized singletons
outbound_policy, guarded_client, and guarded_client_no_timeout in engine/packages/pools/src/reqwest.rs take a &rivet_config::Config argument but cache into process-wide OnceCells, so only the first call's config actually takes effect — later calls with a different Config silently reuse the first policy/client. Not a live bug today since each engine process loads a single config once at startup, but the function signature implies per-call configurability that doesn't exist; worth a doc comment noting the first-call-wins behavior, or asserting on it, so it doesn't bite a future caller (e.g. a test harness spinning up multiple in-process configs).
Out of scope? Unrelated files in this diff
The diff (as shown by gh pr diff) also includes examples/kitchen-sink/scripts/{large-commit-suite,residue-stats,truncate-residue}.ts, examples/kitchen-sink/src/actors/testing/large-commit-db.ts, a RIVET_OTEL_SAMPLER_RATIO removal in self-host/compose/dev-multidc-multinode/docker-compose.yml, and an outputDir path fix in self-host/compose/template/src/main.ts. None of these look related to the SSRF policy feature — worth double-checking these are intentional includes and not base-branch drift before merging.
Nits
engine/packages/config/src/config/outbound.rs: consistent with existing Root accessor patterns (LazyLock default), good.
Docs (troubleshooting.mdx, debugging.mdx) are updated in the same change, matches the docs-sync expectations in CLAUDE.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.