Skip to content

fix(auth): rate-limit socket tokens per user, with an IP backstop - #8958

Merged
waleedlatif1 merged 3 commits into
stagingfrom
fix/socket-token-per-user-limit
Oct 13, 2026
Merged

waleedlatif1 merged 3 commits into
stagingfrom
fix/socket-token-per-user-limit

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

/api/auth/socket-token allowed 30 mints per minute per client IP, checked before the session. The client mints one token per page load or reconnect. So an office behind one NAT IP, or every tab reconnecting after a deploy, shared a single 30/min budget, and some users were left waiting on a 429. Meanwhile one user rotating IPs could get past the limit entirely.

New order:

  1. IP backstop, 300 burst / 300 per min, applied before any session work. Rejecting an unauthenticated request costs the same as before: one limiter call plus a signature check, with no DB read.
  2. Cookie-cached getSession() to read the user id. No user means 401 with the same body as before.
  3. enforceUserRateLimit('socket-token', userId), 30 burst / 30 per min. No user gets less than the old per-IP budget, and rotating IPs no longer helps.
  4. Mint unchanged. better-auth's generateOneTimeToken re-validates the session against the database, bypassing the cookie cache, and fails closed with 401.

Both 429s carry Retry-After, which the client honors (#8952). Session cookies are signed with the auth secret, so a client can't target another user's budget. If the limiter's store is unavailable, behavior is the same as before. This is stricter than the precedent elsewhere in the repo: cli/auth/approve and link-preview limit per user only, with no IP backstop.

Test plan

  • New route.test.ts, using the real limiter with in-memory storage. All 3 cases fail on the old route:
    • one user exhausted across many IPs gets 429 + Retry-After
    • two users behind one IP each get their own budget
    • unauthenticated requests get 401 until the IP backstop, then 429 with no session lookup
  • CI type-check, lint, check:test-patterns

… backstop

The 30/min budget was keyed on client IP and checked before the session,
so an office behind one NAT address (or every tab reconnecting after a
deploy) shared a single budget and throttled each other.

The same 30 burst / 30 per minute now applies per session user, after a
cookie-cached session read; the mint still re-reads the session from the
database. A 300/min IP backstop stays in front of everything so
unauthenticated traffic is rejected before any session lookup. 401 and
429 + Retry-After responses are unchanged.
@vercel

vercel Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Oct 13, 2026 3:49am UTC

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Confidence score: 4/5

  • In apps/sim/app/api/auth/socket-token/route.ts, stale-cookie requests hit the session DB before the per-user limit, and the 300/min IP backstop allows 10× more reads than the old cap. Apply a tighter IP limit before getSession().
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/app/api/auth/socket-token/route.ts">

<violation number="1" location="apps/sim/app/api/auth/socket-token/route.ts:52">
P2: The per-user limit runs after `getSession()`, so stale-cookie requests reach the session DB before any per-user budget applies. Only the 300/min IP backstop bounds those reads, which is 10x the old 30/min per-IP cap. Consider lowering the backstop or confirming this trade-off is intended.</violation>
</file>

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/app/api/auth/socket-token/route.ts Outdated
Comment thread apps/sim/app/api/auth/socket-token/route.ts
@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Critical impact] The PR appears safe to merge; no actionable issues remain.

Summary

The PR gives each user a 30-token budget and keeps a separate 300-token IP backstop before the session lookup. Token minting still requires a fresh session check.

  • Socket-token requests get separate user budgets behind an IP backstop.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Socket token request] --> B{Auth disabled?}
  B -->|Yes| C[Anonymous token]
  B -->|No| D{IP budget available?}
  D -->|No| E[429 with Retry-After]
  D -->|Yes| F{Session has a user?}
  F -->|No| G[401]
  F -->|Yes| H{User budget available?}
  H -->|No| E
  H -->|Yes| I[Mint with fresh session check]
  I --> J[Token or authentication error]
Loading

Reviews (2) · Last reviewed commit: "fix(auth): give the socket-token IP back..." · Reviewed by Greptile

