Skip to content

feat(webapp): share rate limit bucket across additional API keys per environment - #4508

Open
carderne wants to merge 23 commits into
feat/multi-keys-surfacefrom
feature/tri-12888-add-redis-cache-for-additional-api-key-environment-lookup
Open

feat(webapp): share rate limit bucket across additional API keys per environment#4508
carderne wants to merge 23 commits into
feat/multi-keys-surfacefrom
feature/tri-12888-add-redis-cache-for-additional-api-key-environment-lookup

Conversation

@carderne

@carderne carderne commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

What

Rate-limit the API by environment rather than per API key.

Previously the limiter keyed its bucket on the hash of the full Authorization header — one bucket per key. With additional environment API keys (tr_*_sk_*), an environment can mint many keys and each got its own full bucket, so more keys = higher effective rate limit. This collapses all of an environment's keys onto a single shared per-environment bucket, so the ceiling is exactly the configured limit regardless of key mix.

How

  • authorizationRateLimitMiddleware now lets the override return { config?, identifier? }. identifier, when present, is the rate limit bucket key; otherwise it falls back to the hashed Authorization header (unchanged legacy behavior, still used by engineRateLimiter and any unauthenticated fallthrough).
  • apiRateLimiter's override resolves the environment id and uses it as the identifier:
    • Additional keys (isAdditionalApiKey) resolve via a new resolveAdditionalApiKeyRateLimitScope() — a scope-agnostic keyHash → (environmentId, org limiter config) lookup. It is deliberately permissive (restricted keys resolve too) because it's used only for bucketing, never as an auth decision — request auth still goes through the RBAC bearer controller, which enforces scopes. Revoked/expired keys are excluded so they can't hold a bucket warm.
    • Root/legacy keys reuse the environment already resolved by authenticateAuthorizationHeader and key on environment.id too.
  • The identifier is always the stable environment id, never the secret key (which can rotate and would split the bucket).
  • The whole override result is cached per key by the existing SWR cache, so no extra per-request lookup and no separate Redis mapping is added.

Behavior notes

  • Root + additional keys of the same environment now share one bucket (ceiling = configured limit, not a multiple of it). Restricted additional keys are included — they were the biggest gap, since they authenticate via the RBAC controller and previously fell back to per-key buckets.
  • Public JWTs keep their existing fixed-window, per-token bucketing.
  • One-time bucket reset on deploy (bucket keys change); harmless.

Tests

  • New: two tokens resolving to the same identifier share one bucket.
  • New: with no identifier, bucketing stays per-key (legacy behavior preserved).
  • Updated existing override tests to the new { config } return shape.

Base: feat/multi-keys-surface. Closes TRI-12888.

@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e9ae728

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 27 packages
Name Type
@trigger.dev/sdk Patch
@trigger.dev/python Patch
@internal/dashboard-agent Patch
@internal/sdk-compat-tests Patch
@trigger.dev/build Patch
@trigger.dev/core Patch
@trigger.dev/react-hooks Patch
@trigger.dev/redis-worker Patch
@trigger.dev/rsc Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch
@trigger.dev/rbac Patch
@trigger.dev/sso Patch
trigger.dev Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@internal/metrics-pipeline Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/run-store Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d8e390b-6b3b-4285-8ea5-da8f3d06070b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@carderne
carderne marked this pull request as ready for review August 5, 2026 09:20
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

carderne added 12 commits August 5, 2026 18:07
A number passed to `expirationTime` is a Unix timestamp in seconds, not
milliseconds as the JSDoc claimed. Following the old docs produced a token
that effectively never expired.

Also fail loudly when an additional API key reaches a local self-signing
fallback. Those keys are not the environment's JWT signing material, so the
token would never verify. Every endpoint that returns a public access token
sets `x-trigger-jwt`, so this is unreachable today.
The API key policy methods are optional on the plugin-facing controller
contract, so `Pick` over it yields optional members that these call sites
would have to guard. Both already receive the LazyController singleton,
which has substituted its fail-closed defaults, so point them at
HostRbacController and keep the call sites guard-free.
Require both the global issuance switch and organization rollout flag before creating additional keys, while leaving existing credentials available for use and revocation. Show nullable creators and identify SDK v4.5.8 as the first compatible public-token version.
Record bounded outcomes for additional key creation, policy preparation, revocation, and public-token minting.
@carderne
carderne force-pushed the feature/tri-12888-add-redis-cache-for-additional-api-key-environment-lookup branch from ba817b9 to 316db63 Compare August 5, 2026 17:08
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@316db63

trigger.dev

npm i https://pkg.pr.new/trigger.dev@316db63

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@316db63

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@316db63

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@316db63

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@316db63

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@316db63

@trigger.dev/schema-to-json

npm i https://pkg.pr.new/@trigger.dev/schema-to-json@316db63

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@316db63

commit: 316db63

devin-ai-integration[bot]

This comment was marked as resolved.

carderne and others added 3 commits August 5, 2026 17:14
Use environment identifiers when displaying remaining API capacity and ignore additional keys tied to deleted projects.
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@carderne
carderne force-pushed the feature/tri-12888-add-redis-cache-for-additional-api-key-environment-lookup branch from 316db63 to cd2a0c0 Compare August 5, 2026 17:14

@devin-ai-integration devin-ai-integration 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.

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread apps/webapp/app/models/runtimeEnvironment.server.ts
@carderne
carderne force-pushed the feature/tri-12888-add-redis-cache-for-additional-api-key-environment-lookup branch from eb0e8f7 to e9ae728 Compare August 6, 2026 09:42
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