# shell-quote quote() does not escape newlines in object .op values

shell-quote's quote() escaped .op per-character with /(.)/g, which skips line terminators, so a newline in an object token's .op injected a second shell command.

- Payout: Research
- Severity: critical
- Program: shell-quote
- Found by (vendor credit): Akshat Sinha (@akshatgit)
- Found by: akshatgit
- Disclosed: 2026-06-09
- CVE: CVE-2026-9277
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-w7jw-789q-3m8p
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-w7jw-789q-3m8p
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:critical tech:nodejs technique:source-review vuln:command-injection

**Target:** npm library quote()/parse() API
**Root cause:** quote() backslash-escaped object-token .op character by character using /(.)/g, which does not match line terminators (\n, \r, U+2028, U+2029), so a newline passed through unescaped into shell output where POSIX shells treat it as a command separator.
**Steps:**
1. Build an object token with a newline in .op: `quote([{ op: ';\nid' }])`
2. Observe output `"\;\n\\i\\d"` containing a literal newline
3. Alternatively call `parse('echo $X', () => ({ op: ';\nid' }))` so envFn splices the attacker object into tokens
4. Pass result to `execSync(quote(tokens), { shell: true })`; `id` executes after `echo \;`
5. Confirmed under sh, bash, dash, zsh
**How found:** Noticed quote() escaped .op with a per-character regex /(.)/g and reasoned that JavaScript's dot does not match line terminators, so newline-bearing ops slip through the shell-safety boundary; then traced the two documented API paths (direct object construction and envFn return) that let attacker data reach .op.
**Exploitation:** Caller passes attacker-influenced object tokens (directly or via envFn return) to quote() and hands the output to a shell; the unescaped newline terminates the first command and the trailing content runs as a second command.
**Insight:** When auditing shell-escaping libraries, check whether escape regexes use `.` (which skips \n, \r, U+2028, U+2029) instead of an explicit character class; also check whether escaping functions validate structured token fields against the same allowlist the parser emits, since parser output being safe does not make the quoting API safe for caller-supplied objects.

## Proof of concept (copied exactly from the original report)

**Direct construction quote() with newline op**

```
const { parse, quote } = require('shell-quote');

// Direct construction
quote([{ op: ';\nid' }]);
// → "\;\n\\i\\d"  ← literal newline; second line executes as a command
```

**parse() envFn returning malicious op object**

```
const tokens = parse('echo $X', () => ({ op: ';\nid' }));
require('child_process').execSync(quote(tokens), { shell: true });
// Executes `id` after `echo \;`.
```

