security: fix path traversal risk in SPAWN_HOME validation (#1402)

* security: fix path traversal risk in SPAWN_HOME validation

Agent: security-auditor
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: add missing join import and update tests for SPAWN_HOME security validation

Addresses security review feedback on PR #1402:
- Add missing 'join' import to cli-version-and-dispatch.test.ts
- Update all test files to use homedir() instead of tmpdir() for SPAWN_HOME

The security fix in history.ts now enforces that SPAWN_HOME must be within
the user's home directory. All tests have been updated to use home-based
test directories instead of /tmp paths.

Changes:
- cli/src/__tests__/cli-version-and-dispatch.test.ts: Add join to path imports
- All test files: Replace tmpdir() with homedir() and /tmp/spawn- with /.spawn-test-

Tests:
- bun test history.test.ts:  69 pass
- bun test clear-history.test.ts:  27 pass
- bun test cli-version-and-dispatch.test.ts:  62 pass
- bun test list-table-rendering.test.ts:  8 pass

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

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-02-17 09:57:01 -08:00 committed by GitHub
parent 858dd48f86
commit 94b09ab29e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 108 additions and 47 deletions

View file

@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test";
import { resolve } from "path";
import { resolve, join } from "path";
/**
* Tests for CLI version output and dispatch routing via subprocess execution.
@ -493,19 +493,22 @@ describe("subcommand alias routing", () => {
describe("list command aliases", () => {
it("'list' should not crash with empty history", () => {
const { exitCode } = runCLI(["list"], { SPAWN_HOME: "/tmp/spawn-test-empty-home-" + Date.now() });
const { homedir } = require("os");
const { exitCode } = runCLI(["list"], { SPAWN_HOME: join(homedir(), ".spawn-test-empty-home-" + Date.now()) });
// May exit 0 (shows "no spawns") or run interactive picker in non-TTY
// The important thing is it doesn't crash
expect(exitCode).toBeDefined();
});
it("'ls' should work as alias for 'list'", () => {
const { exitCode } = runCLI(["ls"], { SPAWN_HOME: "/tmp/spawn-test-empty-home-" + Date.now() });
const { homedir } = require("os");
const { exitCode } = runCLI(["ls"], { SPAWN_HOME: join(homedir(), ".spawn-test-empty-home-" + Date.now()) });
expect(exitCode).toBeDefined();
});
it("'history' should work as alias for 'list'", () => {
const { exitCode } = runCLI(["history"], { SPAWN_HOME: "/tmp/spawn-test-empty-home-" + Date.now() });
const { homedir } = require("os");
const { exitCode } = runCLI(["history"], { SPAWN_HOME: join(homedir(), ".spawn-test-empty-home-" + Date.now()) });
expect(exitCode).toBeDefined();
});