Back to Blog
high SEVERITY3 min read

load_localStorage.js JSON Parse: Prototype Pollution via __proto__

The load_localStorage.js utility parsed JSON configuration without validating keys, permitting prototype pollution through malicious `__proto__`, `constructor`, or `prototype` properties. An attacker with filesystem access could poison downstream JavaScript execution by injecting these special keys into the loaded data structure.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 25, 2026•Reviewed September 25, 2026

Answer Summary

The load_localStorage.js JSON loader in unpatched versions accepts arbitrary object keys without validation. An attacker with write access to localStorage.json can inject `__proto__` properties that pollute Object.prototype, affecting all subsequent object operations in the browser context. The fix introduces an `isUnsafeKey()` filter and explicit object reconstruction at the deserialization boundary. CWE-502 (Deserialization of Untrusted Data).

Vulnerability at a Glance

cweunknown (CWE-502 referenced in PR)
fixExplicit key filtering with isUnsafeKey() check and bounded object reconstruction
riskPrototype pollution poisoning application-wide object behavior
languageJavaScript
root causeDirect assignment of parsed JSON keys without filtering prototype-reserved names
vulnerabilityUnsafe JSON deserialization with prototype pollution

Affected Versions

Affected unpatched versions prior to harden commit
Fixed in harden: use safe deserialization in load_localStorage.js (CWE-502)
Ecosystem not applicable (first-party code)
CVE / GHSA not assigned
CWE unknown (CWE-502 referenced in PR)

The Vulnerability Explained

The loadJsonAndStoreInLocalStorage() function fetched and parsed JSON data using a bare response.json() call:

cachedData = await response.json();

This direct assignment created a vulnerability surface. When the parsed data contained keys like __proto__, constructor, or prototype, JavaScript's object property resolution would traverse the prototype chain. The subsequent storage via localStorage.setItem() and downstream processing could propagate this pollution throughout the application.

Consider an attacker who gains write access to localStorage.json (through path traversal, compromised build pipeline, or local filesystem manipulation). They could inject:

{"__proto__": {"polluted": true}, "legitimate": "data"}

When parsed and processed, this doesn't merely set a property named "__proto__" — in many JavaScript contexts, it can modify Object.prototype.polluted, affecting every object created afterward. The cachedData variable, populated directly from the parse result, became a vehicle for prototype pollution that could alter application behavior far from the ingestion point.

The real-world impact depends on how the stored data is consumed. If downstream code checks for properties without hasOwnProperty() guards, or if the polluted prototype affects internal JavaScript operations, the attacker gains subtle but pervasive control over execution flow.

The Fix

The fix introduces defense-in-depth through explicit validation and bounded reconstruction:

Before:

cachedData = await response.json();

After:

const parsedData = await response.json();
if (typeof parsedData !== 'object' || parsedData === null || Array.isArray(parsedData)) {
    throw new Error('Invalid localStorage.json: expected a plain object');
}
cachedData = {};
for (const key of Object.keys(parsedData)) {
    if (isUnsafeKey(key)) continue;
    cachedData[key] = parsedData[key];
}

The isUnsafeKey() function provides the primary security boundary:

function isUnsafeKey(key) {
    return key === '__proto__' || key === 'constructor' || key === 'prototype';
}

This change solves the problem through three specific mechanisms:

  1. Schema validation: The type check ensures parsedData is a plain object, rejecting arrays and null values that would break downstream expectations.

  2. Key filtering: By iterating Object.keys() rather than using object spread or direct assignment, the code avoids triggering prototype chain lookups during property access.

  3. Object reconstruction: Creating a fresh cachedData object with only explicitly allowed keys isolates the application from any prototype pollution that might exist in the parsed structure itself.

The continue statement on unsafe keys silently drops them — a deliberate choice that prevents injection without crashing legitimate operation.

Key Takeaways

  • Never assign parsed JSON directly to variables used across security boundaries — always validate structure and filter keys against prototype-reserved names.
  • __proto__, constructor, and prototype are unsafe as object keys in any context where the object will be used with standard property access patterns.
  • Object reconstruction beats sanitization — building a new object with known-safe keys is more reliable than attempting to delete or modify properties on a potentially polluted object.
  • Defense-in-depth at deserialization boundaries prevents cascading failures when upstream trust assumptions break down.

How Orbis AppSec Detected This

Source: The response.json() call ingesting data from the network or filesystem

Sink: The cachedData variable subsequently used with localStorage.setItem() and downstream property access

Missing control: No validation of parsed structure or filtering of prototype-reserved key names (__proto__, constructor, prototype)

CWE: CWE-502 (Deserialization of Untrusted Data) — though the PR notes this as defense-in-depth rather than an exploitable vulnerability in this specific context

Fix: Added isUnsafeKey() predicate with explicit object reconstruction, rejecting __proto__, constructor, and prototype keys at the deserialization boundary

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This fix demonstrates how a small deserialization utility can become a critical security boundary. The loadJsonAndStoreInLocalStorage() function's direct assignment pattern — common in JavaScript codebases — silently accepted prototype pollution vectors. The explicit isUnsafeKey() filter and bounded object reconstruction transform an implicit trust assumption into an explicit security boundary, making the failure mode "fail-closed" rather than "fail-polluted."

Prevention and further reading

Frequently Asked Questions

Why does the fix reconstruct `cachedData` as a new object rather than just deleting unsafe keys from the parsed result?

Deleting `__proto__` from an object doesn't remove the prototype chain itself; creating a fresh object with only safe keys guarantees isolation from the original polluted structure.

Which three specific strings trigger the `isUnsafeKey()` filter?

`__proto__`, `constructor`, and `prototype` — all properties that can reach Object.prototype when used as object keys.

Does the `Array.isArray()` check in the fix prevent any exploitable behavior, or is it defense-in-depth?

It prevents a class of errors where downstream code expects object methods (like `Object.keys()`) on what would become array-indexed data, but the primary security boundary is the `isUnsafeKey()` filter.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #168

Related Articles

high

js-yaml 5.2.1 DoS: Exponential Parsing in Flow Collections

A denial-of-service vulnerability in js-yaml 5.2.1 allows an attacker to crash the parser by supplying deeply nested flow collections that trigger exponential parsing behavior. The fix in version 5.2.2 improves the parser's handling of these structures, preventing the algorithmic complexity attack. This is critical for any service accepting user-controlled YAML input.

high

Anthropic API Adapter Prototype Pollution in parseToolCallInput

The `parseToolCallInput` function in the Anthropic API adapter used `JSON.parse` without protecting against prototype pollution keys. An attacker who could manipulate API responses—through a man-in-the-middle attack, compromised Codex backend, or DNS spoofing—could inject `__proto__`, `constructor`, or `prototype` properties to pollute JavaScript's Object prototype and affect extension runtime behavior.

critical

How Arbitrary Code Execution Happens in protobufjs and How to Fix It

CVE-2026-41242 is a critical vulnerability in protobufjs versions 8.0.0 and earlier that allows attackers to execute arbitrary code by injecting malicious type fields into protobuf definitions. The fix upgrades the dependency from `^8.0.0` to `^8.6.6` in `core/package.json`, eliminating the unsafe code path that processed attacker-controlled type metadata without validation.

critical

How Type Confusion Vulnerabilities Happen in JavaScript Dependencies and How to Fix Them

A critical type confusion vulnerability (CVE-2021-23436) was discovered in immer 9.0.7, a popular immutable state management library used in the client application. By upgrading to immer 9.0.6, the vulnerability was patched, eliminating a flaw that could have allowed attackers to bypass previous security fixes (CVE-2020-28477). This fix demonstrates why keeping dependencies current is essential for maintaining application security.

critical

How Prototype Pollution Happens in i18next-fs-backend and How to Fix It

A critical prototype pollution vulnerability (CVE-2026-48713) was discovered in i18next-fs-backend versions prior to 2.6.6, where specially crafted missing-key strings could pollute the JavaScript object prototype. This fix upgrades the dependency to patch the vulnerability and prevent attackers from injecting malicious properties into application objects.

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.