You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix(webapp): gate CLI auth-code PAT minting behind explicit consent + rate limit
The `/account/authorization-code/:code` loader minted and bound a Personal
Access Token as a pure side effect of a GET, so any authenticated browser that
merely landed on the URL (phished link, prefetch) silently issued a CLI PAT
bound to an attacker-supplied code, which the attacker then harvested from the
unauthenticated, unthrottled `/api/v1/token` endpoint.
- Move the mint out of the loader into an `action` behind an explicit
"Authorize" POST from a logged-in human. The loader now only renders a
consent screen (read-only `isAuthorizationCodeMintable`). The CLI contract is
unchanged: it never calls this route and keeps polling `/api/v1/token`, which
already returns `{ token: null }` until consent is given.
- Add IP rate limiting to `/api/v1/authorization-code` (mint) and per-code rate
limiting to `/api/v1/token` (poll). The poll limiter is keyed by the code,
not the IP, so the CLI's ~1/s poll loop isn't broken behind a shared NAT.
- Remove both endpoints from the global rate-limit allowlist.
- Shorten the unconsumed-code TTL from 10 minutes to 2.
Response shapes are unchanged; the only new behavior is a 429 on limit breach.
Addresses the auth-code half of GHSA-58mc (TRI-9770).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 92ef2c3cd68fc6c5d3e7b01ad553bae5fcf121b6)
* fix(webapp): require ADMIN role to rename or delete a project
verifyProjectMembership only proved org membership, so any MEMBER-role
invitee could rename or permanently delete a project and all its runs,
schedules, and environments. Gate the rename/delete intents on
OrgMember.role === ADMIN.
Addresses TRI-9870.
Co-authored-by: Daniel Sutton <dansutton@trigger.dev>
* fix(webapp): keep auth-code/token endpoints allowlisted (don't break CLI login)
The global apiRateLimiter keys on the Authorization header and returns 401 for
any matched-but-unauthenticated /api path BEFORE the route runs. The CLI
auth-code/token endpoints are intentionally unauthenticated, so removing them
from the allowlist 401s them and breaks CLI login outright. Restore the
allowlist entries (skipping the auth-keyed global limiter) — the dedicated
authCodeRateLimiter in the route actions still provides the throttle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit b823a8750b570802f245cbbab3d948b509db3cef)
* fix(webapp): require ADMIN for org delete/rename and non-DEV key regen
Add an isOrgAdmin helper (legacy OrgMember.role, independent of the RBAC
ability layer so it holds in OSS deployments) and gate on it for:
- organization rename/delete (settings index action)
- regenerating non-development API keys (production/staging/preview are
org-wide; DEV self-service is unchanged)
Both previously checked only org membership, letting any MEMBER perform
them.
Addresses TRI-9870.
Co-authored-by: Daniel Sutton <dansutton@trigger.dev>
* fix(webapp): bound auth-code minting even without X-Forwarded-For + record deferral
- The mint rate limiter previously no-op'd when extractClientIp returned null
(no X-Forwarded-For, e.g. non-ALB/direct deploys), leaving minting unbounded
there. Fall back to a shared key so it's always throttled.
- Add a server-changes note documenting the auth-code/org-admin hardening and
recording the intentional inviteMembers-baseline deferral so it isn't lost.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit d532795a28ac76b52b14d6a0ef72ac401535c7e1)
* fix(webapp): restore auth-code TTL to original 10 minutes
The 2-minute TTL was imported from the third-party report's recommendation, not
grounded in a real need. The consent gate is what closes the phishing vector;
the TTL length is immaterial to it. Restore the original 10-minute window (keep
the shared constant purely to stop the mint/read paths drifting).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 70951ddb57faf9ffb96f032110fda1668ea4ed62)
* fix(cli): widen login poll window to fit the new consent click
The auth-code login page now requires an explicit "Authorize" click before a PAT
is minted (it no longer mints as a side effect of loading the page). The CLI/MCP
poll loop was sized (~60s) for the old auto-mint flow, so a human who takes
longer than ~60s to approve would get "Failed to get access token" even though
the code is valid for 10 minutes. Widen the poll to ~5 minutes — within the code
TTL and the per-code poll rate limit (~1/s).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 80c39d6f0d63c89a6b50fcf8b80c02c1801472ff)
* docs(webapp): correct stale CLI poll-count in auth-code rate-limiter comment
The CLI poll window was widened to ~5 min, so "up to ~61 times/min / 60 retries"
no longer describes it. The steady cadence is ~1/s (~60/min), still under the
100/min/code cap regardless of total poll count. Comment-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3f87584db9c7e91474977ae9431b3b8b71c2e6d2)
* refactor(webapp): use findFirst over findUnique in auth-code lookups
Code-review cleanup: findFirst avoids the findUnique constraint on the
non-unique lookup shape used by the auth-code mint/consent path.
Co-authored-by: Daniel Sutton <dansutton@trigger.dev>
* fix(webapp): skip auth-code mint limit when there's no trustworthy client IP
The shared "no-forwarded-for" fallback collapsed every X-Forwarded-For-less
request into one 30/min bucket, letting a single client DoS login for a whole
non-ALB instance. Skip the limit when there's no client IP instead — matching
the existing magicLinkRateLimiter pattern. Our cloud is behind an ALB so the
per-IP limit always applies there; the consent gate (not this limit) is what
closes the PAT-theft vector, so leaving non-proxied self-host minting unbounded
is low-risk row churn rather than a login outage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit e5b9e109384b163b397ad879c9c0ae3b7e4d1853)
* docs: scope server-changes note to shipped fixes
* format
* fix(webapp): use conform submission.reply for ADMIN-denied 403s
The ADMIN gate returned the pre-parseWithZod `submission.error[""]` shape,
which no longer typechecks (TS2339) and would not surface in the form.
Use `submission.reply({ formErrors })` so the denial message renders and
the types line up with the parseWithZod migration on the base branch.
* chore: consolidate changeset and tighten comments
* fix(cli): retry auth-code polling on 429 instead of aborting
The login poll loop threw an AbortError for any failed token response,
which pRetry treats as fatal. A 429 from the per-code poll rate limiter
would therefore abandon the whole login ("Failed to get access token")
even though the auth code is still valid and the user may not have
approved the consent screen yet.
Expose the HTTP status on wrapZodFetch failures and, in the poll path,
treat a 429 as a retryable error so the loop backs off and keeps polling.
Covers both the CLI login command and the MCP auth flow, which share the
same getPersonalAccessToken helper.
* chore(webapp): scope changes to auth-code login
* chore: align auth-code login release note
---------
Co-authored-by: Daniel Sutton <dansutton@trigger.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments