From 752fb476eb5b058d99edc382a4bdec1ca2f19b38 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Tue, 2 Jun 2026 23:06:49 +0200 Subject: [PATCH] refactor(devtools): Harden props navigation To prevent any unwanted access/changes to some props like the `prototype` or `constructor` --- .../src/lib/property-mutation.spec.ts | 14 ++++++++++++++ .../src/lib/property-mutation.ts | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.spec.ts index ada4a93cb20..22e6b1e4d54 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.spec.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.spec.ts @@ -327,6 +327,20 @@ describe('property-mutation', () => { ); }); + it('throw on mutating __proto__, constructor, or prototype properties', () => { + const obj = {foo: signal({})}; + + expect(() => mutateNestedProp(obj, ['foo', '__proto__'], {})).toThrowError( + /Access to property `__proto__` is blocked for security reasons./, + ); + expect(() => mutateNestedProp(obj, ['foo', 'constructor'], {})).toThrowError( + /Access to property `constructor` is blocked for security reasons./, + ); + expect(() => mutateNestedProp(obj, ['foo', 'prototype'], {})).toThrowError( + /Access to property `prototype` is blocked for security reasons./, + ); + }); + it('immutable updates objects with unrelated nested signals', () => { const obj = { foo: signal({ diff --git a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts index be991938ae3..ce112dc2712 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts @@ -122,6 +122,11 @@ function* getNestedProps( while (keys.length !== 0) { const key = keys.shift()!; + // Prevent Prototype Pollution + if (key === '__proto__' || key === 'constructor' || key === 'prototype') { + throw new Error(`Access to property \`${key}\` is blocked for security reasons.`); + } + if (Array.isArray(receiver) && parseInt(key) >= receiver.length) { throw new Error(`Cannot access index ${key} for array of length ${receiver.length}.`); }