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 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 18:46:32 -07:00
parent 7ca68da785
commit de08279969
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
3 changed files with 10 additions and 10 deletions

View File

@ -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, unknown>): string {
const hash = crypto.createHash("sha256")

View File

@ -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<string> {
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<string> {
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 {

View File

@ -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<void> {
"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 {