fix(browse): portable temp paths — TEMP_DIRS allowlist, tmpdir()-based test files

Local path validation now accepts os.tmpdir() alongside the classic /tmp
(new TEMP_DIRS in platform.ts): on macOS os.tmpdir() is /var/folders/...,
and TMPDIR-honoring CI/sandbox environments point it elsewhere entirely —
both are legitimate scratch space. Remote file serving (TEMP_ONLY) stays
pinned to TEMP_DIR alone; no change to the exfil boundary.

commands.test.ts drops 41 hardcoded /tmp literals for a tmpp() helper on
os.tmpdir() (two message assertions now reference the same variable), and
path-validation's symlink-escape test targets /etc/hosts instead of
/etc/crontab — the target must EXIST for realpath to resolve the link (a
dangling target falls back to the link's own path and passes vacuously),
and /etc/crontab is absent on Amazon Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-28 19:59:06 +00:00
co-authored by Claude Fable 5
parent 626ebc1ba9
commit 97d33a90ad
4 changed files with 71 additions and 47 deletions
+5 -3
View File
@@ -12,16 +12,18 @@
* Security invariants:
* 1. All paths resolved to absolute before checking
* 2. Symlinks resolved to catch traversal via symlink inside safe dir
* 3. SAFE_DIRECTORIES = [TEMP_DIR, cwd] for local commands
* 3. SAFE_DIRECTORIES = [...TEMP_DIRS, cwd] for local commands (TEMP_DIRS =
* classic TEMP_DIR plus os.tmpdir(), which differ on macOS and under
* TMPDIR-honoring CI/sandbox environments)
* 4. TEMP_ONLY = [TEMP_DIR] for remote file serving (prevents project file exfil)
*/
import * as fs from 'fs';
import * as path from 'path';
import { TEMP_DIR, isPathWithin } from './platform';
import { TEMP_DIR, TEMP_DIRS, isPathWithin } from './platform';
// Resolve safe directories through realpathSync to handle symlinks (e.g., macOS /tmp → /private/tmp)
export const SAFE_DIRECTORIES = [TEMP_DIR, process.cwd()].map(d => {
export const SAFE_DIRECTORIES = [...TEMP_DIRS, process.cwd()].map(d => {
try { return fs.realpathSync(d); } catch { return d; }
});