diff --git a/packages/solid/store/src/store.ts b/packages/solid/store/src/store.ts index 3276b3a6d..236d07ee9 100644 --- a/packages/solid/store/src/store.ts +++ b/packages/solid/store/src/store.ts @@ -215,6 +215,13 @@ export function setProperty( value: any, deleting: boolean = false ): void { + // Prototype pollution guard: refuse to redefine the prototype chain via + // `state.__proto__ = ...` or to overwrite built-in prototype links. + if (property === "__proto__") { + if (IS_DEV) + console.warn(`Refusing to set "__proto__" on a store (prototype pollution guard).`); + return; + } if (!deleting && state[property] === value) return; const prev = state[property], len = state.length; @@ -274,6 +281,21 @@ export function updatePath(current: StoreNode, path: any[], traversed: PropertyK const partType = typeof part, isArray = Array.isArray(current); + // Prototype pollution guard: refuse to traverse into dangerous keys + // (e.g. `setStore("__proto__", ...)` or + // `setStore("constructor", "prototype", ...)`), which would otherwise + // let callers reach and mutate Object.prototype / Function.prototype. + if ( + partType === "string" && + (part === "__proto__" || part === "constructor" || part === "prototype") + ) { + if (IS_DEV) + console.warn( + `Refusing to traverse into "${part}" on a store (prototype pollution guard).` + ); + return; + } + if (Array.isArray(part)) { // Ex. update('data', [2, 23], 'label', l => l + ' !!!'); for (let i = 0; i < part.length; i++) { diff --git a/packages/solid/store/test/store.spec.ts b/packages/solid/store/test/store.spec.ts index 6fc6e28c1..1d8736448 100644 --- a/packages/solid/store/test/store.spec.ts +++ b/packages/solid/store/test/store.spec.ts @@ -799,6 +799,33 @@ describe("In Operator", () => { }); }); +describe("Prototype pollution guard", () => { + test("setStore cannot pollute Object.prototype via __proto__ path", () => { + const [, setStore] = createStore>({ a: 1 }); + setStore("__proto__", "polluted_a", true); + expect(({} as any).polluted_a).toBeUndefined(); + }); + + test("setStore cannot pollute Object.prototype via __proto__ merge", () => { + const [, setStore] = createStore>({ a: 1 }); + setStore("__proto__", { polluted_b: true }); + expect(({} as any).polluted_b).toBeUndefined(); + }); + + test("setStore cannot pollute via constructor.prototype", () => { + const [, setStore] = createStore>({ a: 1 }); + setStore("constructor", "prototype", "polluted_c", true); + expect(({} as any).polluted_c).toBeUndefined(); + }); + + test("setStore cannot pollute via JSON-parsed __proto__ own property merge", () => { + const [, setStore] = createStore>({ a: 1 }); + const evil = JSON.parse('{"__proto__": {"polluted_d": true}}'); + setStore(evil); + expect(({} as any).polluted_d).toBeUndefined(); + }); +}); + // type tests // NotWrappable keys are ignored