diff --git a/.env b/.env index 0ef6c7bb..b9e7e0dd 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -VERSION="2.8.1" +VERSION="2.8.2" MAJOR=2 MINOR=8 -PATCH=1 \ No newline at end of file +PATCH=2 \ No newline at end of file diff --git a/Core/Meta/BebopSchema.cs b/Core/Meta/BebopSchema.cs index 08c3f70e..efcbdd8b 100644 --- a/Core/Meta/BebopSchema.cs +++ b/Core/Meta/BebopSchema.cs @@ -163,7 +163,7 @@ public List Validate() { errors.Add(new MultipleDefinitionsException(definition)); } - if (ReservedWords.Identifiers.Contains(definition.Name)) + if (ReservedWords.Identifiers.Contains(definition.Name, StringComparer.OrdinalIgnoreCase)) { errors.Add(new ReservedIdentifierException(definition.Name, definition.Span)); } diff --git a/Core/Meta/WellKnownTypes.cs b/Core/Meta/WellKnownTypes.cs index ccf2daa5..39dc4476 100644 --- a/Core/Meta/WellKnownTypes.cs +++ b/Core/Meta/WellKnownTypes.cs @@ -21,7 +21,10 @@ public static class ReservedWords "BebopMirror", "BebopConstants", "BopConstants", - "Service" + "Service", + "prototype", + "constructor", + "__proto__" }; } diff --git a/Laboratory/Schemas/ShouldFail/reserved.bop b/Laboratory/Schemas/ShouldFail/reserved.bop new file mode 100644 index 00000000..2db823e2 --- /dev/null +++ b/Laboratory/Schemas/ShouldFail/reserved.bop @@ -0,0 +1,5 @@ +struct BebopRecord { + string prototype; + string constructor; + string __proto__; +} \ No newline at end of file diff --git a/Runtime/TypeScript/index.test.ts b/Runtime/TypeScript/index.test.ts index 8c0356dc..0d87cea6 100644 --- a/Runtime/TypeScript/index.test.ts +++ b/Runtime/TypeScript/index.test.ts @@ -1,4 +1,10 @@ -import { BebopTypeGuard, BebopRuntimeError, BebopJson, GuidMap, Guid } from "./index"; +import { + BebopTypeGuard, + BebopRuntimeError, + BebopJson, + GuidMap, + Guid, +} from "./index"; import { describe, expect, it } from "vitest"; @@ -73,6 +79,13 @@ describe("BebopJson", () => { expect(() => BebopJson.ensureKeysExist(["a.b.c", "e"], obj)).toThrow(); }); }); + + describe("security checks", () => { + it("should not be vulnerable to prototype pollution", () => { + const json = '{"user":{"__proto__":{"admin": true}}}'; + expect(() => JSON.parse(json, BebopJson.reviver)).toThrow(BebopRuntimeError); + }); + }); }); describe("BebopTypeGuard", () => { diff --git a/Runtime/TypeScript/index.ts b/Runtime/TypeScript/index.ts index 05925aff..d01c4e44 100644 --- a/Runtime/TypeScript/index.ts +++ b/Runtime/TypeScript/index.ts @@ -809,8 +809,8 @@ const replacer = (_key: string, value: any): any => { * @returns The modified value for the property, or the original value if not a marked type. */ const reviver = (_key: string, value: any): any => { + if (_key === "__proto__" || _key === "prototype" || _key === "constructor") throw new BebopRuntimeError("potential prototype pollution"); if (_key === "") return value; - if (value && typeof value === "object") { if (value[typeMarker]) { switch (value[typeMarker]) { @@ -846,6 +846,7 @@ const reviver = (_key: string, value: any): any => { } } else { for (let k in value) { + if (k === "__proto__" || k === "prototype" || k === "constructor") throw new BebopRuntimeError("potential prototype pollution"); const v = value[k]; if (!isPrimitive(v)) { value[k] =reviver(k, v);