Files
summercms/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md
Jakub Zych ac24ddd518 docs(06-08): complete transition-address SSRF closure plan
Tasks completed: 1/1
- Decode and reclassify embedded IPv4 at the dial-time SSRF boundary

SUMMARY: .planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md
2026-09-20 20:58:54 +02:00

5.0 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
06-http-routing-auth-groups-and-rate-limiting 08 security
ssrf
nat64
6to4
rfc6052
rfc3056
netip
phase provides
06-http-routing-auth-groups-and-rate-limiting dial-time fetchguard classification and the private/reserved IPv4 policy from plan 06-04
NAT64 well-known and local-use embedded IPv4 classification
6to4 embedded IPv4 classification
Dial-control regressions for transition-address SSRF rejection
phase-12-manual-cover-url
phase-14-discogs-cover-import
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
created modified
fetchguard/ip.go
fetchguard/ip_test.go
fetchguard/fetch_test.go
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
Transition extraction recognizes only 64:ff9b::/96, 64:ff9b:1::/48, and 2002::/16; public embedded IPv4 remains allowed
HTTP-07
1h 20m 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