Back to Blog
critical SEVERITY6 min read

BFF Proxy QR Code Endpoint Prototype Pollution via Unvalidated JSON

A critical prototype pollution vulnerability in a backend-for-frontend (BFF) proxy endpoint allowed attackers to inject malicious properties into the JavaScript Object prototype by crafting JSON requests with forbidden keys. This could compromise application behavior across all objects. The fix adds explicit key validation to reject payloads containing `__proto__`, `constructor`, or `prototype`.

O
By Orbis AppSec
•Published September 28, 2026•Reviewed September 28, 2026

Answer Summary

The affected code is a JSON request handler in a BFF proxy edge function that processes requests to a QR code generation endpoint. An attacker could craft a POST request with a JSON body containing `__proto__` or similar keys to pollute the Object prototype, potentially affecting all downstream object operations in the application. The fix adds a runtime check to reject bodies containing these forbidden keys before they are processed. The vulnerability falls under CWE-915 (improper control of dynamically-managed code resources).

Vulnerability at a Glance

cweCWE-915 (Improperly Controlled Modification of Dynamically-Managed Code Resources)
fixValidate that parsed request body does not contain __proto__, constructor, or prototype keys
riskAttackers can inject properties into Object.prototype, affecting all objects in the application runtime
languageJavaScript
root causeJSON request body parsed and used without filtering dangerous prototype-manipulation keys
vulnerabilityPrototype pollution via unvalidated JSON object keys

Introduction

A critical prototype pollution vulnerability existed in a backend-for-frontend (BFF) proxy edge function that handles requests to a QR code generation endpoint. The flaw was in how the endpoint parsed incoming JSON request bodies: while it performed a basic type check to ensure the body was an object, it did not filter or validate the keys themselves. This meant an attacker could send a JSON payload with keys like __proto__, constructor, or prototype, which JavaScript treats as special handles to the Object prototype. By exploiting this, an attacker could inject arbitrary properties into the global Object prototype, affecting every object created or used by the application for the duration of that request or longer.

This is particularly dangerous in serverless and edge-computing contexts, where function instances may be reused across multiple requests, allowing a single malicious request to potentially poison an instance serving subsequent users.

Affected Versions

Affected Not applicable (first-party code)
Fixed in Not applicable (first-party code)—fix applied in PR
Ecosystem Node.js / Netlify Edge Functions
CVE / GHSA Not assigned
CWE CWE-915 (Improperly Controlled Modification of Dynamically-Managed Code Resources)

The Vulnerability Explained

The vulnerable code was a request handler for a QR code generation endpoint that accepted JSON bodies directly from clients. Here is the problematic logic:

qrBody = await request.json();
if (qrBody === null || typeof qrBody !== 'object') {
  throw new Error('body must be a JSON object');
}

The handler checked that qrBody was not null and that its type was 'object', but it performed no validation on the keys inside that object. In JavaScript, certain keys are reserved as prototypes or prototype chain accessors:

  • __proto__ is the dunder-proto property, a direct reference to an object's [[Prototype]]
  • constructor accesses the constructor function, which itself has a prototype property
  • prototype directly references the prototype object

If an attacker sent a request like:

POST /bff/qrcode-generate
Content-Type: application/json

{
  "__proto__": {
    "isAdmin": true,
    "canDelete": true
  }
}

The parsed qrBody object would contain these keys. If the downstream code then used this object in any object-oriented operation—such as iterating its keys, assigning it to another object, or using it as a template—the JavaScript runtime would apply the prototype chain modifications. Every subsequent object created in that execution context would inherit the poisoned properties:

const someUser = { name: "alice" };
console.log(someUser.isAdmin); // true — inherited from polluted prototype!

In a QR code generation context, this could be leveraged to:

  1. Escalate an unprivileged request to admin if role checks use prototype-inherited properties
  2. Bypass permission checks if canDelete or similar flags are checked against all objects
  3. Inject malicious functions into the prototype, causing code execution when inherited methods are called

The Fix

The fix adds two critical safeguards:

if (qrBody === null || typeof qrBody !== 'object' || Array.isArray(qrBody)) {
  throw new Error('body must be a JSON object');
}
if (['__proto__', 'constructor', 'prototype'].some(key => Object.prototype.hasOwnProperty.call(qrBody, key))) {
  throw new Error('body contains forbidden keys');
}

Change 1: Reject arrays.
The first addition checks Array.isArray(qrBody) to ensure the body is a plain object, not an array. While arrays are technically objects in JavaScript, they should not be accepted as request bodies for a QR code endpoint and could be used in alternative prototype pollution attacks.

Change 2: Whitelist-validate keys.
The second addition uses Object.prototype.hasOwnProperty.call(qrBody, key) to check if the parsed body directly owns any of the three forbidden keys. This is critical:

  • It checks the object's own properties, not inherited ones, using hasOwnProperty
  • It uses the call() method to ensure we check against the true Object.prototype, not a polluted one
  • It rejects the entire request if any of these keys are present, failing safely

This approach is whitelist-based: rather than allowing anything and trying to sanitize it, we explicitly define what we will not accept. If a request contains __proto__, it is rejected outright at the entry point, before any downstream code can be affected.

