← Back to PullLight
CVE-2026-1774 CVSS 9.8 Critical npm: @casl/ability 6.7.5+ fixes this

CASL Ability — Prototype Pollution to Authorization Bypass

@casl/ability 2.4.0–6.7.4 allows attackers to overwrite Object.prototype via permission rule conditions, causing CASL's type detection to misidentify every object as the wildcard subject 'all' — authorizing every action for every user.

CVSS 9.8 / 10.0
CWE CWE-1321
Package @casl/ability (< 6.7.5)
Published Feb 10, 2026
~900k weekly npm downloads (@casl/ability + dependents)
Authorization library for Node.js, React, Angular, Vue
Attack surface any user-controlled rule condition in permission definitions
// what happened
  • Affected: @casl/ability 2.4.0–6.7.4 — Node.js / browser authorization library
  • Attack vector: setByPath() in utils.ts walks __proto__, constructor, and prototype path segments without guard. Attacker injects '__proto__.modelName' as a rule condition key — CASL sets Object.prototype.modelName = 'all'
  • Impact: Complete authorization bypass — CASL resolves every subject object as the wildcard 'all' because Object.prototype.modelName === 'all'. Every rule matches every user. No visible evidence of compromise.
  • Fix: Upgrade to @casl/ability 6.7.5+ — adds FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']) guard in setByPath()
  • Attacker model: Any user who can influence permission rule conditions — either directly via application config, or indirectly via user-supplied data that flows into CASL rule definitions

Base Score
9.8 Critical
Attack Vector (AV)
Network — attacker-controlled rule conditions from application input
Attack Complexity (AC)
Low — single condition key injection achieves full pollution
Privileges Required (PR)
Low — ability to influence CASL rule conditions via user input
User Interaction (UI)
None — auth bypass happens server-side, no user action needed
Scope (S)
Changed — authorization system compromised; entire app trust model broken
Confidentiality (C)
High — all data readable via authorized actions that should have been denied
Integrity (I)
High — all write/delete actions authorized; data can be modified or destroyed
Availability (A)
High — denial of service possible via authorized delete/drop operations
Vector String
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

