GHSA-4672-hwv6-gq62: Medium severity npm/trigger.dev vulnerability
Summary
Trigger.dev isolates each project into multiple environments (dev, staging, prod, and per-PR preview branches), each with its own secret API key — the environment is a trust boundary (a dev/preview/CI key is lower-trust than a prod key). Most API routes enforce this by scoping resource lookups to the authenticated key's environment (where: { friendlyId, runtimeEnvironmentId: auth.environment.id }).
The deployment cancel path does not. DeploymentService.getDeployment() scopes the lookup by projectId only — never environmentId — so a secret key for any environment in a project can cancel a deployment belonging to any other environment of the same project, including production. The deployment GET route, by contrast, is env-scoped — so the same key that is 404'd when trying to read a prod deployment can nonetheless cancel it. That asymmetry is the bug.
Affected
apps/webapp, HEAD 5d99457 (current main). Affects self-hosted and cloud.
Root cause
apps/webapp/app/routes/api.v1.deployments.$deploymentId.cancel.ts authenticates to an environment and calls deploymentService.cancelDeployment(authenticatedEnv, deploymentId, ...).
apps/webapp/app/v3/services/deployment.server.ts: ts public cancelDeployment(authenticatedEnv: Pick<AuthenticatedEnvironment,"projectId">, friendlyId, ...) { return this.getDeployment(authenticatedEnv.projectId, friendlyId) // projectId only .andThen(validateDeployment) // rejects only FINAL statuses .andThen(cancelDeployment); // updateMany -> status CANCELED } private getDeployment(projectId: string, friendlyId: string) { return this.prisma.workerDeployment.findFirst({ where: { friendlyId, projectId }, // <-- NO environmentId filter }); }
cancelDeployment accepts authenticatedEnv but its type is literally Pick<AuthenticatedEnvironment,"projectId"> — it discards the environment identity. validateDeployment blocks only FINALDEPLOYMENTSTATUSES, so any in-progress deployment (PENDING/INSTALLING/BUILDING/DEPLOYING) is cancellable.
Contrast — the correctly env-scoped sibling api.v1.deployments.$deploymentId.ts (GET): ts const deployment = await prisma.workerDeployment.findFirst({ where: { friendlyId: deploymentId, environmentId: authenticatedEnv.id }, // env-scoped }); Reads are env-scoped; the cancel mutation is not. The same getDeployment(authenticatedEnv.projectId, …) helper also backs the deployment progress methods (deployment.server.ts:172,329), so the projectId-only scope is a small class.
Runtime PoC (proven on the self-host stack)
Seeded one project with a prod env (key trprod…) and a dev env (key trdev…) and one WorkerDeployment (deploymentpocvictim, status DEPLOYING) in the prod env. As the dev key:
status BEFORE : DEPLOYING GET /api/v1/deployments/deploymentpocvictim (dev key) -> 404 (env-scoped read DENIES it) POST /api/v1/deployments/deploymentpocvictim/cancel (dev key) -> 204 status AFTER : CANCELED (prod deploy canceled by the dev key) CONTROL: same cancel with a DIFFERENT project's key -> 404 (projectId scope blocks cross-project)
The dev key cannot read the prod deployment (404) yet cancels it (204 → CANCELED); a different project's key is correctly 404'd — so the gap is precisely cross-environment within a project.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/trigger.devto a version that resolves this vulnerability.Fixed in 4.5.6
Event History
Frequently Asked Questions
Which deployments are exposed to cancellation by a lower-trust credential?
Any deployment in another environment of the same project, including production deployments, can be targeted. The issue crosses environment boundaries but does not indicate cross-project access.
What does an attacker need to exploit this?
An attacker needs a secret API key for any environment within the target project, such as a dev, preview, staging, or CI environment key. That credential can be used to cancel a deployment associated with a different environment in that project.
Are hosted and self-hosted deployments affected?
Yes. The advisory identifies both Trigger.dev Cloud and self-hosted deployments as affected.
Can the same lower-trust key retrieve details of the production deployment before canceling it?
Not through the deployment GET route described in the advisory. That route is environment-scoped and returns a 404 for a production deployment when accessed with a key from another environment, while the cancel path was not environment-scoped.