Summary
Trigger.dev: Prototype pollution via run metadata operations → process-wide cross-tenant DoS
The run-metadata update endpoint PUT /api/v1/runs/:runId/metadata applies client-supplied
"operations" by passing the attacker-controlled operation.key straight intonew JSONHeroPath(operation.key).set(newMetadata, value)
(packages/core/src/v3/runMetadata/operations.ts:22-23), with no prototype-pollution guard
(@jsonhero/path@^1.0.21 does not reject __proto__/constructor/prototype).
A request with key: "$.__proto__.polluted" sets Object.prototype.polluted in the webapp
process. Because every plain object then inherits that property, it corrupts unrelated code
process-wide and across tenants, including Prisma query building and the Prometheus metrics
client, causing query failures, broken authentication for other tenants' workers, and anuncaughtException (denial of service). Only a normal, low-privilege environment API key is
required (one request).
Severity
A single request from any holder of a normal environment API key contaminates Object.prototype
in the shared webapp process, breaking other tenants' workers (scope change) and degrading/
crashing the process (high availability impact). Prototype pollution is also a primitive for
further gadget chains (integrity/confidentiality).
Affected versions
- Introduced in commit
34f8bd588("Add ability to update parent and root run metadata from
children", PR #1563, 2025-01-08), theJSONHeroPath(operation.key).set()sink is present from
the first commit ofoperations.ts. SDK was at3.3.8at that time. - Still present at HEAD (SDK
4.5.0-rc.7);operations.tsunchanged since 2025-05, and@jsonhero/pathis pinned at^1.0.21(no proto-guard) throughout. - Affected range:
>= v3.3.8(metadata operations API) throughv4-beta/ current v4.x ,
fixed: 4.5.6.
Root cause
packages/core/src/v3/runMetadata/operations.ts, applyMetadataOperations() builds a path from
the untrusted operation.key and writes to it, for every operation type (set, append,increment, …):
const path = new JSONHeroPath(operation.key); // operation.key fully attacker-controlled
path.set(newMetadata, operation.value); // no __proto__/constructor/prototype rejection
The request schema (UpdateMetadataRequestBody) types key as a plain string with no validation,
and @jsonhero/[email protected] walks __proto__ as an ordinary segment → the assignment lands onObject.prototype.
Proof of Concept
Self-host ghcr.io/triggerdotdev/trigger.dev:v4-beta. Authenticated with a normal environment
API key (tr_dev_…) and any run id of that environment.
curl -X PUT "http://localhost:8030/api/v1/runs/run_cmqr2bsyo00013js2twwhdsfu/metadata" \
-H "Authorization: Bearer tr_dev_<env_key>" -H "Content-Type: application/json" \
--data '{"operations":[{"type":"set","key":"$.__proto__.polluted","value":"PWNED"}]}'
Result, Object.prototype.polluted = "PWNED" process-wide. Observed in the webapp logs:
- The request's own query is corrupted,
polluted:"PWNED"injected into every object Prisma
enumerates:prisma.taskRun.updateMany({ where:{ id:"…", metadataVersion:2, polluted:"PWNED" }, data:{ …, metadataVersion:{ increment:1, polluted:"PWNED" }, polluted:"PWNED" }, polluted:"PWNED" }) -> Unknown argument `polluted` - Cross-tenant authentication break, the next request from a different client (a worker's
POST /engine/v1/dev/dequeue) fails insidefindEnvironmentByApiKey:
i.e. one tenant's request breaks authentication for other tenants' workers → their jobs stopprisma.runtimeEnvironment.findFirst({ where:{ apiKey:"…", polluted:"PWNED" }, include:{ project:true, …, polluted:"PWNED" } }) -> PrismaClientValidationError
being dequeued/processed. - Denial of service, full process crash. The
uncaughtExceptionin prom-client
(Error: Added label "polluted" is not included in initial labelset: [ 'kind' ]) crashes the
webapp process. Demonstrated with a second tenant: Tenant B (a different org/env, with its
own API key) had a working request (POST /engine/v1/dev/dequeue→ HTTP 400, auth OK) before
the attack; immediately after Tenant A's single attack request, B's request returned HTTP 000
(no response), the whole multi-tenant webapp was down. Logs show the crash at 20:53:41 and the
process auto-restarting ~3s later (FairQueue/ScheduleEngine started). Repeating the attack in a
loop yields a crash-loop = sustained DoS for all tenants.
(Note: the metadata endpoint itself swallows the Prisma failure with ignoreError:true and still
returns HTTP 200, the damage is the process-wide contamination observed in the logs, not the
endpoint's status code.)
Suggested remediation
- Reject dangerous path segments in
operation.keybefore building the path, block__proto__,constructor,prototype(and validate the$.-rooted JSONHero path shape). - Build metadata on a null-prototype object (
Object.create(null)) and/or use a
pollution-safe setter, so__proto__cannot reachObject.prototype. - Upgrade/replace
@jsonhero/pathfor a version that is prototype-pollution safe, or wrap its.set()with a guard.
Impact
A low-privilege caller (one normal environment API key, one request) pollutes Object.prototype
in the shared multi-tenant webapp process, causing:
- Full cross-tenant denial of service, the resulting
uncaughtExceptioncrashes the webapp
process, taking the service down for all tenants (demonstrated: a second tenant's request
returned HTTP 000 immediately after the attack). Repeating the request produces a crash-loop /
sustained DoS. Even without the crash, contaminated Prisma queries break other tenants' worker
authentication, halting job processing. - A prototype-pollution primitive usable for further gadget chains (auth/logic bypass, etc.).
CVE-2026-73654 has a CVSS score of 8.5 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (4.5.6); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is CVE-2026-73654? CVE-2026-73654 is a high-severity security vulnerability in @trigger.dev/core (npm), affecting versions >= 3.3.8, <= 4.5.5. It is fixed in 4.5.6.
- How severe is CVE-2026-73654? CVE-2026-73654 has a CVSS score of 8.5 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of @trigger.dev/core are affected by CVE-2026-73654? @trigger.dev/core (npm) versions >= 3.3.8, <= 4.5.5 is affected.
- Is there a fix for CVE-2026-73654? Yes. CVE-2026-73654 is fixed in 4.5.6. Upgrade to this version or later.
- Is CVE-2026-73654 exploitable, and should I be worried? Whether CVE-2026-73654 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-73654 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-73654? Upgrade
@trigger.dev/coreto 4.5.6 or later.