mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
* Automation: --root refuses every outside path the same way (#136) With a root set, `resolve` canonicalized a path before checking it against the root, so an existing outside path got "is outside the allowed directory" while a missing one got the OS error ("No such file or directory", "Not a directory", "invalid path"): a confined MCP client or script could probe which files and folders exist anywhere on the disk. Containment is now decided before anything about an outside path is reported: - `..` is resolved by name first (as Windows does), so a path can't pass through an outside folder on its way back in; - on Windows another network share or device namespace (`\\host\share`, `\\?\UNC\...`, `\\.\...`) is refused by name, without contacting it; - the deepest existing ancestor, links followed, must be inside the root before anything is said about the rest; - a broken link is refused the same way, so whether its target exists stays hidden too. Every outside path now gets `<path> is outside the allowed directory <root>`, existing or not, file or folder, read or write. Filesystem errors such as "not found" are still reported for paths inside the root. Behaviour change: on Unix, `..` now means the parent by name, as on Windows (`link/../x` is `x` beside `link`), and a write such as `new/../x.pdf` that stays in the root now succeeds instead of failing with "'..' is not allowed here". Co-Authored-By: Claude Opus 5.5 <[email protected]> * CLI: script images stay inside --root (#137) `pdfcraft-cli run --script steps.json --root DIR` wrote each step's `"out"` PNG with `std::fs::write`, never through the root check the tools use: an absolute `"out"` created or replaced any file the process could write, and a relative one landed in the working directory instead of the root. The image is now saved through the new `Automation::write_output`, which uses the same resolver and atomic write as a tool's own output: outside paths get the usual "is outside the allowed directory" refusal (the step fails, nothing is written), relative paths resolve inside the root, and without --root the path is written as given, in place as before (so `/dev/stdout` still works). `run <tool> --out` goes the same way. Found while testing this, the same "." problem in two tools: - a write path naming a folder, such as "." (the root itself), staged its temporary file beside that folder, i.e. outside the root for ".", overwriting and then deleting any file there called `.root.pdfcraft-tmp`. `write_atomic` now refuses a folder before writing anything; - `image_save` adds an extension to a path without one, so "." became `root.png` beside the root. It now refuses a folder first. Co-Authored-By: Claude Opus 5.5 <[email protected]> * Automation: files named after a document stay in the output folder Found while reviewing the --root fixes for #136 and #137. Folder outputs name their files after the document and joined that name straight onto the resolved folder, without checking the result against the root: - `doc_export_images` and `doc_export_all_images` used the whole document name, so `doc_create` with `name: "../../escape"` followed by `doc_export_images` wrote `escape_page_1.png` outside the root (an absolute name went anywhere); - `page_extract` (separate files), `doc_split`, `ocr_recognize_files` and `action_run` took the last component, but on Windows a name such as `x/C:evil.pdf` gives `C:evil`, and `folder.join("C:evil-page1.pdf")` replaces the folder with a path relative to the current directory on C:. Every such name now goes through `child()`, which turns separators, colons and control characters into `_` (and prefixes a name of only dots), so the file is always a direct child of the folder. `with_root` also refuses a root that is not a folder. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>