Skip to content

mds init follows a symlink at the target path, and --force truncates it in place #386

Description

@dean0x

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:

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions