diff --git a/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md b/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md new file mode 100644 index 0000000..8e32eaf --- /dev/null +++ b/.planning/phases/06-http-routing-auth-groups-and-rate-limiting/06-08-SUMMARY.md @@ -0,0 +1,121 @@ +--- +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*