Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions browse/src/meta-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,43 @@ import type { BrowserManager } from './browser-manager';
import { handleSnapshot } from './snapshot';
import * as Diff from 'diff';
import * as fs from 'fs';
import * as path from 'path';

// Security: Path validation to prevent path traversal attacks
const SAFE_DIRECTORIES = ['/tmp', process.cwd()];

/**
* Validates that a file path is within allowed directories
* @param filePath The path to validate
* @throws Error if path is outside safe directories
*/
function validateOutputPath(filePath: string): void {
const resolved = path.resolve(filePath);
const isSafe = SAFE_DIRECTORIES.some(dir => resolved.startsWith(dir));
if (!isSafe) {
throw new Error(`Path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
}
}

/**
* Validates that a file path for reading doesn't contain path traversal
* @param filePath The path to validate
* @throws Error if path contains traversal sequences
*/
function validateReadPath(filePath: string): void {
// Reject absolute paths outside allowed directories
if (path.isAbsolute(filePath)) {
const isSafe = SAFE_DIRECTORIES.some(dir => path.resolve(filePath).startsWith(dir));
if (!isSafe) {
throw new Error(`Absolute path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
}
}
// Check for path traversal sequences
const normalized = path.normalize(filePath);
if (normalized.includes('..')) {
throw new Error('Path traversal sequences (..) are not allowed');
}
}

export async function handleMetaCommand(
command: string,
Expand Down Expand Up @@ -73,20 +110,23 @@ export async function handleMetaCommand(
case 'screenshot': {
const page = bm.getPage();
const screenshotPath = args[0] || '/tmp/browse-screenshot.png';
validateOutputPath(screenshotPath);
await page.screenshot({ path: screenshotPath, fullPage: true });
return `Screenshot saved: ${screenshotPath}`;
}

case 'pdf': {
const page = bm.getPage();
const pdfPath = args[0] || '/tmp/browse-page.pdf';
validateOutputPath(pdfPath);
await page.pdf({ path: pdfPath, format: 'A4' });
return `PDF saved: ${pdfPath}`;
}

case 'responsive': {
const page = bm.getPage();
const prefix = args[0] || '/tmp/browse-responsive';
validateOutputPath(prefix);
const viewports = [
{ name: 'mobile', width: 375, height: 812 },
{ name: 'tablet', width: 768, height: 1024 },
Expand Down
25 changes: 25 additions & 0 deletions browse/src/read-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,30 @@
import type { BrowserManager } from './browser-manager';
import { consoleBuffer, networkBuffer } from './buffers';
import * as fs from 'fs';
import * as path from 'path';

// Security: Path validation to prevent path traversal attacks
const SAFE_DIRECTORIES = ['/tmp', process.cwd()];

/**
* Validates that a file path for reading doesn't contain path traversal
* @param filePath The path to validate
* @throws Error if path contains traversal sequences or is outside safe directories
*/
function validateReadPath(filePath: string): void {
// Reject absolute paths outside allowed directories
if (path.isAbsolute(filePath)) {
const isSafe = SAFE_DIRECTORIES.some(dir => path.resolve(filePath).startsWith(dir));
if (!isSafe) {
throw new Error(`Absolute path must be within: ${SAFE_DIRECTORIES.join(', ')}`);
}
}
// Check for path traversal sequences
const normalized = path.normalize(filePath);
if (normalized.includes('..')) {
throw new Error('Path traversal sequences (..) are not allowed');
}
}

export async function handleReadCommand(
command: string,
Expand Down Expand Up @@ -98,6 +122,7 @@ export async function handleReadCommand(
case 'eval': {
const filePath = args[0];
if (!filePath) throw new Error('Usage: browse eval <js-file>');
validateReadPath(filePath);
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
const code = fs.readFileSync(filePath, 'utf-8');
const result = await page.evaluate(code);
Expand Down