Skip to content

fix(web): rip @upstash/ratelimit from /api/visit#1

Open
0motionguy wants to merge 1 commit into
mainfrom
fix/visitportal-strip-upstash-2026-05-23
Open

fix(web): rip @upstash/ratelimit from /api/visit#1
0motionguy wants to merge 1 commit into
mainfrom
fix/visitportal-strip-upstash-2026-05-23

Conversation

@0motionguy
Copy link
Copy Markdown
Owner

Summary

  • Removes @upstash/ratelimit + @upstash/redis deps from web/.
  • Deletes web/app/api/visit/rate-limit.ts + its test (kept the SSRF guard; that's the real defence).
  • Cleans the unused getClientIp helper from web/app/api/visit/route.ts.

Why

/api/visit is a same-origin SSRF-guarded outbound fetcher. Scrapers and SSRF probers hammered it at ~62 req/s for 5 days, and the 10/min/IP @upstash/ratelimit triggered one Lua call per hit. That's 27M Upstash commands / 606 GB billed bandwidth / $63.94 in 5 days. The rate-limit was cosmetic — SSRF guard rejects every abuse attempt regardless. Phase 1 already removed the prod env vars (fails open by design). This PR makes the decoupling permanent.

Verification (2026-05-23)

  • pnpm test 24/24 pass
  • pnpm build clean (Next 15.5.15)
  • Deployed dpl_DaNY4XnPeeCeUTXiGhhZu1sWCoab (aliased to visitportal.dev)
  • curl -I https://visitportal.dev/api/visit returns 400/502/200 as before, no X-Ratelimit-* headers (proof the Upstash path is gone)
  • https://visitportal.dev/ home page 200

Follow-up (not in this PR)

Put Cloudflare in front of visitportal.dev with a free-tier WAF rate-limit on /api/visit so scraper traffic drops at the edge instead of consuming Vercel function invocations.

🤖 Generated with Claude Code

The IP-based ratelimit on /api/visit was costing $63.94 / 5 days on
Upstash (606 GB bandwidth, 27 M ops) because the endpoint sits bare on
Vercel and got hammered at ~62 req/s by scrapers probing the SSRF-guarded
outbound fetcher. Every probe hit triggered one Lua ratelimit call.

Phase 1 of the fix removed UPSTASH_* env vars from the Vercel project so
the module fail-opened. This is Phase 3 — delete the dead code path
entirely so re-adding env vars cannot resurrect the cost surface.

Files removed: web/app/api/visit/rate-limit.{ts,test.ts}.
Deps removed: @upstash/ratelimit, @upstash/redis.
route.ts: drop rate-limit import, ratelimit invocation, the rl.headers
merge in `respond`, and the now-unused getClientIp helper.

Next step (out of scope here): put Cloudflare in front of visitportal.dev
with a free-tier rate-limit rule on /api/visit so the abuse traffic gets
dropped at the edge instead of consuming Vercel function invocations.

Verified:
- pnpm test (24/24 pass)
- pnpm build (clean)
- Deployed to prod (dpl_DaNY4XnPeeCeUTXiGhhZu1sWCoab); /api/visit returns
  400/502/200 as before, with no X-Ratelimit-* headers.

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

vercel Bot commented May 23, 2026

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

Project Deployment Actions Updated (UTC)
web Ready Ready Preview, Comment May 23, 2026 3:19pm

Request Review

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