fix(cli): shell-quote positional args instead of join(" ") (#239)

In positional mode (`srt cmd arg1 arg2`), the argv is reassembled into a
string and run via `bash -c`. Reassembling with `commandArgs.join(" ")`
discards argument boundaries, so any arg containing whitespace or shell
metacharacters is re-tokenised by bash:

    srt printf "%s\n" "hello world"
    # before: prints "hello\nworld\n" (two args)
    # after:  prints "hello world\n"

shell-quote is already a dependency (used by the macOS/Linux wrappers).
The -c path is unchanged — it receives a single string and is meant to
be parsed by the shell.

Fixes #157
This commit is contained in:
Dylan Conway
2026-05-05 13:56:01 -07:00
committed by GitHub
parent 1cb78b969c
commit b16b240be4
2 changed files with 24 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
import shellquote from 'shell-quote'
import { Command } from 'commander'
import { SandboxManager } from './index.js'
import type { SandboxRuntimeConfig } from './sandbox/sandbox-config.js'
@@ -147,8 +148,11 @@ async function main(): Promise<void> {
command = options.c
logForDebugging(`Command string mode (-c): ${command}`)
} else if (commandArgs.length > 0) {
// Default mode: simple join
command = commandArgs.join(' ')
// Default mode: argv-style invocation. The result is later
// executed via `bash -c <command>`, so each arg must be
// shell-quoted to survive that re-parse — a plain join(' ')
// splits arguments containing whitespace (#157).
command = shellquote.quote(commandArgs)
logForDebugging(`Original command: ${command}`)
} else {
console.error(

View File

@@ -98,6 +98,24 @@ describe('CLI', () => {
expect(result.stdout).toBe('no newline')
expect(result.status).toBe(0)
})
test('preserves argument boundaries when args contain spaces', () => {
// Regression for #157: positional args are later run via `bash -c`,
// so they must be shell-quoted. printf '%s\n' emits one line per
// arg, which exposes whether "hello world" arrived as one token
// (correct) or was re-split into "hello" and "world".
const result = runCli(['printf', '%s\n', 'hello world', 'a b'])
expect(result.stdout).toBe('hello world\na b\n')
expect(result.status).toBe(0)
})
test('preserves shell metacharacters in positional args', () => {
// Positional mode is argv-style; metacharacters in an arg are data,
// not shell syntax (use -c for shell semantics).
const result = runCli(['printf', '%s', '$HOME;|&'])
expect(result.stdout).toBe('$HOME;|&')
expect(result.status).toBe(0)
})
})
describe('error handling', () => {