What's the vulnerability?

CASL Ability, versions 2.4.0 through 6.7.4, contains a prototype pollution vulnerability.

Root Cause Analysis

# Root Cause Analysis: GHSA-x9vf-53q3-cvx6

## Summary

The CASL Ability library (versions 2.4.0 through 6.7.4) contains a prototype pollution vulnerability in the `setByPath` utility function. When the `rulesToFields` function processes ability rules containing malicious condition keys like `__proto__.polluted`, it passes these paths to `setByPath`, which unsafely traverses and modifies object properties without validating against dangerous property names. This allows attackers to pollute `Object.prototype`, potentially leading to privilege escalation, denial of service, or unauthorized access across the entire application.

## Impact

- **Package**: `@casl/ability` (npm)
- **Affected Versions**: >= 2.4.0, <= 6.7.4
- **Fixed in Version**: 6.7.5
- **CVE**: CVE-2026-1774
- **CVSS**: 9.8 (CRITICAL)
- **CVSS Vector**: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`
- **CWE**: CWE-1321 (Prototype Pollution)

### Risk Level and Consequences

This is a critical severity vulnerability with the following potential impacts:
- **Confidentiality**: High - Attackers may bypass authorization checks by manipulating object properties
- **Integrity**: High - Modified prototypes can alter application behavior globally
- **Availability**: High - Polluted prototypes can cause application crashes or infinite loops

The vulnerability affects any application using the `rulesToFields` function from `@casl/ability/extra` with user-controlled rule conditions.

## Root Cause

The vulnerability exists in the `setByPath` function in `packages/casl-ability/src/utils.ts`. This function sets nested properties on an object using a dot-notation path string. Prior to the fix, `setByPath` did not validate the property names within the path, allowing dangerous properties like `__proto__`, `constructor`, and `prototype` to be used in paths.

### Vulnerable Code Pattern

```typescript
// Vulnerable version (before 6.7.5)
export function setByPath(object: AnyObject, path: string, value: unknown): void {
  let ref = object;
  let lastKey = path;

  if (path.indexOf('.') !== -1) {
    const keys = path.split('.');
    lastKey = keys.pop()!;
    ref = keys.reduce((res, prop) => {
      // NO VALIDATION - dangerous properties allowed
      res[prop] = res[prop] || {};
      return res[prop] as AnyObject;
    }, object);
  }

  ref[lastKey] = value; // Direct assignment without validation
}
```

### Attack Vector

The `rulesToFields` function is part of the public API and is commonly used to extract rule conditions into field objects. When an attacker can control the conditions of rules passed to `rulesToFields`, they can craft a malicious path:

```javascript
can('read', 'Post', { '__proto__.__pollutedValue__': 1 })
```

When `rulesToFields` processes this rule, it calls:
```javascript
setByPath(fields, '__proto__.__pollutedValue__', 1)
```

This traverses `fields.__proto__` (which is `Object.prototype`) and sets `__pollutedValue__ = 1`, polluting the global object prototype.

### Fix

The patch introduces a `FORBIDDEN_PROPERTIES` set and validates each property in the path:

```typescript
const FORBIDDEN_PROPERTIES = new Set(['__proto__', 'constructor', 'prototype']);

