Skip to content

Commit 383665f

Browse files
committed
fix prototype pollution in merge (<<)
1 parent 0d3ca7a commit 383665f

File tree

3 files changed

+41
-14
lines changed

3 files changed

+41
-14
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88

9+
## [4.1.1] - 2025-11-12
10+
### Security
11+
- Fix prototype pollution issue in yaml merge (<<) operator.
12+
13+
914
## [4.1.0] - 2021-04-15
1015
### Added
1116
- Types are now exported as `yaml.types.XXX`.

lib/loader.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ function charFromCodepoint(c) {
120120
);
121121
}
122122

123+
// set a property of a literal object, while protecting against prototype pollution,
124+
// see https://github.com/nodeca/js-yaml/issues/164 for more details
125+
function setProperty(object, key, value) {
126+
// used for this specific key only because Object.defineProperty is slow
127+
if (key === '__proto__') {
128+
Object.defineProperty(object, key, {
129+
configurable: true,
130+
enumerable: true,
131+
writable: true,
132+
value,
133+
});
134+
} else {
135+
object[key] = value;
136+
}
137+
}
138+
123139
var simpleEscapeCheck = new Array(256); // integer, for fast access
124140
var simpleEscapeMap = new Array(256);
125141
for (var i = 0; i < 256; i++) {
@@ -298,7 +314,7 @@ function mergeMappings(state, destination, source, overridableKeys) {
298314
key = sourceKeys[index];
299315

300316
if (!_hasOwnProperty.call(destination, key)) {
301-
destination[key] = source[key];
317+
setProperty(destination, key, source[key]);
302318
overridableKeys[key] = true;
303319
}
304320
}
@@ -358,17 +374,7 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
358374
throwError(state, 'duplicated mapping key');
359375
}
360376

361-
// used for this specific key only because Object.defineProperty is slow
362-
if (keyNode === '__proto__') {
363-
Object.defineProperty(_result, keyNode, {
364-
configurable: true,
365-
enumerable: true,
366-
writable: true,
367-
value: valueNode
368-
});
369-
} else {
370-
_result[keyNode] = valueNode;
371-
}
377+
setProperty(_result, keyNode, valueNode);
372378
delete overridableKeys[keyNode];
373379
}
374380

test/issues/0164.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,25 @@ const yaml = require('../../');
66

77

88
it('should define __proto__ as a value (not invoke setter)', function () {
9-
let object = yaml.load('{ __proto__: {foo: bar} }');
9+
let object = yaml.load('{ __proto__: {polluted: bar} }');
1010

1111
assert.strictEqual(({}).hasOwnProperty.call(yaml.load('{}'), '__proto__'), false);
1212
assert.strictEqual(({}).hasOwnProperty.call(object, '__proto__'), true);
13-
assert(!object.foo);
13+
assert(!object.polluted);
14+
});
15+
16+
17+
it('should merge __proto__ as a value with << operator', function () {
18+
let object = yaml.load(`
19+
payload: &ref
20+
polluted: bar
21+
22+
foo:
23+
<<:
24+
__proto__: *ref
25+
`);
26+
27+
assert.strictEqual(({}).hasOwnProperty.call(yaml.load('{}'), '__proto__'), false);
28+
assert.strictEqual(({}).hasOwnProperty.call(object.foo, '__proto__'), true);
29+
assert(!object.foo.polluted);
1430
});

0 commit comments

Comments
 (0)