Feb 26, 2026·5 min read·68 visits
dottie.js versions 2.0.4-2.0.6 contain a trivial bypass for a previous prototype pollution fix. By using a path like 'key.__proto__.polluted' instead of '__proto__.polluted', attackers can execute arbitrary code or cause denial of service. Fixed in v2.0.7.
A classic example of a failed patch. The popular dottie.js library attempted to fix a prototype pollution vulnerability by blocking malicious keys, but only checked the first segment of the property path. Attackers could simply nest their payload one level deep to bypass the check completely.
JavaScript developers love dot-notation helpers. Why write obj && obj.db && obj.db.config when you can just use dottie.get(obj, 'db.config')? Libraries like dottie.js exist to make accessing and mutating nested objects painless. But as we've seen time and time again in the Node.js ecosystem, convenience often comes at the cost of security.
This isn't a story about a new, groundbreaking exploit technique. It's a story about a "zombie" vulnerability—a bug that was supposedly killed, buried, and marked as fixed, only to crawl back out of the grave because the stake wasn't driven deep enough.
We are looking at CVE-2026-27837, a bypass of the patch for CVE-2023-26132. It highlights a critical lesson in input validation: if you are going to police user input, you have to check the entire payload, not just the first five characters.
To understand the bypass, we have to look at the original fix. In 2023, the maintainers of dottie.js realized that allowing users to set keys like __proto__ was dangerous. It enables Prototype Pollution, where an attacker modifies the base Object.prototype, affecting every object in the Node.js process.
Their solution was logical, but short-sighted. They added a guard clause that looked like this:
// The old, vulnerable check
if (pieces[0] === '__proto__') return;This code splits the input path (e.g., user.name) into an array (['user', 'name']) and checks the first element. If you try to pass __proto__.polluted, pieces[0] is __proto__, and the function returns. The front door is locked.
But the logic flaw here is assuming that the attack vector must be the root of the operation. dottie.js is recursive. It walks down the object tree. If an attacker provides a path like user.__proto__.isAdmin, the array becomes ['user', '__proto__', 'isAdmin'].
Here, pieces[0] is 'user'. The check passes. The library then happily traverses into user, then into __proto__ (which points to Object.prototype), and finally sets isAdmin on the global prototype. The bouncer checked the first guy in line, saw he was cool, and let the entire gang of hackers in behind him.
Exploiting this requires zero advanced knowledge of memory corruption or heap layouts. You just need to shift your malicious payload one index to the right.
Here is a functional Proof of Concept (PoC) demonstrating the bypass:
const dottie = require('dottie');
// A harmless looking object
const payload = {};
// The Attack: We prefix the malicious path with literally anything.
// The check `pieces[0] === '__proto__'` sees 'config' and allows it.
const maliciousPath = 'config.__proto__.polluted';
console.log("Before:", ({}).polluted); // undefined
try {
// dottie traverses 'config' (creates it), then accesses '__proto__',
// then sets 'polluted' on the global Object prototype.
dottie.set(payload, maliciousPath, "pwned");
} catch (e) {
console.log("Blocked?");
}
console.log("After:", ({}).polluted);
// Output: "pwned"
// Every object in the process now has this property.This vulnerability affects both dottie.set() and dottie.transform(). In a real-world scenario, this usually happens when an application takes a JSON body from a request (e.g., updating user preferences) and passes it directly to dottie to update a database object.
Prototype pollution is often dismissed as a "theoretical" risk, but in JavaScript, it is a loaded gun. By polluting Object.prototype, an attacker can:
if (user.isAdmin), and isAdmin is undefined on the user object, it looks up the prototype chain. If the attacker polluted the prototype with isAdmin: true, they are now an admin.toString or valueOf can cause the application to crash instantly whenever it tries to log an object or coerce a string.shell, exec, or outputFunctionName) can trick the engine into executing arbitrary shell commands.With a CVSS score of 6.3, it's marked "Medium," but in the right environment (like a server-side rendering app), it is critical.
The fix in version 2.0.7 replaces the lazy index-0 check with a comprehensive scan of the path. The developers finally realized that any part of the path could be a weapon.
Here is the corrected code logic:
// The Fix: Check EVERY piece of the path
var DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
// Array.some() returns true if any element matches the condition
if (pieces.some(function(p) {
return DANGEROUS_KEYS.indexOf(p) !== -1;
})) return;They also expanded the blacklist. Previously, they only feared __proto__. Now, they correctly block constructor and prototype as well. This prevents attacks that try to walk up the prototype chain via constructor.prototype.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
dottie.js mickhansen | >= 2.0.4, < 2.0.7 | 2.0.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1321 (Prototype Pollution) |
| CVSS v3.1 | 6.3 (Medium) |
| Attack Vector | Network (AV:N) |
| Exploit Maturity | Proof of Concept (PoC) |
| User Interaction | None (if automated processing) |
| Complexity | Low (AC:L) |
JupyterHub is vulnerable to an unauthenticated Denial of Service (DoS) vulnerability. Prior to version 5.5.0, form-based authenticators failed to restrict the size of the username input field on failed logins, allowing remote attackers to exhaust host storage and memory resources.
The self-hosted HTTP transport mode of @arikusi/deepseek-mcp-server (an MCP server for DeepSeek V4) exposes its JSON-RPC endpoint (POST /mcp) without authentication in versions 1.4.2 through 1.7.0. Unauthenticated clients can establish Model Context Protocol sessions and invoke tools, consuming the host's configured DeepSeek API key.
A Server-Side Template Injection (SSTI) leading to Remote Code Execution (RCE) was discovered in the mcp-contextforge-gateway package before version 1.0.0. The vulnerability stems from an unsandboxed Jinja2 template rendering environment combined with an unsafe fallback mechanism using Python's native str.format() function. Attackers with template modification access could bypass static regex filters to execute arbitrary commands on the hosting platform.
CVE-2026-55596 is a critical DOM-based Cross-Site Scripting (XSS) vulnerability in the Plate rich-text editor framework (specifically within the @platejs/media package). The issue stems from an optimization fast-path that short-circuits safety parsing if a provider or source URL is already declared on an element. Consequently, serialized documents carrying malicious javascript: URLs bypass protocol sanitization and are loaded directly into iframe elements, leading to code execution.
The npm package 'pickem' is vulnerable to a terminal escape-sequence injection (CWE-150). Unsanitized terminal outputs allow attackers to execute arbitrary shell commands via clipboard hijacking (OSC 52) or manipulate terminal displays through Control Sequence Introducers (CSI).
CVE-2026-55537 is a server-side request forgery (SSRF) and time-of-check time-of-use (TOCTOU) vulnerability in the PraisonAI multi-agent framework before version 4.6.58. The flaw exists in the job-submission component's webhook URL validation logic. When DNS resolution fails during verification, the application fails open, enabling attackers to register unresolvable URLs. When a completed job triggers the webhook, the application performs a fresh DNS resolution that attackers can manipulate to target internal resources.