Critical severity9.8NVD Advisory· Published Feb 10, 2026· Updated Apr 15, 2026
CVE-2026-1774
CVE-2026-1774
Description
CASL Ability, versions 2.4.0 through 6.7.4, contains a prototype pollution vulnerability.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@casl/abilitynpm | >= 2.4.0, < 6.7.5 | 6.7.5 |
Affected products
1Patches
139da920ec1dffix: ignores potentially insecure fields in rulesToFields (#1093)
2 files changed · +32 −14
packages/casl-ability/spec/rulesToFields.spec.ts+21 −10 renamed@@ -1,12 +1,11 @@ import { rulesToFields } from '../src/extra' -import { defineAbility, PureAbility } from '../src' -import './spec_helper' +import { createMongoAbility, defineAbility } from '../src' -describe('rulesToFields', () => { +describe(rulesToFields.name, () => { it('returns an empty object for an empty `Ability` instance', () => { - const object = rulesToFields(new PureAbility(), 'read', 'Post') + const object = rulesToFields(createMongoAbility(), 'read', 'Post') - expect(object).to.be.an('object').and.empty + expect(Object.keys(object)).toHaveLength(0) }) it('returns an empty object if `Ability` contains only inverted rules', () => { @@ -16,14 +15,14 @@ describe('rulesToFields', () => { }) const object = rulesToFields(ability, 'read', 'Post') - expect(object).to.be.an('object').and.empty + expect(Object.keys(object)).toHaveLength(0) }) it('returns an empty object for `Ability` instance with rules without conditions', () => { const ability = defineAbility(can => can('read', 'Post')) const object = rulesToFields(ability, 'read', 'Post') - expect(object).to.be.an('object').and.empty + expect(Object.keys(object)).toHaveLength(0) }) it('extracts field values from direct rule conditions', () => { @@ -33,7 +32,7 @@ describe('rulesToFields', () => { }) const object = rulesToFields(ability, 'read', 'Post') - expect(object).to.deep.equal({ id: 5, private: true }) + expect(object).toEqual({ id: 5, private: true }) }) it('correctly sets values for fields declared with `dot notation`', () => { @@ -43,7 +42,7 @@ describe('rulesToFields', () => { }) const object = rulesToFields(ability, 'read', 'Post') - expect(object).to.deep.equal({ + expect(object).toEqual({ id: 5, state: { private: true @@ -58,6 +57,18 @@ describe('rulesToFields', () => { }) const object = rulesToFields(ability, 'read', 'Post') - expect(object).to.deep.equal({ private: true }) + expect(object).toEqual({ private: true }) + }) + + it('skips forbidden properties', () => { + const ability = defineAbility((can) => { + can('read', 'Post', { '__proto__.__pollutedValue__': 1 }) + can('read', 'Post', { constructor: 1 }) + can('read', 'Post', { prototype: 2 }) + }) + const object = rulesToFields(ability, 'read', 'Post') + + expect(({} as any).__pollutedValue__).toBeUndefined() + expect(Object.keys(object)).toEqual(['__pollutedValue__']) }) })
packages/casl-ability/src/utils.ts+11 −4 modified@@ -1,9 +1,13 @@ import { AnyObject, Subject, SubjectType, SubjectClass, ForcedSubject, AliasesMap } from './types'; +const hasOwn: (o: object, v: PropertyKey) => boolean = Object.hasOwn || + ((obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)); + export function wrapArray<T>(value: T[] | T): T[] { return Array.isArray(value) ? value : [value]; } +const FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']); export function setByPath(object: AnyObject, path: string, value: unknown): void { let ref = object; let lastKey = path; @@ -13,12 +17,15 @@ export function setByPath(object: AnyObject, path: string, value: unknown): void lastKey = keys.pop()!; ref = keys.reduce((res, prop) => { + if (FORBIDDEN_PROPERTIES.has(prop)) return res; res[prop] = res[prop] || {}; return res[prop] as AnyObject; }, object); } - ref[lastKey] = value; + if (!FORBIDDEN_PROPERTIES.has(lastKey)) { + ref[lastKey] = value; + } } const TYPE_FIELD = '__caslSubjectType__'; @@ -27,7 +34,7 @@ export function setSubjectType< U extends Record<PropertyKey, any> >(type: T, object: U): U & ForcedSubject<T> { if (object) { - if (!Object.hasOwn(object, TYPE_FIELD)) { + if (!hasOwn(object, TYPE_FIELD)) { Object.defineProperty(object, TYPE_FIELD, { value: type }); } else if (type !== object[TYPE_FIELD]) { throw new Error(`Trying to cast object to subject type ${type} but previously it was casted to ${object[TYPE_FIELD]}`); @@ -48,7 +55,7 @@ export function getSubjectTypeName(value: SubjectType) { } export function detectSubjectType(object: Exclude<Subject, SubjectType>): string { - if (Object.hasOwn(object, TYPE_FIELD)) { + if (hasOwn(object, TYPE_FIELD)) { return object[TYPE_FIELD]; } @@ -68,7 +75,7 @@ function expandActions(aliasMap: AliasesMap, rawActions: string | string[], merg while (i < actions.length) { const action = actions[i++]; - if (Object.hasOwn(aliasMap, action)) { + if (hasOwn(aliasMap, action)) { actions = merge(actions, aliasMap[action]); } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-x9vf-53q3-cvx6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-1774ghsaADVISORY
- cwe.mitre.org/data/definitions/1321.htmlnvdWEB
- developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollutionnvdWEB
- github.com/stalniy/casl/commit/39da920ec1dfadf3655e28bd0389e960ac6871f4ghsaWEB
- github.com/stalniy/casl/pull/1093ghsaWEB
- github.com/stalniy/casl/tree/master/packages/casl-abilitynvdWEB
- www.kb.cert.org/vuls/id/458422nvdWEB
News mentions
0No linked articles in our index yet.