--- phase: 06-http-routing-auth-groups-and-rate-limiting plan: 08 subsystem: security tags: [ssrf, nat64, 6to4, rfc6052, rfc3056, netip] requires: - phase: 06-http-routing-auth-groups-and-rate-limiting provides: dial-time fetchguard classification and the private/reserved IPv4 policy from plan 06-04 provides: - NAT64 well-known and local-use embedded IPv4 classification - 6to4 embedded IPv4 classification - Dial-control regressions for transition-address SSRF rejection affects: - phase-12-manual-cover-url - phase-14-discogs-cover-import tech-stack: added: [] patterns: - Transition IPv6 formats are decoded into netip.AddrFrom4 and reclassified through the ordinary IPv4 policy - Recognized malformed RFC 6052 local-use addresses fail closed key-files: created: [] modified: - fetchguard/ip.go - fetchguard/ip_test.go - fetchguard/fetch_test.go key-decisions: - "isReservedOrPrivate owns Addr.Unmap and recursively applies the ordinary IPv4 table to supported transition embeddings so direct and dial-time callers share one policy" - "A 64:ff9b:1::/48 address with a non-zero RFC 6052 u octet is recognized as malformed and rejected rather than treated as public native IPv6" patterns-established: - "Transition extraction recognizes only 64:ff9b::/96, 64:ff9b:1::/48, and 2002::/16; public embedded IPv4 remains allowed" requirements-completed: [HTTP-07] duration: 1h 20m completed: 2026-09-20 --- # Phase 6 Plan 08: Transition-address SSRF closure Summary **Dial-time NAT64 and 6to4 decoding now routes embedded IPv4 through the existing private/reserved policy while preserving public transition destinations** ## Performance - **Duration:** 1h 20m - **Started:** 2026-09-20T15:13:49Z - **Completed:** 2026-09-20T16:34:04Z - **Tasks:** 1 - **Files modified:** 3 ## Accomplishments - Closed the transition-address SSRF bypass for loopback, RFC1918, and link-local metadata IPv4 embedded in NAT64 well-known, NAT64 local-use, and 6to4 addresses. - Preserved public routing semantics by proving an embedded `8.8.8.8` remains public in all three supported formats. - Exercised the production `dialControl` boundary for every unsafe transition category and verified errors map to `ReasonPrivateIP` before a connection is attempted. - Moved IPv4-mapped normalization into the classifier so callers cannot accidentally bypass the IPv4 policy by omitting `Addr.Unmap`. ## Task Commits The TDD task was committed atomically as RED then GREEN: 1. **Task 1 RED: Transition-address security regressions** - `1cd76fc` (test) 2. **Task 1 GREEN: Transition-aware IP classification** - `99fa932` (fix) **Plan metadata:** (this commit) docs(06-08): complete transition-address SSRF closure plan ## Files Created/Modified - `fetchguard/ip.go` - Normalizes mapped IPv4, extracts IPv4 from the two NAT64 prefixes and 6to4, and fails closed on a malformed `/48` `u` octet. - `fetchguard/ip_test.go` - Covers loopback, RFC1918, metadata, and public embeddings across all three formats plus malformed local-use NAT64. - `fetchguard/fetch_test.go` - Calls production `dialControl` with unsafe bracketed IPv6 dial addresses and verifies sentinel/reason mapping. ## Decisions Made - Put `Addr.Unmap` inside `isReservedOrPrivate` so helper callers and the dial hook cannot diverge on IPv4-mapped handling. - Reuse the existing IPv4 CIDR table recursively after transition extraction instead of creating a second transition-specific unsafe list. - Treat a non-zero RFC 6052 `u` octet as recognized-but-invalid, which causes the existing invalid-address path to fail closed. ## Deviations from Plan None - plan executed exactly as written. ## Issues Encountered - The sandboxed default Go build cache was read-only; verification used `GOCACHE=/tmp/summercms-go-build` without changing repository configuration. - Repository tests that create local `httptest` listeners required the existing elevated `go test` permission; the full package and repository suites passed once local sockets were allowed. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - HTTP-07's transition-address gap is closed and the fetchguard boundary is ready for its Phase 12 and Phase 14 production callers. - Ready for plan 06-09 to close the partial-write panic recovery gap. ## TDD Gate Compliance - RED: `1cd76fc` added failing helper and dial-control regressions before implementation. - GREEN: `99fa932` added transition extraction and made the regressions pass. - No refactor commit was needed. ## Self-Check: PASSED - FOUND: `fetchguard/ip.go` - FOUND: `fetchguard/ip_test.go` - FOUND: `fetchguard/fetch_test.go` - FOUND: `1cd76fc` - FOUND: `99fa932` - `go test ./fetchguard -run 'Test(IsReservedOrPrivate|.*Transition|.*NAT64|.*6to4)' -count=1 -race -short` passed. - `go vet ./fetchguard` and `go test ./fetchguard -count=1 -race -short` passed. - `go vet ./...` and `go test ./... -short` passed. --- *Phase: 06-http-routing-auth-groups-and-rate-limiting* *Completed: 2026-09-20*