{"record":{"id":"613665266e73c9ab","repo":"garrytan/gstack","slug":"pdf-from-file-payloadpath-must-be-under-sa","errorCode":null,"errorMessage":"pdf: --from-file ${payloadPath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the payload into the project tree or /tmp first.","messagePattern":"pdf: --from-file (.+?) must be under (.+?) \\(security policy\\)\\. Copy the payload into the project tree or /tmp first\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/meta-commands.ts","lineNumber":148,"sourceCode":"  if (result.format && (result.width || result.height)) {\n    throw new Error('pdf: --format is mutex with --width/--height');\n  }\n  if (result.pageNumbers && result.footerTemplate) {\n    throw new Error('pdf: --page-numbers is mutex with --footer-template (page-numbers writes the footer itself)');\n  }\n\n  return result;\n}\n\nexport function parsePdfFromFile(payloadPath: string): ParsedPdfArgs {\n  // Parity with load-html --from-file (browse/src/write-commands.ts) and\n  // the direct load-html <file> path: every caller-supplied file path\n  // must pass validateReadPath so the safe-dirs policy can't be skirted\n  // by routing reads through the --from-file shortcut.\n  try {\n    validateReadPath(path.resolve(payloadPath));\n  } catch {\n    throw new Error(\n      `pdf: --from-file ${payloadPath} must be under ${SAFE_DIRECTORIES.join(' or ')} (security policy). Copy the payload into the project tree or /tmp first.`\n    );\n  }\n  const raw = fs.readFileSync(payloadPath, 'utf8');\n  let json: any;\n  try {\n    json = JSON.parse(raw);\n  } catch (err) {\n    const msg = err instanceof Error ? err.message : String(err);\n    throw new Error(`pdf: --from-file ${payloadPath} is not valid JSON (${msg}).`);\n  }\n  if (json === null || typeof json !== 'object' || Array.isArray(json)) {\n    throw new Error(`pdf: --from-file ${payloadPath} must be a JSON object, got ${Array.isArray(json) ? 'array' : typeof json}.`);\n  }\n  const out: ParsedPdfArgs = {\n    output: json.output || `${TEMP_DIR}/browse-page.pdf`,\n    format: json.format,\n    width: json.width,","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/meta-commands.ts#L130-L166","documentation":"Thrown by parsePdfFromFile() when the resolved path of the --from-file payload fails validateReadPath(), meaning it is outside all configured SAFE_DIRECTORIES (typically the project tree and /tmp). This is a security policy enforcement — it prevents the CLI from reading arbitrary files on the filesystem via the --from-file shortcut, maintaining parity with load-html path validation.","triggerScenarios":"Passing a --from-file path that resolves outside SAFE_DIRECTORIES, e.g., a file in the user's home directory, /etc, or any non-project/non-tmp location.","commonSituations":"User stores PDF config JSON in their home directory or a shared system path. Or an attacker tries to exfiltrate system files by pointing --from-file at sensitive paths.","solutions":["Copy or move the payload JSON file into the project tree (under the project root)","Copy or move the payload into /tmp","If running in a different project, ensure the path is relative to the current project root"],"exampleFix":"# before\n$B pdf --from-file ~/configs/pdf.json\n\n# after\ncp ~/configs/pdf.json /tmp/pdf.json\n$B pdf --from-file /tmp/pdf.json","handlingStrategy":"validation","validationCode":"// Validate path is under a safe directory before calling\nimport path from 'path';\nconst resolved = path.resolve(payloadPath);\nconst isSafe = SAFE_DIRECTORIES.some(dir => resolved.startsWith(path.resolve(dir)));\nif (!isSafe) {\n  throw new Error(`Path must be under ${SAFE_DIRECTORIES.join(' or ')}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  const parsed = parsePdfFromFile(payloadPath);\n} catch (e) {\n  if (e.message.includes('security policy')) {\n    fs.copyFileSync(payloadPath, '/tmp/pdf-config.json');\n    parsed = parsePdfFromFile('/tmp/pdf-config.json');\n  }\n}","preventionTips":["Always store PDF config payloads in the project tree or /tmp","Never reference absolute paths outside the project root in --from-file","Run validateReadPath before invoking if unsure about directory safety"],"tags":["pdf","security","path-validation","safe-dirs"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}