fix(security): guarantee temp file cleanup in performAutoUpdate (#3307)

Restructure temp file write-execute-cleanup in performAutoUpdate so
cleanup is unconditionally reached after tryCatch captures any exec
error. Previously, the Windows and Unix paths each had separate
tryCatch+cleanup+rethrow sequences that could diverge under future
edits. Now a single tryCatch wraps the platform-branching exec, with
cleanup always running before any error is re-thrown.

Fixes #3306

Agent: security-auditor

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
A 2026-04-14 22:48:12 -07:00 committed by GitHub
parent 1e64d34e5a
commit d1d51fb06d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 27 deletions

View file

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

View file

@ -332,11 +332,22 @@ function performAutoUpdate(latestVersion: string, jsonOutput = false): void {
const platform = isWindows() ? "windows" : "unix";
validateInstallScript(scriptContent, platform);
if (isWindows()) {
// Windows: write to temp file and execute via PowerShell
const tmpFile = path.join(tmpdir(), `spawn-install-${Date.now()}.ps1`);
fs.writeFileSync(tmpFile, scriptContent);
const psResult = tryCatch(() =>
// Write install script to temp file, execute, and guarantee cleanup.
// Uses tryCatch so cleanup always runs before any error is re-thrown.
const tmpExt = isWindows() ? "ps1" : "sh";
const tmpFile = path.join(tmpdir(), `spawn-install-${Date.now()}.${tmpExt}`);
fs.writeFileSync(
tmpFile,
scriptContent,
isWindows()
? undefined
: {
mode: 0o700,
},
);
const execResult = tryCatch(() => {
if (isWindows()) {
executor.execFileSync(
"powershell.exe",
[
@ -348,21 +359,8 @@ function performAutoUpdate(latestVersion: string, jsonOutput = false): void {
{
stdio: installStdio,
},
),
);
// Best-effort cleanup of temp file
tryCatchIf(isFileError, () => fs.unlinkSync(tmpFile));
if (!psResult.ok) {
throw psResult.error;
}
} else {
// 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(() =>
);
} else {
executor.execFileSync(
"bash",
[
@ -371,13 +369,16 @@ function performAutoUpdate(latestVersion: string, jsonOutput = false): void {
{
stdio: installStdio,
},
),
);
// Best-effort cleanup of temp file
tryCatchIf(isFileError, () => fs.unlinkSync(tmpFile));
if (!bashResult.ok) {
throw bashResult.error;
);
}
});
// Cleanup runs unconditionally — tryCatch above captures any exec error
// without short-circuiting, so we always reach this line.
tryCatchIf(isFileError, () => fs.unlinkSync(tmpFile));
if (!execResult.ok) {
throw execResult.error;
}
});