packages/casl-ability/src/utils.ts — @casl/ability < 6.7.5 @casl/ability 2.4.0–6.7.4 — setByPath() walks __proto__ without guard // VULNERABLE: setByPath() walks full path including __proto__, constructor, prototype // Attacker can inject '__proto__.modelName' as a condition key // Object.prototype.modelName gets set to 'all' — CASL misidentifies every object as wildcard subject function setByPath(obj, path, value) { let current = obj; const segments = path.split('.'); for (let i = 0; i < segments.length; i++) { const segment = segments[i]; // <-- No guard against '__proto__', 'constructor', 'prototype' path segments // <-- Object.prototype gets modified, affecting ALL objects in the runtime if (i === segments.length - 1) { // Final segment: set the value current[segment] = value; } else { // Intermediate segment: create object if needed, continue walking if (current[segment] === undefined) { current[segment] = {}; } current = current[segment]; } } return obj; } // Example attacker input: // setByPath({}, '__proto__.modelName', 'all') // ↑ This sets Object.prototype.modelName = 'all' // ↓ Every subsequent object in JS has modelName = 'all' // ↓ CASL's type resolution sees modelName='all' = wildcard subject 'all' // ↓ Every CASL rule matches every object = complete auth bypass
packages/casl-ability/src/extra.ts — rulesToFields() calls setByPath with attacker-controlled keys @casl/ability < 6.7.5 — attacker-controlled rule conditions reach setByPath // VULNERABLE: rulesToFields() builds a map using condition keys as paths // If a rule condition contains '__proto__.modelName', setByPath modifies Object.prototype // CASL uses this map to resolve subject types — polluted map = wrong type resolution import { setByPath } from './utils'; function rulesToFields(rules) { const fields = {}; for (const rule of rules) { const conditions = rule.conditions || []; for (const condition of conditions) { const { field, value } = condition; // <-- field comes from rule.conditions — attacker-controlled if user input // <-- reaches setByPath() without validation // <-- '__proto__.modelName' as field causes Object.prototype pollution setByPath(fields, field, extractFields(value)); } } return fields; } // Attack payload example: // const conditions = [ // { field: '__proto__.modelName', value: 'all' } // ]; // rulesToFields({ conditions }); // ↑ setByPath({}, '__proto__.modelName', 'all') // ↑ Object.prototype.modelName = 'all' // ↓ CASL.abilities.of(user) resolves subject as 'all' (wildcard) // ↓ Can('read', 'all') — every action on every resource authorized
packages/casl-ability/src/utils.ts — @casl/ability 6.7.5 @casl/ability 6.7.5 — FORBIDDEN_PROPERTIES guard blocks __proto__/constructor/prototype // FIX: FORBIDDEN_PROPERTIES blocklist prevents prototype pollution // setByPath() now rejects '__proto__', 'constructor', 'prototype' segments // Object.prototype cannot be modified through this code path const FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']); function setByPath(obj, path, value) { let current = obj; const segments = path.split('.'); for (let i = 0; i < segments.length; i++) { const segment = segments[i]; // FIX: Guard against forbidden path segments if (FORBIDDEN_PROPERTIES.has(segment)) { // Reject the operation — cannot walk into Object prototype chain // This prevents Object.prototype from being modified // Attacker input '__proto__.modelName' is blocked at the '__proto__' segment return obj; } if (i === segments.length - 1) { current[segment] = value; } else { if (current[segment] === undefined) { current[segment] = {}; } current = current[segment]; } } return obj; } // Fix commit: 39da920ec1dfadf3655e28bd0389e960ac6871f4 // @casl/ability 6.7.5 on npm — upgrade immediately
Fix commit — 39da920 (CASL repo) @casl/ability 6.7.5 — commit 39da920ec1dfadf3655e28bd0389e960ac6871f4 // Key fix across multiple files — FORBIDDEN_PROPERTIES guard added to: // - packages/casl-ability/src/utils.ts (setByPath) // - packages/casl-ability/src/extra.ts (rulesToFields) // - Any other path-walking utility that accepts user input as path const FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']); // Guard added at the start of every setByPath() call function setByPath(obj, path, value) { const segments = path.split('.'); for (const segment of segments) { // BLOCK: __proto__, constructor, prototype cannot be used as path segments if (FORBIDDEN_PROPERTIES.has(segment)) { return obj; // silently reject — no error, no modification } } // ... rest of original implementation } // No code execution path remains — Object.prototype cannot be modified
// root cause
The setByPath() utility function in utils.ts walks path segments to set nested object values. It had no guard against __proto__, constructor, or prototype as path segments. In JavaScript, obj.__proto__ references Object.prototype, so setByPath({}, '__proto__.modelName', 'all') sets Object.prototype.modelName = 'all'. Since every JavaScript object inherits from Object.prototype, every object now has a modelName property with value 'all'. CASL's type resolution logic checks subjectType === 'all' to identify wildcard subjects — when every object has modelName === 'all', CASL resolves every subject as wildcard, and every permission rule matches every action. The attacker only needs to influence a rule condition key to inject '__proto__.modelName' — the rest is automatic.

From a rule condition to complete authorization bypass
1
Attacker identifies an application using @casl/ability for authorization and finds a path to influence rule condition keys — via config files, user-supplied data in permission definitions, or database-driven rule generation.
2
Attacker crafts a rule condition with field set to '__proto__.modelName'. This key is passed to rulesToFields() in extra.ts, which calls setByPath() with the attacker-controlled path.
3
setByPath() walks the path ['__proto__', 'modelName']. When it accesses obj.__proto__, it gets a reference to Object.prototype. It then sets Object.prototype.modelName = 'all'.
4
Every JavaScript object in the runtime now has modelName === 'all' due to prototype chain inheritance. CASL's type detection (getTypeName() or equivalent) reads this property to determine the subject type.
5
CASL resolves every subject as the wildcard 'all' because object.modelName === 'all'. Every CASL rule — including rules that should only apply to specific entities — now matches every object.
6
Complete authorization bypass: the attacker (and any user whose actions trigger the polluted rules) can perform any action on any resource. No audit log shows the attack. No error is thrown. CASL silently authorizes everything.
@casl/ability is used for role-based and attribute-based access control across the stack
  • Node.js API servers — CASL used as the authorization layer for REST/GraphQL APIs. A polluted ruleset means every API call is authorized regardless of the actual permissions.
  • React / Angular / Vue SPAs — CASL's frontend ability system handles UI rendering based on permissions. After pollution, every UI element renders as if the user has full access.
  • Multi-tenant SaaS — applications using CASL for tenant isolation are compromised: user A can access tenant B's data if the pollution occurs before any request.
  • Database-driven rules — applications that store CASL rules in a database (admin UI for managing permissions) are vulnerable if any admin account can be compromised to inject the payload.
  • Prototype pollution is silent — Object.prototype pollution produces no error, no crash, and no log entry. The application continues to function normally while every authorization decision is compromised.
