Skip to content

security: rate limit /api/analyze, redact error details, harden external links#9

Merged
Merchantlee99 merged 1 commit into
mainfrom
security/rate-limit-and-link-hardening
Apr 22, 2026
Merged

security: rate limit /api/analyze, redact error details, harden external links#9
Merchantlee99 merged 1 commit into
mainfrom
security/rate-limit-and-link-hardening

Conversation

@Merchantlee99
Copy link
Copy Markdown
Owner

Summary

  • IP-keyed in-memory rate limiter on /api/analyze with two buckets:
    • general: 6 req / 60s per IP
    • forceRefresh: 2 req / 10min per IP (stricter; cache-bypassing path)
  • Request body size capped at 4 KiB; repoUrl capped at 512 chars.
  • Outbound error payload whitelists only user-safe detail fields (retryAfterSeconds, resetAt, authenticated, githubAuthMode) and replaces unknown Error messages with a generic copy to avoid leaking internal paths/stacks.
  • Added HSTS + Cross-Origin-Opener-Policy: same-origin to existing security headers.
  • External links (README links, owner homepage, preview images) now pass through safeExternalHref / safeImageSrc, blocking javascript: / data: schemes before they reach href or src.

Why

  • Current /api/analyze was protected against browser cross-site POST but fully open to server-to-server calls — a single attacker could exhaust our GitHub token quota and inflate Vercel function usage.
  • forceRefresh=true bypasses server cache; it deserves a tighter limit than normal analyze.
  • Error responses were passing raw upstream details (GitHub path, upstream status) back to clients, giving attackers extra recon.
  • README-derived URLs were rendered as <a href> without protocol checks, risking javascript: XSS from a malicious repo README.

Test plan

  • pnpm exec tsc --noEmit
  • pnpm lint
  • pnpm build
  • pnpm test:unit240 tests (26 files + 1 new rate-limit test file, +8 tests)
  • After deploy: hit /api/analyze from local shell 7+ times within a minute → expect a 429 with Retry-After header and details.retryAfterSeconds.
  • Set a GitHub repo README to include a javascript: link, analyze it → expect the link rendered as non-clickable text.

🤖 Generated with Claude Code

…nal links

- Add IP-keyed in-memory rate limiter (lib/analysis/rate-limit.ts)
  - general bucket: 6 req / 60s per IP
  - forceRefresh bucket: 2 req / 10min per IP (stricter for cache-bypassing)
  - both return 429 + Retry-After header + details.retryAfterSeconds
- Wire limiter into /api/analyze after same-origin check
- Cap request body size to 4 KiB and repoUrl length to 512 chars
- Sanitize outbound error payload: whitelist detail fields, replace unknown
  Error messages with a generic copy to avoid leaking internal paths/stacks
- Add HSTS + Cross-Origin-Opener-Policy headers alongside existing ones
- Protocol-check external links (README links, owner homepage, preview images)
  via safeExternalHref / safeImageSrc to block javascript:/data: schemes

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
repo-lens Building Building Preview Apr 22, 2026 0:10am

@Merchantlee99 Merchantlee99 merged commit 521ec3a into main Apr 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant