mirror of https://github.com/garrytan/gstack.git
Merge 907c67e9bf into 1b317aae9a
This commit is contained in:
commit
4af23dd75b
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
name: browse
|
||||
version: 1.0.0
|
||||
model: sonnet
|
||||
description: |
|
||||
Fast web browsing for Claude Code via persistent headless Chromium daemon. Navigate to any URL,
|
||||
read page content, click elements, fill forms, run JavaScript, take screenshots,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,22 @@ 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';
|
||||
|
||||
/**
|
||||
* Validates that a file path is within an allowed directory to prevent path traversal attacks.
|
||||
* Allowed directories: /tmp and the current working directory.
|
||||
*/
|
||||
function validateOutputPath(filePath: string): void {
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
const tmpDir = '/tmp';
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Allow paths in /tmp or under CWD
|
||||
if (!resolvedPath.startsWith(tmpDir + '/') && !resolvedPath.startsWith(cwd + '/')) {
|
||||
throw new Error(`Security: Output path must be within /tmp or the current working directory. Received: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleMetaCommand(
|
||||
command: string,
|
||||
|
|
@ -73,6 +89,7 @@ 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}`;
|
||||
}
|
||||
|
|
@ -80,6 +97,7 @@ export async function handleMetaCommand(
|
|||
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}`;
|
||||
}
|
||||
|
|
@ -87,6 +105,7 @@ export async function handleMetaCommand(
|
|||
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 },
|
||||
|
|
|
|||
|
|
@ -8,6 +8,27 @@
|
|||
import type { BrowserManager } from './browser-manager';
|
||||
import { consoleBuffer, networkBuffer } from './buffers';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Validates that a file path is within allowed directories to prevent path traversal.
|
||||
* Allowed directories: /tmp, CWD, and subdirectories of CWD.
|
||||
*/
|
||||
function validateFilePath(filePath: string): void {
|
||||
// Reject absolute paths outside /tmp
|
||||
if (path.isAbsolute(filePath) && !filePath.startsWith('/tmp/')) {
|
||||
throw new Error(`Security: Absolute paths must be within /tmp. Received: ${filePath}`);
|
||||
}
|
||||
|
||||
// Resolve the path and check for traversal
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
const cwd = process.cwd();
|
||||
const tmpDir = '/tmp';
|
||||
|
||||
if (!resolvedPath.startsWith(tmpDir + '/') && !resolvedPath.startsWith(cwd + '/')) {
|
||||
throw new Error(`Security: File must be within /tmp or the current working directory. Received: ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleReadCommand(
|
||||
command: string,
|
||||
|
|
@ -98,6 +119,7 @@ export async function handleReadCommand(
|
|||
case 'eval': {
|
||||
const filePath = args[0];
|
||||
if (!filePath) throw new Error('Usage: browse eval <js-file>');
|
||||
validateFilePath(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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue