diff --git a/crates/nothooks/src/runner.rs b/crates/nothooks/src/runner.rs index 0be1c47..d8c0b47 100644 --- a/crates/nothooks/src/runner.rs +++ b/crates/nothooks/src/runner.rs @@ -89,6 +89,10 @@ impl HookRunner { Ok(i) => i, Err(msg) => return HookResult::Failed(msg), }; + // SAFETY: `interp` is derived from the file extension or an explicit `interpreter` field + // in HookSpec (both come from config, not untrusted shell input). `spec.script` is a + // filesystem path from config. Both are passed as discrete args with no shell involved, + // so argument injection is not possible. let result = Command::new(&interp).arg(&spec.script).status(); match result { diff --git a/crates/notsecrets/src/sources/bitwarden.rs b/crates/notsecrets/src/sources/bitwarden.rs index ec6f4e9..6f22313 100644 --- a/crates/notsecrets/src/sources/bitwarden.rs +++ b/crates/notsecrets/src/sources/bitwarden.rs @@ -83,6 +83,9 @@ impl BitwardenSource { session }; + // SAFETY: `item_name` and `session` are user-controlled values (config item name and + // BW_SESSION env var / bw-unlock stdout), but are passed as discrete `.args()` elements, + // not interpolated into a shell string. No shell is involved, so injection is not possible. let output = Command::new("bw") .args(["get", "notes", &self.item_name, "--session", &session]) .output() diff --git a/crates/notstrap/src/repo.rs b/crates/notstrap/src/repo.rs index c7a23ba..2dfa50a 100644 --- a/crates/notstrap/src/repo.rs +++ b/crates/notstrap/src/repo.rs @@ -6,6 +6,9 @@ pub fn clone_if_missing(url: &str, dest: &Path) -> Result { if dest.exists() { return Ok(false); } + // SAFETY: `url` comes from the notstrap.toml config and `dest` is a `Path` derived from + // the resolved dotfiles directory. Both are passed as discrete `.arg()` calls with no + // shell involved, so argument injection is not possible. let status = Command::new("git") .arg("clone") .arg(url)