feat(app): cap live analyses per client, and say what the cap is worth - #9
Merged
Merged
Conversation
`POST /api/analyze` is unauthenticated by design — the demo promises no signup — so a deployment with a provider key configured was spending its owner's credit for whoever asked, at whatever rate they asked. Live calls now count against a per-client window (CHANGESAFE_LIVE_RATE_LIMIT, default 10 per hour, 0 to disable), and a refusal answers 429 with retry-after and points at replay, which runs the identical pipeline for free. Replay stays uncapped. It costs nothing, and the promise that anyone can drive the whole gate without an account is the demo. The cap is documented as what it is: a speed bump. It counts in one process's memory, so serverless holds a counter per instance, and it identifies callers by a forwarded header that only a trusted proxy makes trustworthy. README and .env.example say so, and say to put authentication or a proxy in front of a public live deployment. Overstating it would be worse than not having it — someone would rely on it. A bad value for the limit falls back to the default rather than to unlimited: a typo in a deployment variable must not quietly remove the cap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Npn2z4Uami1SpJyaCRsy6N
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
5 tasks
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Item #10 from the security review.
POST /api/analyzeis unauthenticated by design — the demo promises no signup — so on a deployment with a provider key configured, live mode spent the owner's credit for whoever asked, at whatever rate they asked. Live calls now count against a per-client window; over it, the route answers 429 withretry-afterbefore reaching any provider, and points at replay, which runs the identical pipeline for free.Replay stays uncapped. It costs nothing, and "anyone can drive the whole gate with no key and no account" is the demo — capping it to protect a budget it never touches would be the wrong trade.
The cap is documented as a speed bump, not a defense. It counts in one process's memory, so a serverless deployment holds a counter per instance, and it identifies callers by a forwarded header that only a trusted proxy makes trustworthy. README and
.env.exampleboth say so, and say to put authentication or a proxy in front of a public live deployment. Overstating it would be worse than not shipping it, because someone would rely on it.Two smaller decisions worth flagging for review:
CHANGESAFE_LIVE_RATE_LIMITfalls back to the default, not to unlimited — a typo in a deployment variable must not quietly remove the cap.0still disables it deliberately.RATE_LIMITEDis added toApiErrorCodeSchema; the client already renderserror.messageand honorsreplayAvailable, so no UI change was needed.Safety review
tests/unit/rate-limit.test.ts(limit, per-client isolation, window rollover, no push-back on retries,0disables, env parsing including the typo fallback, caller identification) and three cases intests/integration/analyze-api.test.ts— a 429 that never reaches a provider, replay staying open past the live limit, and one client's spending not refusing another's first call.Checklist
npm run lint && npm run typecheck && npm test && npm run buildpassnpm run test:e2epasses (3/3, run against the preinstalled Chromium)Tests are 423 passing (+14). The rate-limit tests consume the window directly rather than by issuing live calls, so the suite still needs no network and no credential. The one failure in my container is the pre-existing root-only case in
verification-bundle.test.ts:924— #7 fixes it.Generated by Claude Code