diff --git a/browse/src/cookie-import-browser.ts b/browse/src/cookie-import-browser.ts index 1e7f1ce4..7dc75e07 100644 --- a/browse/src/cookie-import-browser.ts +++ b/browse/src/cookie-import-browser.ts @@ -386,7 +386,8 @@ function openDb(dbPath: string, browserName: string): Database { } function openDbFromCopy(dbPath: string, browserName: string): Database { - const tmpPath = `/tmp/browse-cookies-${browserName.toLowerCase()}-${crypto.randomUUID()}.db`; + // Use os.tmpdir() instead of hardcoded /tmp for cross-platform support (#708) + const tmpPath = path.join(os.tmpdir(), `browse-cookies-${browserName.toLowerCase()}-${crypto.randomUUID()}.db`); try { fs.copyFileSync(dbPath, tmpPath); // Also copy WAL and SHM if they exist (for consistent reads) diff --git a/browse/src/write-commands.ts b/browse/src/write-commands.ts index 432b6d58..42965620 100644 --- a/browse/src/write-commands.ts +++ b/browse/src/write-commands.ts @@ -13,7 +13,8 @@ import { validateNavigationUrl } from './url-validation'; import { validateOutputPath } from './path-security'; import * as fs from 'fs'; import * as path from 'path'; -import { TEMP_DIR } from './platform'; +import { TEMP_DIR, isPathWithin } from './platform'; +import { SAFE_DIRECTORIES } from './path-security'; import { modifyStyle, undoModification, resetModifications, getModificationHistory } from './cdp-inspector'; /** @@ -441,16 +442,17 @@ export async function handleWriteCommand( case 'cookie-import': { const filePath = args[0]; if (!filePath) throw new Error('Usage: browse cookie-import '); - // Path validation — prevent reading arbitrary files - if (path.isAbsolute(filePath)) { - const safeDirs = [TEMP_DIR, process.cwd()]; - const resolved = path.resolve(filePath); - if (!safeDirs.some(dir => isPathWithin(resolved, dir))) { - throw new Error(`Path must be within: ${safeDirs.join(', ')}`); - } + // Path validation — resolve to absolute and check against safe dirs. + // Fixes #707: relative paths previously bypassed the safe directory check. + // Mirrors validateOutputPath() — resolves symlinks (e.g., macOS /tmp → /private/tmp). + const resolved = path.resolve(filePath); + let resolvedReal = resolved; + try { resolvedReal = fs.realpathSync(resolved); } catch { + // File may not exist yet — resolve parent dir instead + try { resolvedReal = path.join(fs.realpathSync(path.dirname(resolved)), path.basename(resolved)); } catch {} } - if (path.normalize(filePath).includes('..')) { - throw new Error('Path traversal sequences (..) are not allowed'); + if (!SAFE_DIRECTORIES.some(dir => isPathWithin(resolvedReal, dir))) { + throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`); } if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`); const raw = fs.readFileSync(filePath, 'utf-8'); diff --git a/browse/test/commands.test.ts b/browse/test/commands.test.ts index 8434e2ef..2c006955 100644 --- a/browse/test/commands.test.ts +++ b/browse/test/commands.test.ts @@ -1811,7 +1811,8 @@ describe('Path traversal prevention', () => { await handleWriteCommand('cookie-import', ['../../etc/shadow'], bm); expect(true).toBe(false); } catch (err: any) { - expect(err.message).toContain('Path traversal'); + // Traversal blocked by safe-directory check (#707) or explicit .. check + expect(err.message).toMatch(/Path must be within|Path traversal/); } });