harden: add security headers and protect the geocode proxy#87
Open
Yali444 wants to merge 1 commit into
Open
Conversation
Two production-hardening changes; no behavioral change to the UI. next.config.ts: add baseline security headers to every route (X-Content-Type-Options, Referrer-Policy, X-Frame-Options, HSTS, Permissions-Policy) plus a Content-Security-Policy in Report-Only mode. CSP ships Report-Only first because the app relies on inline styles/scripts (framer-motion, next-themes, analytics, Disqus) and several external origins; this lets us validate the policy before enforcing it. The Supabase origin in connect-src is derived from NEXT_PUBLIC_SUPABASE_URL. geolocation stays enabled since the app uses it. src/app/api/geocode: the proxy used cache:"no-store" with no rate limit or input bounds, making it an open proxy that hammered Nominatim and risked an IP ban (Nominatim allows <=1 req/sec). Now caches upstream results (revalidate 86400 + edge Cache-Control), caps query length, and applies a lightweight per-IP rate limit. Existing response contracts are preserved; tests extended to cover the new paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Wos5Hzyyjw79uzN1w1T42
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two production-hardening changes from a senior-dev review. The codebase is in good shape (near-zero
any, pure tested functions, logic in hooks, thoughtful cache headers), so this is not a cleanup/rewrite — it targets the two things with real risk behind them. No UI behavior changes.1. Security headers (
next.config.ts)The site set cache headers but no security headers at all. Added to every route:
X-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originX-Frame-Options: SAMEORIGINStrict-Transport-Security: max-age=63072000; includeSubDomains; preloadPermissions-Policy: camera=(), microphone=(), payment=(), geolocation=(self)— geolocation kept enabled because the app usesuseGeolocation.Content-Security-Policyshipped as Report-Only first.CSP is Report-Only intentionally: the app relies on inline styles/scripts (framer-motion, next-themes theme script, Vercel Analytics, Disqus) and several external origins (Unsplash, OSM tiles, Supabase). An enforced strict policy would risk white-screening the site, so this lets us validate against real traffic before flipping it on. The Supabase origin in
connect-srcis derived fromNEXT_PUBLIC_SUPABASE_URL, not hardcoded.2. Geocode proxy hardening (
src/app/api/geocode/route.ts)The endpoint proxied OpenStreetMap Nominatim with
cache: "no-store", no rate limit, and no input bounds — effectively an open proxy that re-fetched on every keystroke, violated Nominatim's ≤1 req/sec usage policy (risking an IP/User-Agent ban that breaks address search for everyone), and could be hammered by anyone. Now it:next: { revalidate: 86400 }+ edgeCache-Control: public, s-maxage=86400, stale-while-revalidate) — the biggest win for ToS compliance and latency.q> 200 chars, before fetching).All existing response contracts are preserved (400 on missing/whitespace
q, status propagation on non-OK upstream, 502 on throw,{result:null}shapes). The consumer (useAddressSearch) already surfaces non-OK responses as a benign search error, so no client change was needed.Verification
npm run test— 266 tests pass (added 5 covering the new geocode paths: length cap,Cache-Controlheader, per-IP rate limit + 429, per-IP isolation)npm run lint— cleannpm run build— succeedsnext start+ curl that all six headers emit on/and the global headers reach/api/*Follow-ups (not in this PR)
script-srcto a nonce instead of'unsafe-inline'.🤖 Generated with Claude Code
https://claude.ai/code/session_012Wos5Hzyyjw79uzN1w1T42
Generated by Claude Code