Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.key).set(newMetadata, value) in packages/core/src/v3/runMetadata/operations.ts without rejecting dangerous constructor and prototype path segments. A caller with a normal environment API key can pollute Object.prototype in the shared webapp process, corrupting Prisma queries and Prometheus labels, breaking other tenants' worker authentication, and causing a process-wide denial of service. This issue is fixed in version 4.5.6.
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.key).set(newMetadata, value) in packages/core/src/v3/runMetadata/operations.ts without rejecting dangerous constructor and prototype path segments. A caller with a normal environment API key can pollute Object.prototype in the shared webapp process, corrupting Prisma queries and Prometheus labels, breaking other tenants' worker authentication, and causing a process-wide denial of service. This issue is fixed in version 4.5.6.
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.key).set(newMetadata, value) in packages/core/src/v3/runMetadata/operations.ts without rejecting dangerous constructor and prototype path segments. A caller with a normal environment API key can pollute Object.prototype in the shared webapp process, corrupting Prisma queries and Prometheus labels, breaking other tenants' worker authentication, and causing a process-wide denial of service. This issue is fixed in version 4.5.6.
## Summary
The run-metadata update endpoint `PUT /api/v1/runs/:runId/metadata` applies client-supplied
"operations" by passing the **attacker-controlled `operation.key`** straight into
`new 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 an
`uncaughtException` (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) — the `JSONHeroPath(operation.key).set()` sink is present from
the first commit of `operations.ts`. SDK was at `3.3.8` at that time.
- **Still present at HEAD** (SDK `4.5.0-rc.7`); `operations.ts` unchanged since 2025-05, and
`@jsonhero/path` is pinned at `^1.0.21` (no proto-guard) throughout.
- **Affected range:** `>= v3.3.8` (metadata operations API) through `v4-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`, …):
```ts
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 on
`Object.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.
```bash
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:
1. **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`
```
2. **Cross-tenant authentication break** — the *next* request from a different client (a worker's
`POST /engine/v1/dev/dequeue`) fails inside `findEnvironmentByApiKey`:
```
prisma.runtimeEnvironment.findFirst({ where:{ apiKey:"…", polluted:"PWNED" },
include:{ project:true, …, polluted:"PWNED" } }) -> PrismaClientValidationError
```
i.e. one tenant's request **breaks authentication for other tenants' workers** → their jobs stop
being dequeued/processed.
3. **Denial of service — full process crash.** The `uncaughtException` in 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.)*
## 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 `uncaughtException` **crashes 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.).
## Suggested remediation
1. **Reject dangerous path segments** in `operation.key` before building the path — block
`__proto__`, `constructor`, `prototype` (and validate the `$.`-rooted JSONHero path shape).
2. **Build metadata on a null-prototype object** (`Object.create(null)`) and/or use a
pollution-safe setter, so `__proto__` cannot reach `Object.prototype`.
3. Upgrade/replace `@jsonhero/path` for a version that is prototype-pollution safe, or wrap its
`.set()` with a guard.
CISA KEV lists vulnerabilities known to be exploited. FIRST EPSS estimates the likelihood of exploitation in the next 30 days. An unavailable score does not mean zero risk.
Which CVEs have scores here?
This view includes predictions for CVE identifiers explicitly listed in public NuGet advisories. Microsoft release pages have their own prediction coverage. Check source status for the scope and latest checks.
No exploitation or prediction data is available here for these CVEs. This does not mean they are safe or unlikely to be exploited.
Risk history
Compare earlier KEV listings and daily EPSS scores. Advisory wording and affected versions have a separate history.
When does an EPSS score appear as a change?
A prediction change is reported when EPSS rises from below 10% to 10% or higher. Other daily score changes remain in this history.
Advisory history
11 saved versions
See how the publisher's advisory changed over time. Each link opens the version saved at that time. The publisher's update date can differ from the date shown here.
How advisory history works
Each link identifies one complete publisher record. History can include advisories that previously shared an identifier; an identifier removed by a publisher no longer establishes a current relationship. A historical link shows only that record and earlier saved versions. Failed checks and unchanged reads do not create new versions.
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…
Trigger.dev is a platform for building and deploying fully managed AI agents and workflows. From 3.3.8 until 4.5.6, the PUT /api/v1/runs/:runId/metadata endpoint passes attacker-controlled operation.key values to new JSONHeroPath(operation.…