feat(security): rate-limit search/upload/import + gate import routes (P0 #6 - closes last P0) #20
No reviewers
Labels
No labels
agent-context-drift
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: rstillwell/deckhearth#20
Loading…
Reference in a new issue
No description provided.
Delete branch "convoy/add-rate-limiting"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Closes the LAST open P0 ship-blocker. With this merge, all 8 of 8 P0s in
.convoys/ship-readiness.mdare RESOLVED — the launch-readiness checklist is empty for P0s.fix-auth-bypassBrief 4 shippedlib/rate-limit.jswith a single 5/15min auth limiter wired into login + register. This convoy extends the module to 5 named limiters (auth/search/upload/generate/import) and wires them into the remaining abusable surface, plus closes a critical secondary discovery: the import-routes auth gap.Convoy artifacts
.convoys/add-rate-limiting.md(architect ratified 6 decisions; D1 routed back for operator gate-1).convoys/add-rate-limiting/brief-1-extend-rate-limit-and-wire-routes.md60b842e51a3a97Architect's most important discovery
Investigating Decision 1 (scope expansion for import routes), the architect found:
pages/api/cards/import-{mtg,pokemon,lorcana}.jshad ZERO auth checks. Publicly callable, hit external APIs (Scryfall / Pokémon-TCG / Lorcana) with no caller throttling, performed unbounded DB writes.pages/admin/card-import.js's fetch sent NOAuthorizationheader today. AddinggetUserFromRequestto the import APIs without atomically fixing the admin UI would have returned 401 on every "Import Cards" click — a broken-but-passing PR (the admin UI isn't smoke-tested).The brief atomically does both: API gating + admin UI Bearer-header fix in one commit.
Decisions ratified
checkAuthRateLimitcontract; add 4 named functions;Map<className, Ratelimit>cache; per-class Redis prefixextractUserIdentifierTHROWS on null/undefined/empty/NaN userId to prevent silent fallback-to-IP that would convert per-user limits into household-IP lockouts'Too many attempts. Try again later.'matches login.js/register.js (anti-fingerprinting)fill-vitest-handler-coverageDiff
10 files, +185 / -23.
pages/api/auth/login.js+pages/api/auth/register.jsbyte-identical at HEAD (Brief 4's contract preserved).Per-class limiter inventory
checkAuthRateLimit(unchanged)login.js,register.jscheckSearchRateLimit(new)users/search.js,cards/search.jscheckUploadRateLimit(new)user/avatar.jscheckGenerateRateLimit(new)user/avatar/generate.jscheckImportRateLimit(new)cards/import-{mtg,pokemon,lorcana}.jsVerification
npm run lintnpm run test:run(vitest)user.role !== 'admin') inimport-*.js'auth_token'key, matching codebase convention)login.js,register.js) byte-identicaltcgvault:auth/search/upload/generate/import)CI results (post-push run 26382185019)
forbidden-cors-headers(fromcors-tighten): passforbidden-endpoints(existing): passadopt-playwright-smokeDecision 4 (no baseline yet)Post-merge state
P0 status table will become:
getUserFromRequesthardcoded adminJWT_SECREThardcoded fallback8 of 8 RESOLVED. P1 backlog (
pick-a-name,single-auth-provider,single-sql-client,migration-tool, etc.) becomes the next focus.Out of scope (queued)
delete-dead-lorcana-import— Lorcana is gated defensively even though dead in frontendgod-function-split/refactor-cards-search-sql—cards/search.js's 240-line SQL god-function untouchedharden-multipart-parser—user/avatar.js's body streaming defensewithAdmin(handler)wrapper extraction — queued adjacent tosingle-auth-providerfill-vitest-handler-coverage— per-route handler testsTest plan
The latest updates on your projects. Learn more about Vercel for GitHub.
Pipeline Health
Build + CI gates
Build runs on Vercel; this CI runs lint and schema-map drift only (no duplicate build).
Role reports
See individual comments above for details. This rollup updates automatically.
Visual Diff
Screenshots and diffs uploaded as artifacts: view run
If intentional changes: update snapshots locally with
npx playwright test --project=visual --update-snapshotsand commit.