Repository navigation
Add pkg/ssrf shared egress guard; use it for WithNoLocalIP and analyzer clients - #5288
Merged
Merged
Conversation
Centralizes SSRF egress protection so detectors and downstream consumers (the thog analyzer guard) share one classification and one enforcement mechanism. pkg/ssrf provides ErrEgressBlocked, IsNonPublicIP, CheckDialAddress, and GuardDialer. The blocked set is broader than the old isLocalIP: it adds CGNAT (incl. Alibaba metadata), class E, test-nets and other special-use ranges, all multicast, and IPv6 embeddings of internal v4 targets (NAT64, 6to4, IPv4-compatible) that To4() does not normalize. WithNoLocalIP now installs a dialer whose Control hook checks the resolved address immediately before connect. The previous implementation vetted a LookupIP result and then dialed the hostname again, which a DNS-rebinding resolver could race; Control runs on the address actually being dialed, and re-runs on every redirect hop. ErrNoLocalIP remains as a deprecated alias of ssrf.ErrEgressBlocked so errors.Is call sites keep working.
Classification fixes in pkg/ssrf, each pinned by a test: - Block fec0::/10 (deprecated site-local, still internal scope on legacy gear), SIIT IPv4-translated ::ffff:0:0:0/96, and Teredo 2001::/32, which embed v4 targets that To4() does not normalize. - Stop blanket-blocking the NAT64 well-known prefix 64:ff9b::/96. DNS64 resolvers synthesize it for every public IPv4-only endpoint, so blocking the whole prefix broke all guarded dials in IPv6-only networks. IsNonPublicIP now extracts the embedded v4 from the low 32 bits and classifies that instead, so 64:ff9b::a00:1 (10.0.0.1) stays blocked while 64:ff9b::808:808 (8.8.8.8) is reachable. The RFC8215 local-use prefix stays blanket-blocked since its embedding layout is operator-defined. Analyzer client coverage: analyzers take their endpoints from scanned content (a secret's domain, a connection string), but the clients built in pkg/analyzer/analyzers used bare http.DefaultTransport. They now build on a guarded transport behind an opt-in SetEgressRestriction toggle, default off so the CLI and self-hosted behavior is unchanged. This covers NewAnalyzeClient, NewAnalyzeClientUnrestricted, the rate-limiter fallback transport, and HttpStatusTest.RunTest's bare client.
Corpora Test ResultsNo detector regex or keyword changes in this PR. Bench skipped. |
martinlocklear
approved these changes
Sep 30, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Description
Centralizes SSRF egress protection into a new
pkg/ssrfpackage so detectors and downstream consumers (including the thog analyzer guard, see trufflesecurity/thog#7229) share one classification policy and one enforcement mechanism.pkg/ssrf
ErrEgressBlockedsentinel,IsNonPublicIP,CheckDialAddress, andGuardDialer.isLocalIP: adds CGNAT 100.64.0.0/10 (incl. Alibaba metadata), class E, test-nets and other special-use ranges, all multicast, deprecated IPv6 site-local fec0::/10, and IPv6 embeddings of internal v4 targets thatTo4()does not normalize (IPv4-compatible ::/96, SIIT, 6to4, Teredo, NAT64 local-use).WithNoLocalIP
Now installs the guard as a dialer
ControlContexthook that checks the resolved address immediately before connect. The previous implementation vetted aLookupIPresult and then dialed the hostname again, which a DNS-rebinding resolver could race; the hook runs on the address actually being dialed and re-runs on every redirect hop.ErrNoLocalIPremains as a deprecated alias ofssrf.ErrEgressBlockedsoerrors.Iscall sites keep working.Behavior changes to be aware of (documented in the godoc):
DialContextalready set on the transport is replaced, not chained.Analyzer clients
Analyzers take their endpoints from scanned content, but the clients built in
pkg/analyzer/analyzersused barehttp.DefaultTransport. They now build on a guarded transport behind an opt-inSetEgressRestrictiontoggle, default off, so CLI and self-hosted behavior is unchanged. CoversNewAnalyzeClient,NewAnalyzeClientUnrestricted, the rate-limiter fallback, andHttpStatusTest.RunTest.Checklist:
go test ./pkg/ssrf ./pkg/analyzer/analyzers ./pkg/detectors); full suite left to CImake lint)? (golangci-lint not installed locally; left to CI)Note
High Risk
Changes security-sensitive outbound networking for secret verification and optional analyzer egress; broadened blocking and per-IP semantics can break legitimate checks against CGNAT/private endpoints when guards are enabled.
Overview
Introduces
pkg/ssrfas the shared policy for blocking outbound dials to non-public addresses when destinations come from untrusted scan content. Enforcement runs in the dialerControlContexton the resolved IP immediately before connect (DNS-rebinding and redirect-safe), with a broader blocked set than the oldisLocalIPlogic (CGNAT, metadata-adjacent ranges, multicast, IPv6 smuggling forms, etc.) and special handling so NAT6464:ff9b::/96classifies the embedded IPv4 instead of blanket-blocking DNS64-synthesized public targets.WithNoLocalIPin detectors now wrapsssrf.GuardDialerinstead of pre-resolving withLookupIP;ErrNoLocalIPremains as a deprecated alias ofssrf.ErrEgressBlocked. Documented behavior shifts: per-address blocking (mixed A records can still connect on a public IP) and replacement rather than chaining of customDialContext.Analyzer HTTP clients (
NewAnalyzeClient, unrestricted variant, rate-limiter fallback,HttpStatusTest.RunTest) build onbaseTransport(), which applies the guarded transport only whenSetEgressRestriction(true)is called—default off so OSS/self-hosted internal verification is unchanged.Reviewed by Cursor Bugbot for commit 1923347. Bugbot is set up for automated code reviews on this repo. Configure here.