Found during the #227 review (PR #385), where mds init is the one deliberately raw std::fs::write site left outside atomic_write_file (allow-listed in crates/mds-cli/tests/write_funnel.rs). The allow-list justification originally claimed init "only ever creates a file"; that is false and the text was corrected in PR #385, but the behaviour itself is pre-existing and unchanged.
Reproduction (built from PR #385 head, macOS):
ln -s "$PWD/victim2.txt" t1.mds # dangling link, no --force
mds init t1.mds # exit 0, "Created t1.mds"; t1.mds is still a symlink; victim2.txt was created through it
ln -s "$PWD/victim.txt" t2.mds # live link
mds init t2.mds --force # exit 0; victim.txt TRUNCATED and overwritten with the starter template
Cause: Path::exists() and std::fs::write both follow symlinks, and --force skips the exists-check entirely (crates/mds-cli/src/main.rs, the Init arm).
Impact: low (the template is a fixed public starter, the path is user-typed, and the result is reproducible by re-running), but it is the only CLI write that can be redirected through a symlink, and it is the only site outside the atomic funnel.
Options:
- Route
init through crate::output::atomic_write_file(path, template, Durability::RenameOnly) — gains the symlink refusal (live and dangling) and the create-with-umask-mode branch for free; drop the allow-list entry so the funnel becomes unconditional for crates/mds-cli/src.
- Keep the raw write but add a
symlink_metadata refusal before it (mirrors the primitive's rule).
Option 1 is preferred: one primitive, one enforcement point. Needs a positive-control test pair (link refused; plain path created) in crates/mds-cli/tests/cli_commands.rs or wherever init is tested today.
Refs #227, #385.
Found during the #227 review (PR #385), where
mds initis the one deliberately rawstd::fs::writesite left outsideatomic_write_file(allow-listed incrates/mds-cli/tests/write_funnel.rs). The allow-list justification originally claimedinit"only ever creates a file"; that is false and the text was corrected in PR #385, but the behaviour itself is pre-existing and unchanged.Reproduction (built from PR #385 head, macOS):
Cause:
Path::exists()andstd::fs::writeboth follow symlinks, and--forceskips the exists-check entirely (crates/mds-cli/src/main.rs, theInitarm).Impact: low (the template is a fixed public starter, the path is user-typed, and the result is reproducible by re-running), but it is the only CLI write that can be redirected through a symlink, and it is the only site outside the atomic funnel.
Options:
initthroughcrate::output::atomic_write_file(path, template, Durability::RenameOnly)— gains the symlink refusal (live and dangling) and the create-with-umask-mode branch for free; drop the allow-list entry so the funnel becomes unconditional forcrates/mds-cli/src.symlink_metadatarefusal before it (mirrors the primitive's rule).Option 1 is preferred: one primitive, one enforcement point. Needs a positive-control test pair (link refused; plain path created) in
crates/mds-cli/tests/cli_commands.rsor whereverinitis tested today.Refs #227, #385.