Also corrects the Changed fragment: it named three containment exports as the only ones, which stopped being true when the lexical family was added to close DW1/DW9. It now describes one decision resolved two ways, and says why the lexical pair exists rather than leaving a reader to assume it is a weaker alternative to the realpath form. scripts/lint-docs-required.cjs now passes (ok_docs_updated) — it could not evaluate against the mandated pr:0 placeholder. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1.4 KiB
type, pr
| type | pr |
|---|---|
| Changed | 4672 |
The path-containment predicate is now a single exported seam — security.cjs no longer exports validatePath. Containment is decided in exactly one place and resolved two ways: assertWithinRoot (throws) and tryWithinRoot (returns null) resolve symlinks, while assertWithinRootLexical and tryWithinRootLexical use string resolution alone and never touch the filesystem, for the few callers that must preserve a symlink rather than resolve it or that validate a destination before it exists. requireSafePath is preserved as an alias of the throwing form. All of them return a branded ContainedPath so a validated path cannot be silently swapped for an unvalidated one. The per-call-site { allowAbsolute: true } flag is replaced by the named PathAcceptance policy, which states what it actually permits: an absolute path outside the root was always rejected and still is. The traversal rejection text Path escapes allowed directory: <resolved> is outside <base> is preserved verbatim, and no command changes what it accepts or rejects. Three rejection MESSAGES are reworded, none of which now reveals a host path it previously hid: state.cts's <label> path rejected: … becomes <label> path validation failed: …, and the sub-repo and agent-skills warnings name the condition instead of echoing the predicate's error string. (#4653)