Skip to content

Commit a786bc6

Browse files
committed
fix: block unsafe pointer set tokens
1 parent fddef8a commit a786bc6

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

lib/pointer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const slashes = /\//g;
1313
const tildes = /~/g;
1414
const escapedSlash = /~1/g;
1515
const escapedTilde = /~0/g;
16+
const unsafeSetTokens = new Set(["__proto__", "constructor", "prototype"]);
1617

1718
/**
1819
* This class represents a single JSON pointer and its resolved value.
@@ -194,6 +195,8 @@ class Pointer<S extends object = JSONSchema, O extends ParserOptions<S> = Parser
194195
return value;
195196
}
196197

198+
assertSafeSetTokens(this.path, tokens);
199+
197200
// Crawl the object, one token at a time
198201
this.value = unwrapOrThrow(obj);
199202
if (this.$ref.dynamicIdScope && !isAliasedResource(this.$ref)) {
@@ -372,6 +375,16 @@ function setValue(pointer: Pointer, token: string, value: JSONSchema4Type | JSON
372375
return value;
373376
}
374377

378+
function assertSafeSetTokens(pointerPath: string, tokens: string[]) {
379+
for (const token of tokens) {
380+
if (unsafeSetTokens.has(token)) {
381+
throw new JSONParserError(
382+
`Error assigning $ref pointer "${pointerPath}". \nUnsafe JSON Pointer token "${token}" cannot be used for assignment.`,
383+
);
384+
}
385+
}
386+
}
387+
375388
function unwrapOrThrow(value: unknown) {
376389
if (isHandledError(value)) {
377390
throw value;

test/specs/refs.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,5 +316,28 @@ describe("$Refs object", () => {
316316
$refs.set("external.yaml#/foo/bar/baz", { hello: "world" });
317317
expect($refs.get("external.yaml#/foo/bar/baz")).to.deep.equal({ hello: "world" });
318318
});
319+
320+
it("should reject prototype-polluting JSON Pointer path tokens", async () => {
321+
const $refs = await $RefParser.resolve(path.abs("test/specs/external/external.yaml"));
322+
const dangerousPaths = [
323+
"external.yaml#/__proto__/polluted",
324+
"external.yaml#/constructor/prototype/polluted",
325+
"external.yaml#/prototype/polluted",
326+
];
327+
328+
for (const $ref of dangerousPaths) {
329+
try {
330+
$refs.set($ref, "polluted");
331+
helper.shouldNotGetCalled();
332+
} catch (err) {
333+
expect(err).to.be.an.instanceOf(Error);
334+
expect((err as Error).message).to.contain("Unsafe JSON Pointer token");
335+
} finally {
336+
delete (Object.prototype as Record<string, unknown>).polluted;
337+
}
338+
339+
expect(({} as Record<string, unknown>).polluted).to.be.undefined;
340+
}
341+
});
319342
});
320343
});

0 commit comments

Comments
 (0)