{"id":"CVE-2026-114079","aliases":["EUVD-2026-106882","GHSA-4672-hwv6-gq62","GHSA-49rg-27pr-85f9"],"status":"published","title":"Trigger.dev before 4.5.6 Cross-Environment Deployment Cancel via Cancel Endpoint","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","severity":{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00302,"percentile":0.23259,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-639"],"published":"2026-10-02T22:42:40Z","modified":"2026-10-30T02:30:59Z","fixes":[{"ecosystem":"npm","package":"trigger.dev","fixed_in":["4.5.6"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"trigger.dev","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"4.5.5"},{"fixed":"4.5.6"}]}]},{"source":"osv","ecosystem":"npm","package":"trigger.dev","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"4.5.6"}]}]}],"products":[{"vendor":"triggerdotdev","product":"trigger.dev"}],"references":[{"url":"https://github.com/triggerdotdev/trigger.dev/security/advisories/GHSA-4672-hwv6-gq62","tags":["vendor-advisory","exploit","advisory","web"]},{"url":"https://www.vulncheck.com/advisories/trigger-dev-before-4.5.6-cross-environment-deployment-cancel-via-cancel-endpoint","tags":["third-party-advisory","advisory"]},{"url":"https://github.com/triggerdotdev/trigger.dev/pull/4316","tags":["web"]},{"url":"https://github.com/triggerdotdev/trigger.dev/commit/6997aeb05e27d2db47f9eda01fdc8a17c81a1ae0","tags":["web"]},{"url":"https://github.com/triggerdotdev/trigger.dev/releases/tag/v4.5.6","tags":["web"]},{"url":"https://github.com/advisories/GHSA-4672-hwv6-gq62"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-114079","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-49rg-27pr-85f9"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/114xxx/CVE-2026-114079.json","tags":["advisory"]},{"url":"https://github.com/triggerdotdev/trigger.dev","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-114079","title":"Trigger.dev before 4.5.6 Cross-Environment Deployment Cancel via Cancel Endpoint","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","status":"published","published":"2026-10-21T00:36:47.902Z","modified":"2026-10-28T15:31:57.77Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium","assessor":"cna"},{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","level":"medium","assessor":"cna"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T13:40:01.309549Z"}],"euvd":[{"id":"EUVD-2026-106882","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","status":"published","published":"2026-10-21T00:36:47Z","modified":"2026-10-28T15:31:57Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T16:59:20.750321Z"}],"ghsa":[{"id":"GHSA-4672-hwv6-gq62","title":"Trigger.dev: Cross-environment deployment cancel","description":"### Summary\n\nTrigger.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 }`).\n\nThe 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.\n\n### Affected\n\n`apps/webapp`, HEAD `5d99457` (current `main`). Affects self-hosted and cloud.\n\n### Root cause\n\n`apps/webapp/app/routes/api.v1.deployments.$deploymentId.cancel.ts` authenticates to an environment and calls `deploymentService.cancelDeployment(authenticatedEnv, deploymentId, ...)`.\n\n`apps/webapp/app/v3/services/deployment.server.ts`:\n```ts\npublic cancelDeployment(authenticatedEnv: Pick\u003cAuthenticatedEnvironment,\"projectId\"\u003e, friendlyId, ...) {\n  return this.getDeployment(authenticatedEnv.projectId, friendlyId)   // projectId only\n    .andThen(validateDeployment)                                      // rejects only FINAL statuses\n    .andThen(cancelDeployment);                                       // updateMany -\u003e status CANCELED\n}\nprivate getDeployment(projectId: string, friendlyId: string) {\n  return this._prisma.workerDeployment.findFirst({\n    where: { friendlyId, projectId },        // \u003c-- NO environmentId filter\n  });\n}\n```\n\n`cancelDeployment` accepts `authenticatedEnv` but its type is literally `Pick\u003cAuthenticatedEnvironment,\"projectId\"\u003e` — it discards the environment identity. `validateDeployment` blocks only `FINAL_DEPLOYMENT_STATUSES`, so any in-progress deployment (`PENDING`/`INSTALLING`/`BUILDING`/`DEPLOYING`) is cancellable.\n\n**Contrast — the correctly env-scoped sibling** `api.v1.deployments.$deploymentId.ts` (GET):\n```ts\nconst deployment = await prisma.workerDeployment.findFirst({\n  where: { friendlyId: deploymentId, environmentId: authenticatedEnv.id },   // env-scoped\n});\n```\nReads 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.\n\n### Runtime PoC (proven on the self-host stack)\n\nSeeded one project with a `prod` env (key `tr_prod_…`) and a `dev` env (key `tr_dev_…`) and one `WorkerDeployment` (`deployment_pocvictim`, status `DEPLOYING`) in the **prod** env. As the **dev** key:\n\n```\nstatus BEFORE                                                  : DEPLOYING\nGET  /api/v1/deployments/deployment_pocvictim   (dev key)      -\u003e 404   (env-scoped read DENIES it)\nPOST /api/v1/deployments/deployment_pocvictim/cancel (dev key) -\u003e 204\nstatus AFTER                                                   : CANCELED   (prod deploy canceled by the dev key)\nCONTROL: same cancel with a DIFFERENT project's key            -\u003e 404   (projectId scope blocks cross-project)\n```\n\nThe 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.","status":"published","published":"2026-10-02T22:42:40Z","modified":"2026-10-02T22:42:43Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","level":"medium"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T14:41:24.739884Z"},{"id":"GHSA-49rg-27pr-85f9","title":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of...","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","status":"published","published":"2026-10-21T03:32:22Z","modified":"2026-10-21T03:32:26Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","level":"medium"},{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T15:14:08.480034Z"}],"nvd":[{"id":"CVE-2026-114079","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","status":"published","published":"2026-10-21T01:17:32.19Z","modified":"2026-10-28T16:17:05.327Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","level":"medium","assessor":"secondary"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T14:13:02.045979Z"}],"osv":[{"id":"CVE-2026-114079","title":"Trigger.dev before 4.5.6 Cross-Environment Deployment Cancel via Cancel Endpoint","description":"Trigger.dev before 4.5.6 contains an improper authorization vulnerability that allows holders of lower-trust environment API keys to cancel other environments' deployments because DeploymentService.getDeployment scopes lookups by projectId only. Attackers with a dev, preview, or CI secret key can POST to the deployment cancel endpoint to cancel in-progress production deployments within the same project.","status":"published","published":"2026-10-21T00:36:47.902Z","modified":"2026-10-30T02:30:59.411790709Z","cvss":[{"version":"4.0","score":5.3,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N","level":"medium"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T15:44:56.310988Z"},{"id":"GHSA-4672-hwv6-gq62","title":"Trigger.dev: Cross-environment deployment cancel","description":"### Summary\n\nTrigger.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 }`).\n\nThe 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.\n\n### Affected\n\n`apps/webapp`, HEAD `5d99457` (current `main`). Affects self-hosted and cloud.\n\n### Root cause\n\n`apps/webapp/app/routes/api.v1.deployments.$deploymentId.cancel.ts` authenticates to an environment and calls `deploymentService.cancelDeployment(authenticatedEnv, deploymentId, ...)`.\n\n`apps/webapp/app/v3/services/deployment.server.ts`:\n```ts\npublic cancelDeployment(authenticatedEnv: Pick\u003cAuthenticatedEnvironment,\"projectId\"\u003e, friendlyId, ...) {\n  return this.getDeployment(authenticatedEnv.projectId, friendlyId)   // projectId only\n    .andThen(validateDeployment)                                      // rejects only FINAL statuses\n    .andThen(cancelDeployment);                                       // updateMany -\u003e status CANCELED\n}\nprivate getDeployment(projectId: string, friendlyId: string) {\n  return this._prisma.workerDeployment.findFirst({\n    where: { friendlyId, projectId },        // \u003c-- NO environmentId filter\n  });\n}\n```\n\n`cancelDeployment` accepts `authenticatedEnv` but its type is literally `Pick\u003cAuthenticatedEnvironment,\"projectId\"\u003e` — it discards the environment identity. `validateDeployment` blocks only `FINAL_DEPLOYMENT_STATUSES`, so any in-progress deployment (`PENDING`/`INSTALLING`/`BUILDING`/`DEPLOYING`) is cancellable.\n\n**Contrast — the correctly env-scoped sibling** `api.v1.deployments.$deploymentId.ts` (GET):\n```ts\nconst deployment = await prisma.workerDeployment.findFirst({\n  where: { friendlyId: deploymentId, environmentId: authenticatedEnv.id },   // env-scoped\n});\n```\nReads 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.\n\n### Runtime PoC (proven on the self-host stack)\n\nSeeded one project with a `prod` env (key `tr_prod_…`) and a `dev` env (key `tr_dev_…`) and one `WorkerDeployment` (`deployment_pocvictim`, status `DEPLOYING`) in the **prod** env. As the **dev** key:\n\n```\nstatus BEFORE                                                  : DEPLOYING\nGET  /api/v1/deployments/deployment_pocvictim   (dev key)      -\u003e 404   (env-scoped read DENIES it)\nPOST /api/v1/deployments/deployment_pocvictim/cancel (dev key) -\u003e 204\nstatus AFTER                                                   : CANCELED   (prod deploy canceled by the dev key)\nCONTROL: same cancel with a DIFFERENT project's key            -\u003e 404   (projectId scope blocks cross-project)\n```\n\nThe 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.","status":"published","published":"2026-10-02T22:42:40Z","modified":"2026-10-21T10:57:24.966429549Z","cvss":[{"version":"3.1","score":5.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L","level":"medium"}],"cwes":["CWE-639"],"updated_at":"2026-11-02T15:46:36.434231Z"}]}}
