fix: allow NAT64-wrapped public IPs through SSRF filter#3675
Merged
Conversation
DNS64/NAT64 networks synthesize IPv6 answers that embed a public IPv4 in the RFC 6052 well-known prefix 64:ff9b::/96. The SSRF classifier rejected these because the prefix is is_reserved, breaking all outbound metadata and cover fetching on NAT64-only deployments. is_forbidden_ip now unwraps a NAT64-wrapped address and evaluates the embedded IPv4 instead, so a wrapped public IPv4 is allowed while a wrapped private/internal IPv4 (e.g. 127.0.0.1, 10.0.0.1) stays blocked. Fixes #3668 Generated-By: PostHog Code Task-Id: 73b38615-966e-4955-9228-2cb6bbcda277
Contributor
There was a problem hiding this comment.
Pull request overview
Updates RomM’s SSRF IP classifier to correctly handle DNS64/NAT64 environments by unwrapping RFC 6052 well-known NAT64 IPv6 addresses (64:ff9b::/96) and applying SSRF rules to the embedded IPv4, preventing false blocks of legitimate public destinations while still blocking wrapped internal/private targets.
Changes:
- Add NAT64 well-known prefix detection and embedded-IPv4 extraction to
is_forbidden_ip. - Extend SSRF unit tests to cover NAT64-wrapped public vs. private/loopback/link-local behavior.
- Add async-backend regression tests to ensure NAT64-wrapped public addresses connect and NAT64-wrapped private addresses are rejected.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| backend/utils/ssrf.py | Unwraps RFC 6052 well-known NAT64 IPv6 addresses and evaluates SSRF forbidden-ness based on the embedded IPv4. |
| backend/tests/utils/test_ssrf.py | Adds unit + async-backend regression coverage for NAT64-wrapped addresses (public allowed, private/loopback still blocked). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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.
Summary
Fixes #3668. On DNS64/NAT64 networks (e.g. OPNsense + Tayga), the resolver synthesizes IPv6 answers by embedding a public IPv4 inside the RFC 6052 well-known NAT64 prefix
64:ff9b::/96. For exampleneoclone.screenscraper.frresolves to64:ff9b::8d5f:accd, which wraps the public IPv4141.95.172.205.RomM's SSRF classifier
is_forbidden_iprejected these because the well-known NAT64 range is classified asis_reserved(andnot is_globalon some Python versions), producing:This broke all outbound metadata and cover fetching on any NAT64-only deployment.
Change
is_forbidden_ipnow detects a NAT64-wrapped address, extracts the embedded IPv4, and runs the forbidden check against that IPv4:64:ff9b::8d5f:accd→141.95.172.205) is now allowed.64:ff9b::7f00:1→127.0.0.1,64:ff9b::a00:1→10.0.0.1, cloud metadata, etc.) is still blocked.Because
is_forbidden_ipis the shared classifier used by both the DNS-resolution backends and the syntactic URL validator, this single change covers every code path. The RFC 8215 local-use prefix64:ff9b:1::/48is marked private by Python, so it is not unwrapped and remains blocked.Scope: only the RFC 6052 well-known prefix
64:ff9b::/96is unwrapped (the default used by the reporter and the vast majority of NAT64/DNS64 setups). No new configuration.Tests
TestIsForbiddenIpcases for NAT64-wrapped public (allowed) and private/loopback/link-local (blocked) addresses, plus an RFC 8215 sanity case.64:ff9b::8d5f:accdconnects to the pinned NAT64 address; one resolving to64:ff9b::7f00:1still raisesConnectError.uv run pytest tests/utils/test_ssrf.py→ 62 passed.trunk fmt+trunk checkclean. No API surface change, so no OpenAPI/type regen needed.AI assistance disclosure
This change was written with AI assistance (PostHog Code / Claude). The root-cause analysis, code changes, tests, and this description were AI-generated and reviewed before submission.
Created with PostHog Code