Files
kyleandClaude Opus 5.5 cfde35397a P5a plan: PDF documents — seven Ornith tasks with acceptance tests first
docs/plans/p5a: 01 kind+fetch limits, 02 pdftext child + runner, 03 dcache PDF tables,
04 pdfpool, 05 pdfdoc render (map/pages/grep), 06 api, 07 digest citations, metrics, CLI,
skill, unit, README. Given tests compile against a panic-only skeleton and fail there for the
intended reasons; PDFs for tests are built by internal/pdftest (given), plus two qpdf-encrypted
fixtures.

Owner changes for the run: vendor go-pdfkit/extract v0.3.0 + reader v0.6.0 (pinned via
tools.go until task 02); AGENTS.md, tools/run-plan.sh (enforces a Co-Authored-By trailer for
the model), docs/implementer-log.md, `make gate`; gofmt (whitespace) on clean.go and guard;
TestUnsupportedType uses a zip now that PDFs are supported; requestDeadline 90→210 s and
writeTimeout 120→240 s for the PDF path's worst case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:47:38 -07:00

65 lines
2.3 KiB
Go

package guard
import "net/netip"
// IANA special-purpose registries (IPv4 and IPv6). Families that can embed
// IPv4 (6to4, Teredo, well-known NAT64, local-use NAT64) are blocked outright
// rather than decoded. IsGlobalUnicast remains a final gate in CheckAddr.
//
// Sources:
//
// https://www.iana.org/assignments/iana-ipv4-special-registry
// https://www.iana.org/assignments/iana-ipv6-special-registry
var ianaDeny = []struct {
prefix netip.Prefix
reason string
}{
// IPv4
{p("0.0.0.0/8"), "unspecified address"},
{p("10.0.0.0/8"), "private address"},
{p("100.64.0.0/10"), "tailnet address"},
{p("127.0.0.0/8"), "loopback address"},
{p("169.254.0.0/16"), "link-local address"},
{p("172.16.0.0/12"), "private address"},
{p("192.0.0.0/24"), "special-purpose address"},
{p("192.0.2.0/24"), "special-purpose address"},
{p("192.31.196.0/24"), "special-purpose address"},
{p("192.52.193.0/24"), "special-purpose address"},
{p("192.88.99.0/24"), "special-purpose address"},
{p("192.168.0.0/16"), "private address"},
{p("192.175.48.0/24"), "special-purpose address"},
{p("198.18.0.0/15"), "special-purpose address"},
{p("198.51.100.0/24"), "special-purpose address"},
{p("203.0.113.0/24"), "special-purpose address"},
{p("224.0.0.0/4"), "multicast address"},
{p("240.0.0.0/4"), "special-purpose address"},
{p("255.255.255.255/32"), "special-purpose address"},
// IPv6
{p("::/128"), "unspecified address"},
{p("::1/128"), "loopback address"},
{p("64:ff9b::/96"), "NAT64 prefix"},
{p("64:ff9b:1::/48"), "NAT64 prefix"},
{p("100::/64"), "special-purpose address"},
{p("2001::/23"), "special-purpose address"}, // IETF protocol: Teredo, ORCHID, ORCHIDv2, …
{p("2001:db8::/32"), "special-purpose address"},
{p("2002::/16"), "special-purpose address"}, // 6to4; do not decode embedded v4
{p("3fff::/20"), "special-purpose address"}, // documentation (RFC 9637)
{p("fc00::/7"), "private address"},
{p("fe80::/10"), "link-local address"},
{p("fec0::/10"), "special-purpose address"}, // deprecated site-local
{p("ff00::/8"), "multicast address"},
{p("fd7a:115c:a1e0::/48"), "tailnet address"},
}
func p(s string) netip.Prefix { return netip.MustParsePrefix(s) }
func checkIANA(addr netip.Addr) error {
for _, e := range ianaDeny {
if e.prefix.Contains(addr) {
return blockedAddr(e.reason, addr)
}
}
return nil
}