Files
msd-core/.changeset/eager-badgers-bark.md
sim 2a6f74027f chore(#4653): backfill PR 4672 into both changesets
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>
2026-09-12 18:49:48 -04:00

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)