Skip to content

fix: allow NAT64-wrapped public IPs through SSRF filter#3675

Merged
gantoine merged 1 commit into
masterfrom
posthog-code/fix-ssrf-dns64-nat64
Jul 5, 2026
Merged

fix: allow NAT64-wrapped public IPs through SSRF filter#3675
gantoine merged 1 commit into
masterfrom
posthog-code/fix-ssrf-dns64-nat64

Conversation

@gantoine

@gantoine gantoine commented Jul 5, 2026

Copy link
Copy Markdown
Member

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 example neoclone.screenscraper.fr resolves to 64:ff9b::8d5f:accd, which wraps the public IPv4 141.95.172.205.

RomM's SSRF classifier is_forbidden_ip rejected these because the well-known NAT64 range is classified as is_reserved (and not is_global on some Python versions), producing:

SSRF prevention: hostname 'neoclone.screenscraper.fr' resolves to forbidden IP 64:ff9b::8d5f:accd
Unable to fetch cover at https://neoclone.screenscraper.fr/...

This broke all outbound metadata and cover fetching on any NAT64-only deployment.

Change

is_forbidden_ip now detects a NAT64-wrapped address, extracts the embedded IPv4, and runs the forbidden check against that IPv4:

  • A wrapped public IPv4 (64:ff9b::8d5f:accd141.95.172.205) is now allowed.
  • A wrapped private/internal IPv4 (64:ff9b::7f00:1127.0.0.1, 64:ff9b::a00:110.0.0.1, cloud metadata, etc.) is still blocked.

Because is_forbidden_ip is 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 prefix 64:ff9b:1::/48 is marked private by Python, so it is not unwrapped and remains blocked.

Scope: only the RFC 6052 well-known prefix 64:ff9b::/96 is unwrapped (the default used by the reporter and the vast majority of NAT64/DNS64 setups). No new configuration.

Tests

  • New TestIsForbiddenIp cases for NAT64-wrapped public (allowed) and private/loopback/link-local (blocked) addresses, plus an RFC 8215 sanity case.
  • New async-backend regression tests: a hostname resolving to 64:ff9b::8d5f:accd connects to the pinned NAT64 address; one resolving to 64:ff9b::7f00:1 still raises ConnectError.

uv run pytest tests/utils/test_ssrf.py → 62 passed. trunk fmt + trunk check clean. 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

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
Copilot AI review requested due to automatic review settings July 5, 2026 02:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gantoine gantoine merged commit fcdb071 into master Jul 5, 2026
11 checks passed
@gantoine gantoine deleted the posthog-code/fix-ssrf-dns64-nat64 branch July 5, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SSRF blocks DNS64

2 participants