fix(spam-detection): stop truncating IPv4-mapped IPv6 addresses #149
No reviewers
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
javascript
question
severity: high
severity: low
severity: medium
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
isyuricunha/website!149
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/client-ip-ipv6-mapped-truncation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Root cause
getClientIp()'s port-stripping heuristic inapps/web/src/lib/spam-detection.tschecked onlyfirst.includes('.') && first.includes(':')to decide whether a header value was an IPv4 address with a trailing port (e.g.198.51.100.1:1234).That shape also matches two other real-world address forms that contain both a
.and a:without carrying a port at all:::ffff:192.168.1.164:ff9b::203.0.113.5Reproduction (against
origin/main)Impact
getClientIp()feeds the per-IP rate limiter (checkRateLimit) used across 10+ routes:apps/web/src/app/api/contact/route.tsapps/web/src/app/api/newsletter/subscribe/route.tsandunsubscribe/route.tsapps/web/src/app/api/ai/chat/route.tsandfeedback/route.tsapps/web/src/app/api/ai/content/route.tsapps/web/src/app/api/admin/translate/route.tsapps/web/src/app/api/admin/posts/route.ts(3 call sites)A request from a client behind either address shape gets normalized to
'unknown'or a garbage key ('64') instead of its real address, so the per-IP rate limit is shared across all such clients (or silently ineffective) instead of applying per-address.Fix
Only strip a trailing
:<port>when the substring before the last colon matches a dotted-quad IPv4 address (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) and the substring after it is numeric. IPv6 addresses — mapped or not — now pass through unchanged, since they never satisfy the dotted-quad check on the left side.Tests
3 regression tests added to
apps/web/src/tests/lib/spam-detection.test.ts:::ffff:192.168.1.1stays intact (previously became'unknown')64:ff9b::203.0.113.5stays intact (previously became'64')203.0.113.10:8443still has its port stripped correctly (no regression on the original behavior)All three verified to fail against
origin/main'sspam-detection.tsand pass against this branch.Validation
pnpm vitest run apps/web/src/tests/lib/spam-detection.test.ts— 8/8 passedpnpm test:unit(full suite) — 185/185 passed (182 pre-existing + 3 new)pnpm turbo type-check --filter=@isyuricunha/web— cleanpnpm turbo lint --filter=@isyuricunha/web— clean (--max-warnings 0)Note
The repo's
lint-staged/husky pre-commit hook auto-fixed one unrelated line in the same file during commit (checkRateLimit's cleanup loop:Array.from(...).forEach(...)→for...of) — same behavior, style-only, not something I authored intentionally. Flagging it here for transparency since it's outside the diff I wrote by hand.The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment