From 0fb139d51e97b71d96527400b8d4166518171599 Mon Sep 17 00:00:00 2001 From: gus Date: Sat, 4 Apr 2026 20:31:24 -0300 Subject: [PATCH] fix(design): bind server to localhost and validate reload paths The design comparison board server binds to 0.0.0.0 (all interfaces) with no authentication. The /api/reload endpoint reads arbitrary files from disk. Any device on the same network can read local files by POSTing a path to /api/reload and fetching the result via GET /. Tested: started the server, POSTed {"html":"/etc/hosts"} to /api/reload, fetched / and got the contents of /etc/hosts. Fix: - Bind to 127.0.0.1 (matching the browse server at server.ts:1042) - Validate reload paths are within cwd or tmpdir --- design/src/serve.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/design/src/serve.ts b/design/src/serve.ts index 7d974905c..93d33e750 100644 --- a/design/src/serve.ts +++ b/design/src/serve.ts @@ -33,19 +33,21 @@ */ import fs from "fs"; +import os from "os"; import path from "path"; import { spawn } from "child_process"; export interface ServeOptions { html: string; port?: number; + hostname?: string; // default '127.0.0.1' — localhost only timeout?: number; // seconds, default 600 (10 min) } type ServerState = "serving" | "regenerating" | "done"; export async function serve(options: ServeOptions): Promise { - const { html, port = 0, timeout = 600 } = options; + const { html, port = 0, hostname = '127.0.0.1', timeout = 600 } = options; // Validate HTML file exists if (!fs.existsSync(html)) { @@ -59,6 +61,7 @@ export async function serve(options: ServeOptions): Promise { const server = Bun.serve({ port, + hostname, fetch(req) { const url = new URL(req.url); @@ -182,6 +185,17 @@ export async function serve(options: ServeOptions): Promise { ); } + // Validate path is within cwd or temp directory + const resolved = path.resolve(newHtmlPath); + const safeDirs = [process.cwd(), os.tmpdir()]; + const isSafe = safeDirs.some(dir => resolved.startsWith(dir + path.sep) || resolved === dir); + if (!isSafe) { + return Response.json( + { error: `Path must be within working directory or temp` }, + { status: 403 } + ); + } + // Swap the HTML content htmlContent = fs.readFileSync(newHtmlPath, "utf-8"); state = "serving";