fix(security): pipe install script via temp file instead of bash -c to prevent command injection (#3292)
Some checks are pending
CLI Release / Build and release CLI (push) Waiting to run
Lint / ShellCheck (push) Waiting to run
Lint / Biome Lint (push) Waiting to run
Lint / macOS Compatibility (push) Waiting to run

Fixes #3291

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
A 2026-04-13 01:55:24 -07:00 committed by GitHub
parent 439e5a1446
commit ace5aa94d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 24 additions and 13 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "1.0.5",
"version": "1.0.6",
"type": "module",
"bin": {
"spawn": "cli.js"

View file

@ -313,9 +313,9 @@ describe("update-check", () => {
expect(execFileSyncCalls[0].file).toBe("curl");
expect(execFileSyncCalls[0].args).toContain("-fsSL");
expect(execFileSyncCalls[0].args.some((a: string) => a.includes("install.sh"))).toBe(true);
// 2. bash to execute fetched script
// 2. bash to execute fetched script via temp file (not -c)
expect(execFileSyncCalls[1].file).toBe("bash");
expect(execFileSyncCalls[1].args[0]).toBe("-c");
expect(execFileSyncCalls[1].args[0]).toMatch(/spawn-install-.*\.sh$/);
// 3. which spawn for binary lookup
expect(execFileSyncCalls[2].file).toBe("which");
expect(execFileSyncCalls[2].args).toEqual([

View file

@ -320,17 +320,28 @@ function performAutoUpdate(latestVersion: string, jsonOutput = false): void {
throw psResult.error;
}
} else {
// macOS/Linux: execute via bash -c
executor.execFileSync(
"bash",
[
"-c",
scriptContent,
],
{
stdio: installStdio,
},
// macOS/Linux: write to temp file and execute via bash to avoid
// command injection and ARG_MAX limits (consistent with Windows path)
const tmpFile = path.join(tmpdir(), `spawn-install-${Date.now()}.sh`);
fs.writeFileSync(tmpFile, scriptContent, {
mode: 0o700,
});
const bashResult = tryCatch(() =>
executor.execFileSync(
"bash",
[
tmpFile,
],
{
stdio: installStdio,
},
),
);
// Best-effort cleanup of temp file
tryCatchIf(isFileError, () => fs.unlinkSync(tmpFile));
if (!bashResult.ok) {
throw bashResult.error;
}
}
});