Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦 npm
Not in CISA KEV

CVE-2026-85651 — trigger.dev

Fix: triggerdotdev/trigger.dev@34b1a18

CVE-2026-85651 is a CWE-862 vulnerability in trigger.dev. A fix is available for trigger.dev — see the affected versions and patch details below.

Trigger.dev before 4.5.2 Unauthorized Environment Access via Run Replay

Also known asGHSA-qxpp-qjg8-x4jv
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-85651.

EPSS Exploitation Probability

via FIRST.org ↗
0.5%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs41th percentile — riskier than 41% of all scored CVEsHighest risk
0.00%0.33%0.66%0.99%0.5%0.5%Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

1other npm packages depend on this — each one inherits the vulnerability until it's patched upstream
trigger.devnpm
572Kdownloads / week

Description

Summary

The dashboard replay action authorizes the source run (it must belong to the caller's org), but the target environment for the replayed run is taken verbatim from the request body and is never checked for org or project membership. The environment lookup used by the replay path filters by id only. As a result, an authenticated user can replay one of their own runs into another organization's or project's environment, creating a task run there that consumes the victim tenant's queue and compute and pollutes their run history.

Details

The replay action member-scopes the source run correctly (apps/webapp/app/routes/resources.taskruns.$runParam.replay.ts, the findFirst on taskRun filtered by project.organization.members.some.userId), but passes the target environment straight from the submitted form, resources.taskruns.$runParam.replay.ts:344-346:

const replayRunService = new ReplayTaskRunService();
const newRun = await replayRunService.call(taskRun, {
  environmentId: submission.value.environment,   // attacker-controlled, unvalidated
  payload: submission.value.payload,
  ...
});

submission.value.environment comes from ReplayRunData (apps/webapp/app/v3/replayTask.ts: environment: z.string().optional()), so it is fully client-controlled.

ReplayTaskRunService resolves it with no ownership check, apps/webapp/app/v3/services/replayTaskRun.server.ts:26-28:

const authenticatedEnvironment = await findEnvironmentById(
  overrideOptions.environmentId ?? existingTaskRun.runtimeEnvironmentId
);

findEnvironmentById, apps/webapp/app/models/runtimeEnvironment.server.ts:197-211:

export async function findEnvironmentById(id) {
  const environment = await $replica.runtimeEnvironment.findFirst({
    where: { id },                 // no membership / org / project filter
    include: authIncludeWithParent,
  });
  if (!environment || environment.project.deletedAt !== null) return null;
  return toAuthenticated(environment);
}

The resolved environment is then handed to TriggerTaskService.call (apps/webapp/app/v3/services/triggerTask.server.ts), which trusts the passed authenticatedEnvironment and performs no further authorization. The replay loader does constrain the environment picker to the run's own project (replay.ts around 178-184), but the action does not re-impose that constraint, so the server-side control is missing (UI-gated only).

PoC

As an authenticated user who owns a run in their own org (run param runParam), and who knows a target environment id belonging to another org or project (a CUID, obtained or guessed):

POST /resources/taskruns/<own runParam>/replay
Content-Type: application/x-www-form-urlencoded

environment=<environment id of the victim org/project>&failedRedirect=/...

The replay resolves the victim environment with no membership check and calls TriggerTaskService against it, creating a run in the victim tenant.

Live-validated: a Postgres database was seeded with two tenants (orgA with env_A, orgB with env_B_victim) and the verbatim findEnvironmentById lookup (findFirst joining the environment to its project, WHERE id = $1, the exact query the replay path runs) was executed with the victim env id as an orgA attacker. Captured:

[002 replay findEnvironmentById] attacker(orgA) supplied env_B_victim -> {"id":"env_B_victim","organization_id":"orgB","project_id":"proj_B","deleted_at":null}
CROSS-TENANT: CONFIRMED (resolved another org's env with no membership check -> replay would run there)

The lookup returned another organization's environment with no membership filter. That the replay action passes the client-supplied environmentId straight into this lookup (and TriggerTaskService trusts the returned env) is verified at source.

Impact

An authenticated user can create task runs in another tenant's environment (including production), consuming that tenant's queue concurrency and compute and inserting entries into their run history. Practical exploitation requires knowing a target environment id (not enumerable) and, for the injected run to actually execute rather than error, a task identifier that exists in the target environment; this bounds reliability but not the unauthorized cross-tenant write itself.

Remediation

In the replay action (or in ReplayTaskRunService), require the resolved target environment to belong to the source run's project/organization, for example assert environment.projectId === existingTaskRun.projectId, or re-validate the supplied environment id against the caller's membership (findEnvironmentBySlug with the validated projectId, or a members.some.userId filter), mirroring the constraint the loader already applies to the picker.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmtrigger.devall versions4.5.2npm install trigger.dev@4.5.2

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for trigger.dev, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update trigger.dev to 4.5.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-85651 is resolved across your whole dependency graph.

  3. Workarounds

    Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.

Frequently Asked Questions

### Summary The dashboard replay action authorizes the source run (it must belong to the caller's org), but the target environment for the replayed run is taken verbatim from the request body and is never checked for org or project membership. The environment lookup used by the replay path filters by id only. As a result, an authenticated user can replay one of their own runs into another organization's or project's environment, creating a task run there that consumes the victim tenant's queue and compute and pollutes their run history. ### Details The replay action member-scopes the source r
O3 Security · Impact-Aware SCA

Is CVE-2026-85651 in your dependencies?

Find it across npm, including transitive dependencies.

CVE-2026-85651: trigger.dev — Fixed in 4.5.2