Skip to content

Partition pre-authentication rate limits by API-key public ID - #15

Open
Lucenx9 wants to merge 1 commit into
mainfrom
codex/fix-global-pre-authentication-rate-limiting-issue
Open

Partition pre-authentication rate limits by API-key public ID#15
Lucenx9 wants to merge 1 commit into
mainfrom
codex/fix-global-pre-authentication-rate-limiting-issue

Conversation

@Lucenx9

@Lucenx9 Lucenx9 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Motivation

  • The previous implementation used a single process-wide token bucket for pre-authentication attempts, allowing unauthenticated actors to generate syntactically valid tokens and exhaust capacity for all legitimate keys.
  • The intent of this change is to preserve pre-auth throttling for repeated attempts while preventing attacker-controlled unknown public IDs from denying service to other public IDs or real keys.

Description

  • Replace the single global TokenBucket in AuthenticationRateLimiter with a Map<string, TokenBucket> keyed by the parsed API-key publicId, and change consume() to consume(publicId: string).
  • Update rateLimitAuthentication to parse the bearer token first and call limiter.consume(parsed.publicId) only when parsing succeeds (so buckets are partitioned per public id).
  • Update unit and security regression tests to reflect the per-public-id behavior, including a unit test that verifies independent refill/limits per public id and a security test that ensures unknown public ids cannot exhaust another key’s limit.

Testing

  • Ran static checks: npm run typecheck and npm run lint, both succeeded.
  • Ran formatting check: npm run format:check for the modified files, which succeeded.
  • Ran unit tests: npm run test:unit -- --run tests/unit/authentication/api-key.test.ts, and the targeted unit tests passed.
  • Attempted to run the full security test (tests/security/authentication/bearer-authentication.test.ts) but the suite that requires a PostgreSQL Testcontainer could not start because no container runtime was available in the environment; as a result the security suite failed to execute in CI but the added regression test logic is present and exercised by unit coverage where applicable.

Codex Task

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bc4a9f035

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const parsed =
match === null ? undefined : parseApiKeyToken(match[1] ?? "");
if (parsed !== undefined) {
const decision = limiter.consume(parsed.publicId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve an aggregate cap on authentication attempts

An unauthenticated client can generate a fresh syntactically valid 16-character public ID for every request, so every call receives a full new bucket and proceeds through bearerAuthentication to the database-backed findActiveByPublicId query. Consequently, authenticationRequestsPerMinute no longer limits aggregate pre-authentication database work at all; retain an aggregate or non-attacker-controlled limit in addition to the per-ID bucket.

Useful? React with 👍 / 👎.

if (this.bucket.tokens >= 1) {
this.bucket.tokens -= 1;
bucket.updatedAt = now;
this.buckets.set(publicId, bucket);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bound or evict attacker-created rate-limit buckets

When clients rotate valid-format public IDs, each unauthenticated request permanently inserts another entry into this process-wide map. Entries are never removed after their tokens refill or IDs become inactive, allowing remote traffic to grow the heap without bound until the process restarts; add expiry/eviction or a hard capacity rather than retaining every observed ID indefinitely.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant