diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/vercel-firewall.md | 43 |
1 files changed, 43 insertions, 0 deletions
diff --git a/docs/vercel-firewall.md b/docs/vercel-firewall.md new file mode 100644 index 00000000..0d3fdbff --- /dev/null +++ b/docs/vercel-firewall.md @@ -0,0 +1,43 @@ +# Vercel WAF — rate-limiting rules + +Coarse, per-IP abuse protection that runs at the edge, complementing the +app-level limiters in `src/lib/Error/rateLimit.ts` (which handle what the WAF +cannot — per-user windows and the per-(IP, badge) click counter). + +These rules are **not committable config** — Vercel WAF rules are managed in the +dashboard (or via Terraform / the REST API), not `vercel.json`. This file is the +spec to enter under **Project → Firewall → Configure → + New Rule**. + +Caveats from the platform: + +- Counters are **per region** — traffic spread across regions can exceed a + single-region limit. +- Counting keys on Hobby/Pro are **IP / JA4 only** (no app data like a badge id; + that is why the click counter is limited app-side instead). +- Window: min **10s**, max **10min** (Hobby/Pro). +- **Hobby allows only 1 rule per project**; Pro allows 40. +- Start each rule's action as **Log** to observe real traffic, then switch to + **Deny (429)** once the thresholds look right. + +## Hobby (single rule) + +| Field | Value | +| --- | --- | +| Name | `api-mutations-ratelimit` | +| If | `Request Path` starts with `/api/` **AND** `Request Method` is one of `POST`, `PUT`, `DELETE` | +| Then | Rate Limit — Fixed Window | +| Window / Limit | `60s` / `100` requests | +| Key | `IP` | +| Action | `Deny` (429) | + +## Pro (targeted rules) + +| Name | If | Window / Limit | Key | Action | +| --- | --- | --- | --- | --- | +| `auth-ratelimit` | Path starts with `/api/oauth/` | `60s` / `20` | IP | Deny | +| `mutations-ratelimit` | Path is one of `/api/badges`, `/api/preferences`, `/api/configuration` **or** starts with `/api/notifications/`; Method `POST`/`PUT`/`DELETE` | `60s` / `60` | IP | Deny | +| `graphql-ratelimit` | Path is `/graphql`; Method `POST` | `60s` / `60` | IP | Deny | + +Public GET reads and the click counter are left to the CDN cache and the +app-level limiters respectively; add a coarse `/api/*` GET rule only if you see +read abuse. |