From 792cbe72e52302f2363b16e93cbb97fe0968bb7b Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Tue, 28 Apr 2026 11:15:07 +0200 Subject: [PATCH 1/3] Use the null prototype when parsing YAML maps YAML maps map a key to a value. These keys may conflict with keys on the JavaScript `Object` prototype. Several JavaScript APIs that use a plain object to represent a key/value map, use a null prototype. Closes #675 --- src/nodes/Pair.ts | 2 +- src/nodes/YAMLMap.ts | 6 +++++- tests/node-to-js.ts | 9 ++++++--- tests/properties.ts | 21 ++++++++++++++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/nodes/Pair.ts b/src/nodes/Pair.ts index 18ddad34..d353cec6 100644 --- a/src/nodes/Pair.ts +++ b/src/nodes/Pair.ts @@ -36,7 +36,7 @@ export class Pair< doc: Document, ctx: ToJSContext ): ReturnType { - const pair = ctx.mapAsMap ? new Map() : {} + const pair = ctx.mapAsMap ? new Map() : { __proto__: null } return addPairToJSMap(doc, ctx, pair, this) } diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index b2075041..186a3765 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -235,7 +235,11 @@ export class YAMLMap< Type?: { new (): T } ) { ctx ??= new ToJSContext() - const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {} + const map = Type + ? new Type() + : ctx?.mapAsMap + ? new Map() + : { __proto__: null } if (this.anchor) ctx.setAnchor(this, map) for (const pair of this.values.values()) addPairToJSMap(doc, ctx, map, pair) return map diff --git a/tests/node-to-js.ts b/tests/node-to-js.ts index 170cd309..03da0b8c 100644 --- a/tests/node-to-js.ts +++ b/tests/node-to-js.ts @@ -17,12 +17,12 @@ describe('scalars', () => { describe('collections', () => { test('map', () => { const doc = parseDocument('key: 42') - expect(doc.value.toJS(doc)).toMatchObject({ key: 42 }) + expect(doc.value.toJS(doc)).toStrictEqual({ __proto__: null, key: 42 }) }) test('map in seq', () => { const doc = parseDocument('- key: 42') - expect(doc.get(0).toJS(doc)).toMatchObject({ key: 42 }) + expect(doc.get(0).toJS(doc)).toStrictEqual({ __proto__: null, key: 42 }) }) }) @@ -51,7 +51,10 @@ describe('alias', () => { two: &b { key: *a } three: *b `) - expect(doc.get('three').toJS(doc)).toMatchObject({ key: 42 }) + expect(doc.get('three').toJS(doc)).toStrictEqual({ + __proto__: null, + key: 42 + }) }) test('missing anchor', () => { diff --git a/tests/properties.ts b/tests/properties.ts index 9950f964..8001d013 100644 --- a/tests/properties.ts +++ b/tests/properties.ts @@ -1,6 +1,23 @@ import * as fc from 'fast-check' import { parse, stringify } from 'yaml' +/** Set pojo prototypes to `null` recursively */ +function nullifyProto(value: unknown): unknown { + if (typeof value !== 'object' || value == null) { + return value + } + if (Array.isArray(value)) { + return value.map(nullifyProto) + } + const result: Record = { __proto__: null } + for (const key in value) { + if (Object.hasOwn(value, key)) { + result[key] = nullifyProto((value as Record)[key]) + } + } + return result +} + describe('properties', () => { test('parse stringified object', () => { const key = fc.fullUnicodeString() @@ -25,7 +42,9 @@ describe('properties', () => { fc.assert( fc.property(yamlArbitrary, optionsArbitrary, (obj, opts) => { - expect(parse(stringify(obj, opts), opts)).toStrictEqual(obj) + expect(parse(stringify(obj, opts), opts)).toStrictEqual( + nullifyProto(obj) + ) }) ) }) From 5e1e819d5abc648fae5a5cc98e08d1adeda2623c Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Sat, 2 May 2026 14:01:47 +0200 Subject: [PATCH 2/3] Use Object.create() to create null prototype objects The use of `__proto__` is deprecated. --- src/nodes/Pair.ts | 2 +- src/nodes/YAMLMap.ts | 2 +- tests/node-to-js.ts | 17 +++++++++++------ tests/properties.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/nodes/Pair.ts b/src/nodes/Pair.ts index d353cec6..d371382b 100644 --- a/src/nodes/Pair.ts +++ b/src/nodes/Pair.ts @@ -36,7 +36,7 @@ export class Pair< doc: Document, ctx: ToJSContext ): ReturnType { - const pair = ctx.mapAsMap ? new Map() : { __proto__: null } + const pair = ctx.mapAsMap ? new Map() : Object.create(null) return addPairToJSMap(doc, ctx, pair, this) } diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index 186a3765..ab60353c 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -239,7 +239,7 @@ export class YAMLMap< ? new Type() : ctx?.mapAsMap ? new Map() - : { __proto__: null } + : Object.create(null) if (this.anchor) ctx.setAnchor(this, map) for (const pair of this.values.values()) addPairToJSMap(doc, ctx, map, pair) return map diff --git a/tests/node-to-js.ts b/tests/node-to-js.ts index 03da0b8c..0878e0f3 100644 --- a/tests/node-to-js.ts +++ b/tests/node-to-js.ts @@ -17,12 +17,16 @@ describe('scalars', () => { describe('collections', () => { test('map', () => { const doc = parseDocument('key: 42') - expect(doc.value.toJS(doc)).toStrictEqual({ __proto__: null, key: 42 }) + expect(doc.value.toJS(doc)).toStrictEqual( + Object.assign(Object.create(null), { key: 42 }) + ) }) test('map in seq', () => { const doc = parseDocument('- key: 42') - expect(doc.get(0).toJS(doc)).toStrictEqual({ __proto__: null, key: 42 }) + expect(doc.get(0).toJS(doc)).toStrictEqual( + Object.assign(Object.create(null), { key: 42 }) + ) }) }) @@ -51,10 +55,11 @@ describe('alias', () => { two: &b { key: *a } three: *b `) - expect(doc.get('three').toJS(doc)).toStrictEqual({ - __proto__: null, - key: 42 - }) + expect(doc.get('three').toJS(doc)).toStrictEqual( + Object.assign(Object.create(null), { + key: 42 + }) + ) }) test('missing anchor', () => { diff --git a/tests/properties.ts b/tests/properties.ts index 8001d013..b82ae3ab 100644 --- a/tests/properties.ts +++ b/tests/properties.ts @@ -9,7 +9,7 @@ function nullifyProto(value: unknown): unknown { if (Array.isArray(value)) { return value.map(nullifyProto) } - const result: Record = { __proto__: null } + const result: Record = Object.create(null) for (const key in value) { if (Object.hasOwn(value, key)) { result[key] = nullifyProto((value as Record)[key]) From ac9a8caafd2177b2a1baaeec36d1a0c1bfb5a8b8 Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Sun, 3 May 2026 12:23:22 +0200 Subject: [PATCH 3/3] Fix lint issues --- src/nodes/Pair.ts | 4 +++- src/nodes/YAMLMap.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nodes/Pair.ts b/src/nodes/Pair.ts index d371382b..72f642c3 100644 --- a/src/nodes/Pair.ts +++ b/src/nodes/Pair.ts @@ -36,7 +36,9 @@ export class Pair< doc: Document, ctx: ToJSContext ): ReturnType { - const pair = ctx.mapAsMap ? new Map() : Object.create(null) + const pair = ctx.mapAsMap + ? new Map() + : (Object.create(null) as Record) return addPairToJSMap(doc, ctx, pair, this) } diff --git a/src/nodes/YAMLMap.ts b/src/nodes/YAMLMap.ts index ab60353c..c0e9f86b 100644 --- a/src/nodes/YAMLMap.ts +++ b/src/nodes/YAMLMap.ts @@ -239,7 +239,7 @@ export class YAMLMap< ? new Type() : ctx?.mapAsMap ? new Map() - : Object.create(null) + : (Object.create(null) as Record) if (this.anchor) ctx.setAnchor(this, map) for (const pair of this.values.values()) addPairToJSMap(doc, ctx, map, pair) return map