* 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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
* 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 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>