From 1b731739bb658b81bcfe612f820d7679f93431b7 Mon Sep 17 00:00:00 2001 From: iaohkut Date: Mon, 13 Jul 2026 11:04:48 -0400 Subject: [PATCH] fix: prevent prototype hijack in theme safeMerge (CWE-1321) safeMerge() recursively merges component `theme` overrides using bracket-notation assignment (`out[key] = ...`). Unlike object-literal spread, bracket assignment invokes the `Object.prototype.__proto__` accessor, so a crafted overrides object with an own `__proto__` key (e.g. produced by JSON.parse of an external theme config) hijacks the prototype of the merged object instead of being stored as a normal property. Add a denylist for `__proto__`/`constructor`/`prototype` keys so they are skipped during the merge, matching the function's stated intent of being a *safe* merge. Verified the fix blocks the hijack while preserving legitimate deep-merge behavior and PlatformColor sentinel handling; full existing test suite passes. Co-Authored-By: Claude Sonnet 5 --- src/theme/provider.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/theme/provider.tsx b/src/theme/provider.tsx index b4101eaa0f..4da4909183 100644 --- a/src/theme/provider.tsx +++ b/src/theme/provider.tsx @@ -29,6 +29,11 @@ export const isPlatformColorSentinel = (v: unknown): boolean => typeof v === 'object' && ('resource_paths' in v || 'semantic' in v || 'dynamic' in v); +// Keys that would otherwise let a crafted `overrides` object (e.g. parsed via +// `JSON.parse` from an external theme config) hijack the prototype of the +// merged output through bracket-notation assignment below. +const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']); + export const safeMerge = (base: T, overrides: unknown): T => { if ( !base || @@ -45,6 +50,9 @@ export const safeMerge = (base: T, overrides: unknown): T => { } const out: Record = { ...(base as Record) }; for (const key of Object.keys(overrides as Record)) { + if (UNSAFE_KEYS.has(key)) { + continue; + } out[key] = safeMerge( (base as Record)[key], (overrides as Record)[key]