Comment thread apps/sim/app/api/auth/socket-token/route.test.ts Outdated
Comment thread apps/sim/app/api/auth/socket-token/route.test.ts Outdated
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review this PR

@cubic-dev-ai

cubic-dev-ai Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this PR

@waleedlatif1 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Heads up: you’ve reached your flex budget. Increase your flex budget or wait for usage to reset.

Turn on auto-fix | Re-trigger cubic

@waleedlatif1
waleedlatif1 merged commit b3e929a into staging Oct 13, 2026
50 checks passed
waleedlatif1 added a commit that referenced this pull request Oct 13, 2026
--diagnostics read an empty or truncated diagnostics.txt as fewer errors,
so a failed compile's artifact plus --update could erase the baseline.
--report now writes status.json (exit code and printed error count) once
the compile's output and exit code agree, and --diagnostics requires it:
exit 0 must mean no errors, the parse must match the recorded count, and
a missing or malformed status fails. Command-level tests cover each case
and that --update never writes the baseline from such input.

Also passes a route context to the socket-token route test's POST call,
the one new test type error (TS2554) the ratchet caught after #8958.
waleedlatif1 added a commit that referenced this pull request Oct 13, 2026
* ci: ratchet type errors in apps/sim unit tests per file

apps/sim's tsconfig excludes *.test.ts(x), so ~3.4k test files are never
type-checked and any new test can add errors. check:test-types runs the
native tsc on apps/sim/tsconfig.tests.json (the app config with tests left
in), counts errors per file, and compares with a shrink-only baseline,
matching check:explicit-any's semantics: a rise or a new file fails, a drop
fails until --update shrinks it, --update never raises, --update --init only
creates a missing baseline. Output it cannot place (global diagnostics, a
crash) fails outright instead of reading as fewer errors.

It type-checks the whole app, so it runs in its own test-types job gated
like test (wired into ci-gate.sh), not in check:audits. On failure the job
uploads counts.json and the compiler output; --counts <file> lets the
baseline be bootstrapped or shrunk from that artifact without a local
compile.

* ci: commit the test type-error baseline from CI and drop the one-time --init path

Baseline from run 38342772080's test-types counts: 3209 errors in 874 files.
--update stays shrink-only; a missing baseline is restored from git.

* ci: ratchet test type errors per file and TS code, with command-level tests

A per-file count let one new error replace a fixed one with the total flat.
The baseline is now { file: { TSnnnn: count } }: any (file, code) count
that rises fails, including a new code in a baselined file, and --update
stays shrink-only per pair. The key stops at the code: line and column
move with edits, and messages print types that change with unrelated
source or compiler versions.

--diagnostics <file> replaces --counts: it reads a saved compiler output
(the CI artifact's diagnostics.txt) through the same parser and undercount
checks, so a fix can shrink the baseline without a local compile. The
baseline was migrated from run 38342772080's diagnostics.txt with the
script's parser: 3209 errors in 874 files across 52 codes, every per-file
total equal to the previous baseline's.

Command-level tests run the real script on fixture diagnostics: unchanged,
a rising count, a new code replacing a fixed error, a new file, a dropped
count, --update refusing a raise, --update shrinking, and unparseable or
inconsistent output. The checks.yml comment now describes the artifact as
input for shrinking the existing baseline.

* ci: verify saved test-type diagnostics against the compile's exit status

--diagnostics read an empty or truncated diagnostics.txt as fewer errors,
so a failed compile's artifact plus --update could erase the baseline.
--report now writes status.json (exit code and printed error count) once
the compile's output and exit code agree, and --diagnostics requires it:
exit 0 must mean no errors, the parse must match the recorded count, and
a missing or malformed status fails. Command-level tests cover each case
and that --update never writes the baseline from such input.

Also passes a route context to the socket-token route test's POST call,
the one new test type error (TS2554) the ratchet caught after #8958.

* ci: read the saved compiler status through toRecord
@waleedlatif1
waleedlatif1 deleted the fix/socket-token-per-user-limit branch October 13, 2026 05:51

This branch was previously deployed

1 inactive deployment
Preview — 8fd9f7c3 Deployed Oct 13, 2026 by vercel[bot]
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