Skip to content

feat(webapp): enforce scopes for environment API keys - #4389

Open
carderne wants to merge 7 commits into
feat/multi-keys-foundationsfrom
feat/multi-keys-auth
Open

feat(webapp): enforce scopes for environment API keys#4389
carderne wants to merge 7 commits into
feat/multi-keys-foundationsfrom
feat/multi-keys-auth

Conversation

@carderne

@carderne carderne commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Environment API keys backed by the additional-key table can authenticate API requests using their stored effective scopes. Revoked and expired keys are rejected, branch environments retain their existing routing behavior, and last-used timestamps are updated on a throttled best-effort basis.

Design

API route builders receive the resolved ability and reject restricted keys on routes without an authorization declaration. Existing deployment, environment variable, queue, run, task, batch, session, and waitpoint routes declare the resources they access.

Trigger and batch responses return server-signed public access tokens, so additional keys never need access to the environment signing secret. Root-key rotation also keeps public tokens valid for the existing grace window.

Deployment notes

The schema migration must be present before this code is deployed. Because bearer resolution runs on every authenticated request, deploy the resolver with additional-key lookup disabled, verify root-key and public-token parity, then enable lookup before any additional keys can be issued.

The multi-task authorization tightening changes the result for narrowly scoped tokens that request tasks outside their grants. Observe would-deny results before enforcing that check. Request-idempotency keys are also newly isolated by environment and task, so a retry crossing the deployment boundary may execute once more before old cache entries expire.

Follow-ups

  • Add a system-wide kill switch for additional-key lookup, defaulted off for the initial deployment.
  • Add authentication observability by credential kind, result, latency, and lookup path without recording credential values.
  • Add would-deny observability and an independent enforcement switch for multi-task authorization.
  • Add an independent switch for server-issued batch tokens while root-key parity is verified.
  • Confirm every API route reachable by a restricted key has an explicit authorization declaration or intentionally fails closed.
  • Verify root-key rotation, revoked-key grace, and public-token validation through each bearer resolver path.

@coderabbitai

coderabbitai Bot commented Jul 27, 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: e8b2f1a3-85be-4e91-9fa6-90ffec986e77

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multi-keys-auth

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.

@changeset-bot

changeset-bot Bot commented Jul 27, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3f1d281

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

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

Click here to learn what changesets are, and how to add one.

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

@carderne
carderne force-pushed the feat/multi-keys-foundations branch from 0e70a0b to d20a5af Compare July 27, 2026 11:51
@carderne
carderne force-pushed the feat/multi-keys-auth branch from 37de624 to 6b31535 Compare July 27, 2026 11:51
@pkg-pr-new

pkg-pr-new Bot commented Jul 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

@trigger.dev/build

npm i https://pkg.pr.new/@trigger.dev/build@3f1d281

trigger.dev

npm i https://pkg.pr.new/trigger.dev@3f1d281

@trigger.dev/core

npm i https://pkg.pr.new/@trigger.dev/core@3f1d281

@trigger.dev/python

npm i https://pkg.pr.new/@trigger.dev/python@3f1d281

@trigger.dev/react-hooks

npm i https://pkg.pr.new/@trigger.dev/react-hooks@3f1d281

@trigger.dev/redis-worker

npm i https://pkg.pr.new/@trigger.dev/redis-worker@3f1d281

@trigger.dev/rsc

npm i https://pkg.pr.new/@trigger.dev/rsc@3f1d281

@trigger.dev/schema-to-json

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

@trigger.dev/sdk

npm i https://pkg.pr.new/@trigger.dev/sdk@3f1d281

commit: 3f1d281

@carderne
carderne force-pushed the feat/multi-keys-auth branch from 6b31535 to a8e673e Compare July 27, 2026 12:35
@carderne
carderne force-pushed the feat/multi-keys-foundations branch from 36f03ba to 51e1189 Compare July 27, 2026 15:22
@carderne
carderne force-pushed the feat/multi-keys-auth branch from a8e673e to 24771ce Compare July 27, 2026 15:23
carderne added 4 commits July 27, 2026 16:47
Add a global, org-locked kill switch (additionalApiKeyLookupEnabled,
default off) gating the additional environment API-key lookup, and bounded
OTel auth telemetry (api_auth.attempts counter, api_auth.duration_ms
histogram, api_auth.rollout_mode gauge) to watch the rollout.

Attributes are closed enums only (resolver, credential_kind, result,
lookup_path); no credentials, hashes, or tenant identifiers are recorded.
Revert authenticateApiKeyWithScope to rbac.authenticateAuthorizeBearer
(the kill switch still applies via the rbac controller), and make the
auth telemetry tolerate a missing resolution from test doubles. Also
register the v3/apiKeys subpath in core's typesVersions for node10.
@carderne
carderne marked this pull request as ready for review July 28, 2026 10:19
devin-ai-integration[bot]

This comment was marked as resolved.

@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 1 new potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines 122 to +129
triggerAction: "trigger",
});

const $responseHeaders = await responseHeaders(
batch,
authentication.environment,
triggerClient
);
const $responseHeaders = await publicAccessTokenResponseHeaders({
environment: authentication.environment,
scopes: [`read:batch:${batch.id}`],
expirationTime: "1h",
});

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.

🔍 Missing parent-run authorization on v1 batch trigger route

apps/webapp/app/routes/api.v1.tasks.batch.ts accepts body.parentRunId (via BatchTriggerTaskV2RequestBody) and passes body straight to BatchTriggerV3Service.call at apps/webapp/app/routes/api.v1.tasks.batch.ts:111, but the handler (api.v1.tasks.batch.ts:43) never destructures ability nor calls canWriteParentRun(...). Every other trigger/batch route touched in this PR added that check (api.v1.tasks.$taskId.trigger.ts, api.v1.tasks.$taskId.batch.ts, api.v2.tasks.batch.ts, api.v3.batches.ts). Because this route sets allowJWT: true and declares everyResource task authorization, a restricted task-scoped credential passes the route gate for its own task yet can supply an arbitrary parentRunId it does not own to link/resume. This is an authorization gap consistent with the ones the PR fixes elsewhere; reported via the security scan.

(Refers to lines 111-129)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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