The Ravens settings page merges the JSON you send into the settings object with an unguarded recursive merge. If your input carries the special __proto__ key you pollute the shared prototype, so every fresh panel sees itself as admin.
A settings page merges JSON into the settings object, and a button opens a fresh panel that reads isAdmin from a new object. The new object has no own isAdmin, so it inherits from the prototype; pollute it and the panel becomes an admin panel.
Pollute the prototype so a fresh panel becomes an admin panel.
You polluted the prototype and unlocked the admin panel.
Why the solution worked
The recursive merge wrote into the shared prototype through the __proto__ key, so every new object inherits the polluted property. The fix is blocking __proto__, constructor and prototype keys and using Map or null-prototype objects.
The root cause
A recursive merge writes the __proto__ key into the prototype. The root is trusting user object keys without blocking the dangerous ones.
How the code is fixed
Block __proto__, constructor and prototype in merges and use a Map or a null-prototype object.
Vulnerable code
function merge(target, source) {
for (const key in source) {
if (typeof source[key] === 'object') merge(target[key] ||= {}, source[key]);
else target[key] = source[key]; // __proto__ flows straight through
}
}
Safe code
const DANGEROUS = new Set(['__proto__', 'constructor', 'prototype']);
function merge(target, source) {
for (const key of Object.keys(source)) {
if (DANGEROUS.has(key)) continue; // never merge prototype-reaching keys
if (isPlainObject(source[key])) merge(target[key] ||= {}, source[key]);
else target[key] = source[key];
}
}
Impact in the real world
Prototype pollution changes the behaviour of the whole app and can lead to privilege bypass or, in some environments, code execution.
How defenders detect it
Defenders watch inputs for __proto__ and constructor keys and test that object properties did not change after a merge.