Skip to content

Browse: fix 429s (same-origin bypasses rate limit + bumped caps) - #170

Merged
pablostanley merged 1 commit into
mainfrom
fix/browse-rate-limit
Apr 20, 2026
Merged

Browse: fix 429s (same-origin bypasses rate limit + bumped caps)#170
pablostanley merged 1 commit into
mainfrom
fix/browse-rate-limit

Conversation

@pablostanley

Copy link
Copy Markdown
Owner

Bug

`/browse` loads ~60 animated cards on first paint. Animated rate limit is 30/min per IP per lambda → 30 succeed, the other 30+ come back 429 → broken thumbnails in the grid. User hit it today.

Fix

The rate limit exists to stop external consumers from hammering the Sharp animated pipeline. Our own UI isn't abuse. Two changes:

  1. `isSameOrigin(request)` helper in `rate-limit.ts` — checks `Sec-Fetch-Site: same-origin`. Modern browsers send this on same-origin image loads, and it can't be forged cross-site by script. `/api/pixabot/[id]` and `/api/og` both skip `checkRate` when it's true.
  2. Bumped the per-IP caps: animated 30 → 120, OG 20 → 60. External consumers rendering a small themed grid shouldn't hit it on normal use.

Hotlinkers / curl scripts still capped — just at a more reasonable level.

Test plan

  • Load `pixabots.com/browse` after deploy. All 60 cards animate, zero 429s in the console.
  • Scroll load-more a few times. No 429s.
  • External `curl` burst on `/api/pixabot/{id}?animated=true` still gets 429 after 120 reqs.

🤖 Generated with Claude Code

/browse loads ~60 animated cards on first paint. Each fires off
/api/pixabot/{id}?animated=true. The per-IP rate limit on animated
renders was 30/min, so 30 succeeded and the other 30+ came back 429
— broken thumbnails in the grid.

The limit exists to stop external consumers from hammering the
Sharp animated pipeline. Our own UI isn't abuse. Two fixes:

- New `isSameOrigin(request)` in `rate-limit.ts` — checks
  `Sec-Fetch-Site: same-origin`, which modern browsers send on same-
  origin image loads and which can't be forged cross-site by script.
  External consumers (curl, other sites hotlinking, SDKs) still get
  rate-limited.
- `/api/pixabot/[id]` and `/api/og` both skip `checkRate` when
  `isSameOrigin` is true.
- Bumped the per-IP animated limit 30 → 120 and OG limit 20 → 60
  so legitimate external consumers rendering a small grid don't hit
  it on normal use either.

Hotlinkers, bots, and curl scripts still capped — just at a more
reasonable level for a themed avatar grid (e.g. 50-bot board).

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

vercel Bot commented Apr 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Apr 20, 2026 2:14am

Request Review

@pablostanley
pablostanley merged commit 35e244a into main Apr 20, 2026
3 checks passed
pablostanley added a commit that referenced this pull request Apr 20, 2026
… cache policy

Rolls the important stuff from this long session into the living docs
so next-agent (or next-me) doesn't have to reread the whole thread.

CLAUDE.md
- **Key conventions**: replace the old 8-frame-bounce line with the
  16-tick super-loop model; add sub-animation `kind` (static/blink/
  sequence) + `resolveFrameIndex` as the one source for frame picks;
  document `TOTAL_COMBOS_LABEL` as the single-source for the count;
  add the keydown `hasModifier` rule (earned from the ⌘R bug).
- **Adding new parts**: split static vs animated workflows. Static
  is flat PNG + stitcher copy-through. Animated uses subdir layouts
  (blink: open/closed pair, sequence: numbered) that the stitcher
  detects automatically; updated the parts.ts entry shape
  (`'name'` vs `{ name, frames, kind }`).
- **npm**: drop the stale "CLI token caveat" note — first-publish
  is done, token allow-list extended.
- **API**: refresh the endpoint list (frames metadata, batch, palette
  + bg params); document the 1d fresh / 7d SWR cache policy and the
  same-origin rate-limit bypass.

ROADMAP.md
- Appends Done entries for PR #158 (⌘R guard), #160 (SW bump),
  #162 (TOTAL_COMBOS dedupe), #166/#168/#169 (cache revalidate),
  and #170 (browse 429s). Each entry includes the "why we needed it"
  so future readers see the failure mode, not just the fix.

Memory (not in this commit; lives outside the repo):
- New `feedback_keyboard_modifier_guard.md` — the ⌘R lesson promoted
  to a rule.
- New `project_animation_infra.md` — the sub-animation mental model.
- Updated `project_npm_published.md` with current versions of core /
  react / cli.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
… cache policy (#171)

Rolls the important stuff from this long session into the living docs
so next-agent (or next-me) doesn't have to reread the whole thread.

CLAUDE.md
- **Key conventions**: replace the old 8-frame-bounce line with the
  16-tick super-loop model; add sub-animation `kind` (static/blink/
  sequence) + `resolveFrameIndex` as the one source for frame picks;
  document `TOTAL_COMBOS_LABEL` as the single-source for the count;
  add the keydown `hasModifier` rule (earned from the ⌘R bug).
- **Adding new parts**: split static vs animated workflows. Static
  is flat PNG + stitcher copy-through. Animated uses subdir layouts
  (blink: open/closed pair, sequence: numbered) that the stitcher
  detects automatically; updated the parts.ts entry shape
  (`'name'` vs `{ name, frames, kind }`).
- **npm**: drop the stale "CLI token caveat" note — first-publish
  is done, token allow-list extended.
- **API**: refresh the endpoint list (frames metadata, batch, palette
  + bg params); document the 1d fresh / 7d SWR cache policy and the
  same-origin rate-limit bypass.

ROADMAP.md
- Appends Done entries for PR #158 (⌘R guard), #160 (SW bump),
  #162 (TOTAL_COMBOS dedupe), #166/#168/#169 (cache revalidate),
  and #170 (browse 429s). Each entry includes the "why we needed it"
  so future readers see the failure mode, not just the fix.

Memory (not in this commit; lives outside the repo):
- New `feedback_keyboard_modifier_guard.md` — the ⌘R lesson promoted
  to a rule.
- New `project_animation_infra.md` — the sub-animation mental model.
- Updated `project_npm_published.md` with current versions of core /
  react / cli.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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