Complete Authorization Bypass
Every permission check returns true once Object.prototype.modelName is set to 'all'. No error, no trace.
No Visible Evidence of Compromise
Object.prototype pollution is silent — no exceptions thrown, no log entries. The application functions normally while every check is bypassed.
Widespread CASL Adoption
~900k weekly npm downloads. Used in Node.js backends, React SPAs, Angular apps, and Vue projects. One vulnerable version affects the entire stack.
Supply Chain Attack Vector
If an attacker compromises a rule configuration (e.g., via a vulnerable admin panel), they can deploy the prototype pollution payload across the entire application.

🔴 PullLight — Critical Finding
[CRITICAL] Prototype Pollution → Authorization Bypass — packages/casl-ability/src/utils.ts

setByPath() walks path segments with no guard against __proto__, constructor, or prototype. Attacker injects '__proto__.modelName' as a rule condition key. setByPath({}, '__proto__.modelName', 'all') sets Object.prototype.modelName = 'all'. CASL's type resolution identifies every object as the wildcard subject 'all' because object.modelName === 'all'. Every rule matches every object. Complete authorization bypass with no visible evidence.

This is CWE-1321 (Prototype Pollution) with a CVSS 9.8 rating. The impact is total auth bypass — every permission check passes for every user and every resource once the prototype chain is polluted.
→ Fix: Upgrade to @casl/ability 6.7.5+ which adds FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']) guard to setByPath(). Additionally, audit any application code that uses user input as path segments in object-walking operations.

Four reasons this vulnerability hides from line-by-line review

  • Prototype pollution requires understanding the full JavaScript prototype chain. A reviewer looking at setByPath() in isolation sees a utility function that sets nested object properties — a normal and common pattern. The vulnerability only emerges when the path can reach Object.prototype through __proto__.
  • The attack surface is in a dependency's utility function. The vulnerable code is in @casl/ability itself, not in application code. App-level reviewers typically trust library code and focus on their own authorization logic.
  • No obvious attacker-controlled input in the function signature. setByPath(obj, path, value) takes a path string — reviewers don't immediately see how user input flows to this function. Understanding the call chain (rulesToFields() → attacker-controlled conditions) is required.
  • Prototype pollution has no visible runtime effect until authorization checks run. The polluted Object.prototype doesn't throw an exception — it silently changes CASL's type resolution behavior. The application appears to work fine while being completely compromised.

What makes this a PullLight-specialized catch

  • Prototype chain modeling: PullLight understands JavaScript's prototype chain and can identify when __proto__ path segments in object-walking functions can reach Object.prototype.
  • Authorization library vulnerability mapping: PullLight cross-references known vulnerable patterns in CASL (@casl/ability) against the actual code paths used in the PR diff.
  • CVSS 9.8 severity calibration: PullLight correctly identifies the impact chain: prototype pollution → type resolution misidentification → global auth bypass.
  • Fix precision: PullLight identifies the specific fix (FORBIDDEN_PROPERTIES blocklist in setByPath) rather than vague "sanitize inputs" advice.

Feb 10, 2026 GitHub Security Advisory GHSA-x9vf-53q3-cvx6 published. @casl/ability 6.7.5 released with patch. CVE-2026-1774 assigned by MITRE.
Feb 11, 2026 CVE-2026-1774 added to NVD. CVSS 9.8 confirmed. Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H. CWE-1321.
Feb 12+, 2026 Alma Security and other security scanners begin covering CVE-2026-1774. npm advisory updated.
Jun 2026 PullLight documents CVE-2026-1774 as 12th CVE case study — demonstrating prototype pollution detection in PR review.

Don't let prototype pollution reach your authorization layer

More CVE Case Studies