diff options
| author | Fuwn <[email protected]> | 2026-06-02 01:56:02 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-06-02 01:56:02 +0000 |
| commit | 6dc809d833212a1b5ff75786bfacfb4581dac1a2 (patch) | |
| tree | 598fd32eef9eba6047ee4973912666aac77ff729 /src/stores | |
| parent | fix(security): sanitize badge_wall_css server-side, render via textContent (diff) | |
| download | due.moe-6dc809d833212a1b5ff75786bfacfb4581dac1a2.tar.xz due.moe-6dc809d833212a1b5ff75786bfacfb4581dac1a2.zip | |
fix(security): make rate limiting real; limit the click counter (L8/L10)
rateLimit.ts built a new in-memory RateLimiter inside the function on every
call, so its store was always empty and it never limited anything. Rewrite
as module-level singletons: auth/mutation/read IP classes returning 429,
plus a click-counter limiter keyed on (IP, badge) so a viewer browsing many
different badges isn't throttled while hammering one badge is capped. Wire
the counter into PUT /api/badges?incrementClickCount (L10). Add a pluggable
RateLimiterStore seam (in-memory default, Upstash/Vercel KV ready) and
document the serverless per-region caveat.
Add docs/vercel-firewall.md with the dashboard WAF rate-limit rule spec
(Hobby 1-rule vs Pro) for coarse per-IP edge protection — keys the app
limiters can't express at the edge.
Verified locally: same-badge hits 200,200,429,429…; a different badge stays
200; a no-origin hit is rate-checked then 401.
Diffstat (limited to 'src/stores')
0 files changed, 0 insertions, 0 deletions