Key Takeaways

  • Prototype pollution is a parser-level threat in JavaScript. Unlike SQL injection or command injection, it doesn't require a downstream sink—the mere presence of a forbidden key in a parsed object can cause harm if that object is ever used in a context where prototype chain semantics apply.

  • Type checks alone are insufficient. Validating that typeof obj === 'object' does not tell you whether the object's keys are safe. You must validate the keys themselves.

  • Use hasOwnProperty with .call() for safety. Calling hasOwnProperty directly on an untrusted object (e.g., qrBody.hasOwnProperty('__proto__')) could be subverted if the object itself overrides hasOwnProperty. Using Object.prototype.hasOwnProperty.call(qrBody, key) ensures you check against the true base Object prototype.

  • Reject at the entry point. Catching prototype pollution at the request parsing stage prevents the corrupted object from ever propagating to downstream code, where the damage could be harder to detect or undo.

  • Edge functions and serverless environments are high-risk contexts. If function instances are reused across requests, a prototype pollution attack in one request can poison the environment for subsequent requests from other users.

How Orbis AppSec Detected This

Source: The request.json() method parsing untrusted client-supplied JSON into the qrBody variable in the QR code generation handler.

Sink: Any subsequent use of qrBody in object operations, property assignments, or property checks where prototype chain lookup would occur.

Missing control: No validation of qrBody's keys. The code performed a type check (typeof qrBody === 'object') but did not filter out prototype-manipulation keys before allowing the object to be used.

CWE: CWE-915 — Improperly Controlled Modification of Dynamically-Managed Code Resources.

Fix: Added a post-parse validation check that explicitly rejects request bodies containing the keys __proto__, constructor, or prototype, rejecting the entire request with a 400 error if any are found.

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

Prototype pollution is a deceptively dangerous vulnerability in JavaScript because it operates at the language semantics level—no special sinks or dangerous functions are required, just the presence of malicious keys in a parsed object. The QR code endpoint's failure to validate input object keys created an entry point for attackers to poison the Object prototype, potentially compromising the integrity of every object in the application.

The fix is surgical and effective: reject requests containing forbidden keys at the parser boundary. This is a reminder that in JavaScript, input validation must consider not just types and values, but the structure and semantics of objects themselves. When parsing untrusted JSON, especially in serverless or edge contexts where function state may persist, always ask: What keys should this object contain, and what keys should it never contain?

Prevention and further reading

Frequently Asked Questions

What happens if an attacker sends `{"__proto__": {"isAdmin": true}}` to the QR code endpoint before the fix?

The JSON parser would accept the key, and if the handler passed this object to any downstream code that accessed object properties, the injected `isAdmin` property would be present on all objects in that execution context.

Why does the fix reject both `constructor` and `prototype` in addition to `__proto__`?

All three keys are gadgets for prototype pollution in JavaScript. `constructor.prototype` can be chained to reach Object.prototype, and `prototype` itself allows direct manipulation. Rejecting all three prevents multiple attack vectors.

Does this fix break legitimate use cases where a user might intentionally send these field names?

No legitimate QR code generation request would require these keys. If actual application data needs these names, they should be remapped to safe aliases before reaching this endpoint, following a principle of input remapping rather than blind passthrough.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #112

Related Articles

high

smol-toml 1.7.0 DoS: Malformed TOML Documents Crash Parser

A denial-of-service vulnerability in smol-toml 1.7.0 allows attackers to crash the parser by supplying malformed TOML documents. The vulnerability affects any application that parses untrusted TOML input. The fix, available in smol-toml 1.7.1, hardens input validation and error recovery.

high

JOSMFileHack TransformerFactory XXE: External DTD Processing Enabled

OSM2World's JOSMFileHack utility, which processed OpenStreetMap files generated by the JOSM editor, contained an insecure TransformerFactory configuration that permitted external DTD and stylesheet access. The vulnerability was resolved by completely removing the vulnerable code path rather than hardening it in place.

critical

LDAP Filter Injection in da_unique_email_validator Fixed

The registration-time email uniqueness validator, `da_unique_email_validator`, formatted the submitted email address straight into an LDAP search filter with Python's `%` operator, so filter metacharacters in the email were interpreted as filter syntax. The fix wraps the value in `ldap.filter.escape_filter_chars()` (and imports the `ldap.filter` submodule explicitly), so a submitted address is always treated as a literal attribute value. Any deployment with `ldap login` enabled and a bind accoun

high

installPlugin(): Unvalidated npm Package Names Reach npm install

A plugin manager service exposed an `installPlugin(plugin: PluginInfo)` method that passed `plugin.packageName` and `plugin.version` straight into the platform's npm install routine with no validation, no blocklist, and no integrity verification of the fetched tarball. Because npm treats a non-semver "version" as a fetch specifier — a tarball URL, a git ref, a local path — an attacker who could influence the plugin listing could get arbitrary code installed and executed with full Electron/Node p

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

critical

Updater.parseUpdate() CWE-494: Unsigned Metadata Download

The parseUpdate function in the Updater component extracted download URLs from remote server responses without cryptographic verification, enabling supply chain attacks via compromised or spoofed update servers. The fix adds strict URL validation requiring HTTPS and a trusted hostname before accepting any update metadata.