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

122 lines
5.0 KiB
Markdown

---
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*