From de08279969f49f7a95376cc1f36273ce5a45644c Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 14 Aug 2026 18:46:32 -0700 Subject: [PATCH] fix(make-pdf): write browse-bound temp files under the safe-dirs allowlist os.tmpdir() on macOS resolves to /var/folders/..., which fails browse's safe-dirs validation ([/tmp, cwd]) since the v1.6.0.0 --from-file tightening. Default PDF output (generate with no -o), the preview HTML, tmpFile() scratch files, and setup's smoke-test fixture/output all wrote there, so browse rejected the paths it was asked to read or write. Export PAYLOAD_TMP_DIR from browseClient (the existing TEMP_DIR convention: os.tmpdir() on Windows, /tmp elsewhere) and route orchestrator.ts and setup.ts temp files through it. Contributed by @lvthewah (PR #2505; the browse-binary directory guard from that PR landed separately via PR #2538). Co-Authored-By: Claude Fable 5 --- make-pdf/src/browseClient.ts | 8 +++++--- make-pdf/src/orchestrator.ts | 7 +++---- make-pdf/src/setup.ts | 5 ++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/make-pdf/src/browseClient.ts b/make-pdf/src/browseClient.ts index 08213a0e5..099760c8c 100644 --- a/make-pdf/src/browseClient.ts +++ b/make-pdf/src/browseClient.ts @@ -195,16 +195,18 @@ function runBrowse(args: string[]): string { } /** - * Write a payload to a tmp file and return the path. Used for any payload - * >4KB to avoid Windows argv limits (Codex round 2 #3). + * Temp dir for any file handed to browse (payloads, rendered HTML, PDF output). * * Path must be under the browse safe-dirs allowlist (/tmp or cwd on * non-Windows; os.tmpdir on Windows). v1.6.0.0 tightened --from-file * validation to close a CLI/API parity gap (PR #1103), so os.tmpdir() * on macOS (/var/folders/...) now fails validateReadPath. Use the same * TEMP_DIR convention as browse/src/platform.ts. + * + * Exported because orchestrator.ts and setup.ts write files that browse must + * read back; os.tmpdir() there trips the same validateReadPath rejection. */ -const PAYLOAD_TMP_DIR = process.platform === "win32" ? os.tmpdir() : "/tmp"; +export const PAYLOAD_TMP_DIR = process.platform === "win32" ? os.tmpdir() : "/tmp"; function writePayloadFile(payload: Record): string { const hash = crypto.createHash("sha256") diff --git a/make-pdf/src/orchestrator.ts b/make-pdf/src/orchestrator.ts index 12a21570d..9fb940849 100644 --- a/make-pdf/src/orchestrator.ts +++ b/make-pdf/src/orchestrator.ts @@ -15,7 +15,6 @@ */ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; import * as crypto from "node:crypto"; import { spawn } from "node:child_process"; @@ -86,7 +85,7 @@ export async function generate(opts: GenerateOptions): Promise { const to = opts.to ?? "pdf"; const outputPath = path.resolve( - opts.output ?? path.join(os.tmpdir(), `${deriveSlug(input)}.${to}`), + opts.output ?? path.join(browseClient.PAYLOAD_TMP_DIR, `${deriveSlug(input)}.${to}`), ); // Stage 1: read markdown @@ -358,7 +357,7 @@ export async function preview(opts: PreviewOptions): Promise { progress.end("Rendering HTML", `${rendered.meta.wordCount} words`); // Write to a stable path under /tmp so the user can reload in the same tab. - const previewPath = path.join(os.tmpdir(), `make-pdf-preview-${deriveSlug(input)}.html`); + const previewPath = path.join(browseClient.PAYLOAD_TMP_DIR, `make-pdf-preview-${deriveSlug(input)}.html`); fs.writeFileSync(previewPath, rendered.html, "utf8"); progress.begin("Opening preview"); @@ -378,7 +377,7 @@ function deriveSlug(p: string): string { function tmpFile(ext: string): string { const hash = crypto.randomBytes(6).toString("hex"); - return path.join(os.tmpdir(), `make-pdf-${process.pid}-${hash}.${ext}`); + return path.join(browseClient.PAYLOAD_TMP_DIR, `make-pdf-${process.pid}-${hash}.${ext}`); } function tryOpen(pathOrUrl: string): void { diff --git a/make-pdf/src/setup.ts b/make-pdf/src/setup.ts index 4137f7013..8135edc66 100644 --- a/make-pdf/src/setup.ts +++ b/make-pdf/src/setup.ts @@ -10,7 +10,6 @@ * 6. Print a 3-command cheatsheet */ -import * as os from "node:os"; import * as path from "node:path"; import * as fs from "node:fs"; @@ -77,8 +76,8 @@ export async function runSetup(): Promise { "The second paragraph contains curly quotes (\"hello\"), an em dash -- like this, and an ellipsis... all of which should render correctly.", "", ].join("\n"); - const fixturePath = path.join(os.tmpdir(), `make-pdf-smoke-${process.pid}.md`); - const outPath = path.join(os.tmpdir(), `make-pdf-smoke-${process.pid}.pdf`); + const fixturePath = path.join(browseClient.PAYLOAD_TMP_DIR, `make-pdf-smoke-${process.pid}.md`); + const outPath = path.join(browseClient.PAYLOAD_TMP_DIR, `make-pdf-smoke-${process.pid}.pdf`); fs.writeFileSync(fixturePath, fixture, "utf8"); try {