export function setByPath(object: AnyObject, path: string, value: unknown): void {
  // ... path splitting ...
  
  ref = keys.reduce((res, prop) => {
    if (FORBIDDEN_PROPERTIES.has(prop)) return res; // BLOCKED
    res[prop] = res[prop] || {};
    return res[prop] as AnyObject;
  }, object);

  if (!FORBIDDEN_PROPERTIES.has(lastKey)) {
    ref[lastKey] = value; // Only set if not forbidden
  }
}
```

**Fix Commit**: https://github.com/stalniy/casl/commit/39da920ec1dfadf3655e28bd0389e960ac6871f4

## Reproduction Steps

The reproduction script is located at `repro/reproduction_steps.sh`.

### What the Script Does

1. **Installs vulnerable version (6.7.3)**: Sets up a test environment with the vulnerable `@casl/ability` package
2. **Tests prototype pollution**: Creates an ability with a malicious condition containing `__proto__.__pollutedValue__` and calls `rulesToFields`. Verifies that `Object.prototype` is polluted.
3. **Installs patched version (6.7.5)**: Upgrades to the fixed version
4. **Verifies the fix**: Repeats the test and confirms that `Object.prototype` is NOT polluted in the fixed version

### Expected Evidence

The script produces the following evidence in `logs/`:
- `npm_install_vuln.log` - Installation of vulnerable version
- `test_vulnerable.log` - Demonstrates prototype pollution
- `npm_install_fixed.log` - Installation of fixed version
- `test_fixed.log` - Confirms fix works

**Key Evidence from Vulnerable Version:**
```
Before test: ({}).__pollutedValue__ = undefined
After rulesToFields: ({}).__pollutedValue__ = 1
Returned fields: {}
[FAIL] Prototype pollution confirmed! Object.prototype was polluted.
```

**Key Evidence from Fixed Version:**
```
Before test: ({}).__pollutedValue__ = undefined
After rulesToFields: ({}).__pollutedValue__ = undefined
Returned fields: {"__pollutedValue__":1}
[PASS] Fix confirmed! Object.prototype was NOT polluted.
```

Note: In the fixed version, the value is stored in the returned fields object (as expected) but does NOT pollute the global `Object.prototype`.

## Evidence

All evidence is captured in the `logs/` directory:
- `/root/.pruva/runs/ghsa-x9vf-53q3-cvx6_20260219-193039/logs/npm_install_vuln.log`
- `/root/.pruva/runs/ghsa-x9vf-53q3-cvx6_20260219-193039/logs/test_vulnerable.log`
- `/root/.pruva/runs/ghsa-x9vf-53q3-cvx6_20260219-193039/logs/npm_install_fixed.log`
- `/root/.pruva/runs/ghsa-x9vf-53q3-cvx6_20260219-193039/logs/test_fixed.log`

### Environment Details

- **Node.js**: Available via npm
- **Tested Versions**: 
  - Vulnerable: 6.7.3 (also affected 2.4.0 through 6.7.4)
  - Fixed: 6.7.5
- **Test Environment**: Linux, npm package manager

## Recommendations / Next Steps

### Immediate Actions

1. **Upgrade**: Update `@casl/ability` to version 6.7.5 or later immediately
   ```bash
   npm install @casl/ability@^6.7.5
   ```

2. **Audit**: Check if your application uses `rulesToFields` with user-controlled rule conditions

3. **Input Validation**: Until patched, sanitize any user input that might be used in ability rule conditions

### Testing Recommendations

1. Run `npm audit` to identify vulnerable dependencies
2. Review code that constructs ability rules from user input
3. Add regression tests using the reproduction approach in this report
4. Monitor for suspicious rule conditions containing `__proto__`, `constructor`, or `prototype` keys

### Long-term Prevention

1. Implement automated security scanning in CI/CD pipelines
2. Use lockfiles and audit checks in pre-commit hooks
3. Subscribe to security advisories for @casl/ability

## Additional Notes

### Idempotency Confirmation

The reproduction script was executed **twice consecutively** with identical results:
- Run 1: Exit code 0 (vulnerability confirmed)
- Run 2: Exit code 0 (vulnerability confirmed)

Both runs successfully demonstrated:
1. Vulnerable version (6.7.3) allows prototype pollution
2. Fixed version (6.7.5) prevents prototype pollution

### Edge Cases and Limitations

- The vulnerability requires the use of `rulesToFields` function with user-controlled conditions
- Applications that only use basic ability checks without `rulesToFields` may not be directly exploitable
- The fix properly handles both dot-notation paths (`__proto__.polluted`) and direct property assignments
- The fix also protects against `constructor.prototype` attack variants
One Command

Verify with pruva-verify

Run the Pruva CLI to automatically fetch and execute the reproduction script.

pruva-verify REPRO-2026-00097
or pruva-verify GHSA-x9vf-53q3-cvx6
or pruva-verify CVE-2026-1774
Install: curl -fsSL https://pruva.dev/install.sh | sh

Or Run Manually

1

Download the script

curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00097/artifacts/reproduction_steps.sh
2

Make executable

chmod +x reproduction_steps.sh
3

Run the script

./reproduction_steps.sh
Run in a VM, container, or disposable environment. This exploits a real vulnerability.

How Pruva Reproduced This

Watch the AI agent's step-by-step process.

Loading session...

Artifacts

No artifacts available