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
122 lines
5.0 KiB
Markdown
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*
|