Skip to content

Refuse a path where a command name belongs - #69

Merged
l1a merged 1 commit into
mainfrom
fix/completions-helper-rejects-a-path
Aug 25, 2026
Merged

Refuse a path where a command name belongs#69
l1a merged 1 commit into
mainfrom
fix/completions-helper-rejects-a-path

Conversation

@l1a

@l1a l1a commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Tooling only; no runtime change. 145 tests, unchanged. scripts/install_completions.pytemplate v3.

Found in rusticprofile, where this helper destroyed a working binary on a host taking hourly backups — a 3.6 MB executable replaced by a 21 KB bash completion script. etr's copy was identical apart from line endings and carried the same defect.

The mechanism

binaries are command names, and the output path is directory / pattern.format(bin=binary). Path("/dest") / "/abs/path" discards the left operand, so an absolute argument relocates every write out of the completion directory and onto the path itself — which under --from-path is the installed binary.

It fails in the worst available order:

  • --from-path runs [binary], so an absolute path works for the read and only breaks the write. Generation succeeds, then destroys its own input. Exit 0, nothing printed.
  • The flag is called --from-path, which invites exactly the argument that breaks it — so documenting it would not have prevented it.

The fix

reject_path_like() refuses any argument containing a path separator or resolving absolute, before any file is written, and names the correct form in the error.

Watched failing in both places it is enforced — neutering the condition fails --self-test ("rejects an absolute path — expected True, got False") and fails just standard-check, which just check depends on. Re-running the original accident against a stand-in file now leaves it byte-identical instead of clobbered.

A fourth self-test case pins the property — joining a directory with an absolute string yields the absolute string — so the check survives a rewrite of the guard.

Scope

etr's own recipes were never at risk: install, install-tag and standard-check pass {{BINS}}, bare names. Checked, not assumed. The exposure is anyone invoking the helper directly, which is how it happened.

Incidentally repaired: this file's worktree copy was CRLF while the index was LF (git ls-files --eoli/lf w/crlf), stale from before .gitattributes landed — which is why this is a 50-line diff rather than a whole-file rewrite.

Test plan

  • just pr green — fmt + clippy --all-targets, 145 tests, man pages build
  • just standard-check passes; fails when the guard is neutered
  • Accident reproduced against a stand-in: refused, file byte-identical
  • Wiki Development.md updated and pushed before this PR (§4.11)

install_completions.py takes a command NAME, but the output path is
`directory / pattern.format(bin=binary)` and pathlib discards the left
operand when the right side is absolute. Passing a path -- which the
flag `--from-path` invites -- therefore wrote the completion script
over the binary it was asked to read.

Found in rusticprofile, where it destroyed a working binary on a host
taking hourly backups. This copy was identical apart from line
endings.

`--from-path` runs [binary], so an absolute path works for the READ
and only breaks the write: generation succeeds, then destroys its own
input, exit 0.

Now refused before any file is written. Watched failing: neutering the
guard fails --self-test and fails just standard-check, which just
check depends on. etr's own recipes pass bare names and were never at
risk.

Also repairs this file's worktree copy, which was CRLF while the index
was LF (stale from before .gitattributes).

Template v3.

Assisted-By: Claude Opus 5
@l1a
l1a merged commit 15ff160 into main Aug 25, 2026
22 checks passed
@l1a
l1a deleted the fix/completions-helper-rejects-a-path branch August 25, 2026 02:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant