//nefariousplan

Prototype Pollution Trust Bypass

A trust check that reads shared prototype state. Attacker writes once, every object reports trusted.

JavaScript has a feature that, read through a security lens, is a shared global mutable state that every object in the runtime inherits from. Write to it once, every object in the program reports the change. The feature is Object.prototype. It is how the language does method inheritance, default behavior, and duck-typed polymorphism. It is also how attacker-controlled input can reach into a privileged component's trust decision and flip every check to pass.

This pattern is what happens when a security gate reads properties off an object to decide whether to permit a privileged action, and an attacker has poisoned the prototype those properties inherit from. The gate queries, say, user.admin. The object the gate was handed does not have an admin property directly. The lookup falls through to Object.prototype.admin, which the attacker wrote to earlier by smuggling a __proto__ field into a parsed JSON payload. The gate reads true. The privileged action fires. The authentication system did exactly what it was designed to do.

Mechanism

The pattern operates on three assumptions most JavaScript code makes without thinking. First, that properties read off an object came from the object itself. They did not; they came from the prototype chain, and the prototype chain can be reached from user input via __proto__ keys. Second, that JSON.parse produces safe values. It does not in any runtime that routes __proto__ through the normal property setter. Third, that an object created from user input is isolated from the rest of the runtime. It is not; it shares Object.prototype with every other object in the process.

The attack shape is consistent across specific CVEs. An attacker-controlled JSON document includes a __proto__ key with an object value. The server parses it into an options object, a config, a parameter bag. Somewhere in the code path, a library function merges that input into a new object using a recursive assign-like helper that treats __proto__ as a normal key. The merge walks into Object.prototype and sets the attacker's fields. Every object in the process now inherits those fields. The security gate downstream reads user.admin against a fresh object that has no admin of its own, gets true via the prototype, permits the privileged action. The timing is deterministic, the reliability is 100%, and the attack surface is every library that merges user JSON into internal structures.

What makes this distinct from generic prototype pollution is the TARGET. Many prototype pollution CVEs are denial of service or unexpected behavior. Trust-bypass specifically describes cases where a security-sensitive property (admin, role, allowed, privileged, trusted, authenticated) is reachable via prototype lookup and the runtime's authentication reads it without defense. The pollution is not the vulnerability. The trust decision that reads from the polluted prototype is the vulnerability.

Exhibits

CVE-2026-34621: Adobe Acrobat's Privilege Gate Inherits What It Checks. Acrobat's JavaScript environment has a privilege gate that reads properties off an object to determine whether a script may invoke privileged operations. The attacker's PDF embeds a script that pollutes the prototype, causing the properties the gate consults to evaluate as permitting the privileged call. The gate is not broken; it is reading exactly what the runtime has in the prototype chain. The attack succeeds because the gate's trust decision is built on property-read semantics that the attacker reached from the other side. The post walks through the specific property names the gate queries and shows the JSON payload that sets them.

Exhibits

CVE-2026-34621: Adobe Acrobat's Privilege Gate Inherits What It Checks. Acrobat's JavaScript environment has a privilege gate that reads properties off an object to determine whether a script may invoke privileged operations. The attacker's PDF embeds a script that pollutes the prototype, causing the properties the gate consults to evaluate as permitting the privileged call. The gate is not broken; it is reading exactly what the runtime has in the prototype chain. The attack succeeds because the gate's trust decision is built on property-read semantics that the attacker reached from the other side. The post walks through the specific property names the gate queries and shows the JSON payload that sets them.

Boundaries

Not every prototype pollution. Many pollution CVEs change program behavior without bypassing a trust check. An attacker who sets Object.prototype.length = -1 breaks iterators; that is denial of service, not trust-bypass. The pattern specifically requires that a SECURITY GATE reads the polluted property and authorizes an action based on it.

Not every JavaScript privilege escalation. Other mechanisms exist: unsafe eval, broken vm sandboxes, leaked capabilities, prototype pollution through constructors rather than through __proto__. The pattern is the specific shape where an __proto__ path flips a property that a trust check later consults.

Not specific to JavaScript in principle. Any runtime with a shared fallback lookup mechanism can produce the same attack if a security check consults the fallback. Python's __class__ mutation, Ruby's open-class, Lua's metatables all support shapes that rhyme. Most mainstream runtimes have defaults that make this specific attack harder; JavaScript's are among the most permissive, which is why this pattern shows up disproportionately in JS stacks.

Defender playbook

Do not make trust decisions based on property presence or value on objects that may have been constructed from attacker-controlled JSON. If the gate reads user.admin, the gate is trusting whatever the prototype chain returns. The defense is to build the trust decision around a controlled data structure, not around property reads on a general object.

Use Object.create(null) for security-sensitive lookups. Objects created with a null prototype have no prototype chain to poison. A role-checking map built on Object.create(null) returns undefined for missing keys instead of falling through to whatever the attacker wrote to Object.prototype.

Parse incoming JSON into a Map rather than into a plain object for fields that will feed security decisions. new Map(Object.entries(json)) gives you key lookup without the prototype mechanics. The Map does not care what was written to Object.prototype.

Freeze Object.prototype at startup in hardened environments. Object.freeze(Object.prototype) on first tick blocks late writes from any merge helper, at the cost of making some libraries that depend on mutating prototypes misbehave. For server-side JavaScript with a strict dependency audit, the tradeoff is usually right.

Audit the merge helpers your stack uses. lodash.merge, Object.assign with deep-merge wrappers, and many in-house deep-merge implementations are the most common entry points for this pattern. Versions that explicitly reject __proto__ and constructor keys are the defense; many older versions and hand-rolled merges do not.

Kinship

Trust Inversion. Specific locus. The authorizer is the property-read in the security gate; the prototype chain is the inverted trust mechanism. Prototype-pollution-trust-bypass is trust inversion at the JavaScript property-lookup layer.

Content Is Command. The JSON payload is content. The runtime's property-lookup semantics interpret the __proto__ key as a command to mutate shared state. The content is making commands across the process because the parser treats attacker-chosen keys as legitimate schema fields.

Fail Open Intercept. Adjacent class of security gate failure. Fail-open-intercept describes gates that permit forbidden actions when they cannot validate. Prototype-pollution-trust-bypass describes gates that permit forbidden actions because the property they read was poisoned. Both are security gates failing to protect; the mechanisms are different, the defender response (rewrite the gate's trust basis) is similar.

The gate read true. Every gate that runs that check will read true. The attacker did not bypass the gate; the gate's own definition of truth is now the attacker's.