From 037588ecf6092a2bbedb3c1dc26a2f1c0f2d1088 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 7 Sep 2026 20:04:02 -0400 Subject: [PATCH] fix(help): stop eight functions executing on --help These eight ignore $argv entirely, so passing --help ran the command instead of describing it. The new check shadows every external binary they reach with a recording stub on PATH and fails if anything is invoked. Before the fix it reported, verbatim: cleanup --help EXECUTED: pacman -Qtdq fzf-update --help EXECUTED: git clone --depth 1 https://github.com/junegunn/fzf.git /tmp/.../.fzf limine-edit --help EXECUTED: sudoedit /boot/limine.conf sudo limine-enroll-config sudo limine-mkinitcpio sudo sbctl sign-all lock --help EXECUTED: loginctl lock-session screensleep --help EXECUTED: busctl --user call org.kde.kglobalaccel ... invokeShortcut s Turn Off Screen sudo-toggle --help EXECUTED: sudo stat -c %s /etc/sudoers.d/nofail-toggle sudo tee /etc/sudoers.d/nofail-toggle tmux-clean --help EXECUTED: tmux list-sessions -F #{session_name} #{session_attached} upgrade --help EXECUTED: paru -Syu --noconfirm cleanup's log line is the read that precedes `sudo pacman -Rns $orphans`, which the stub suppressed by returning no orphans; on a real machine with orphans present the removal ran. Each now answers --help from its own comment header. The call site is the first statement of the body, above the C4 guard, so help stays reachable when the component is disabled and nothing side-effecting runs first. --- functions/cleanup.fish | 2 ++ functions/fzf-update.fish | 2 ++ functions/limine-edit.fish | 2 ++ functions/lock.fish | 2 ++ functions/screensleep.fish | 2 ++ functions/sudo-toggle.fish | 2 ++ functions/tmux-clean.fish | 2 ++ functions/upgrade.fish | 2 ++ tests/functional.fish | 52 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 68 insertions(+) diff --git a/functions/cleanup.fish b/functions/cleanup.fish index eaa30db..b908ee7 100644 --- a/functions/cleanup.fish +++ b/functions/cleanup.fish @@ -14,6 +14,8 @@ # EXAMPLE # cleanup function cleanup --description 'Log orphans to ~/.removed_orphans and remove them' + __fish_help_header (status current-function) $argv; and return 0 + set -l orphans (pacman -Qtdq) if test -n "$orphans" echo "📝 Logging orphans to ~/.removed_orphans..." diff --git a/functions/fzf-update.fish b/functions/fzf-update.fish index 1a403ed..5297dc8 100644 --- a/functions/fzf-update.fish +++ b/functions/fzf-update.fish @@ -14,6 +14,8 @@ # EXAMPLE # fzf-update function fzf-update --description 'Install or upgrade fzf from git HEAD' + __fish_help_header (status current-function) $argv; and return 0 + if test -d ~/.fzf echo "Updating fzf..." git -C ~/.fzf pull --ff-only diff --git a/functions/limine-edit.fish b/functions/limine-edit.fish index f22a652..9f3b502 100644 --- a/functions/limine-edit.fish +++ b/functions/limine-edit.fish @@ -16,6 +16,8 @@ # EXAMPLE # limine-edit function limine-edit --description 'Safely edit and re-verify Limine configuration' + __fish_help_header (status current-function) $argv; and return 0 + # 1. Open the config with sudoedit sudoedit /boot/limine.conf diff --git a/functions/lock.fish b/functions/lock.fish index c1e61bc..302d29d 100644 --- a/functions/lock.fish +++ b/functions/lock.fish @@ -13,5 +13,7 @@ # EXAMPLE # lock function lock --wraps='loginctl' --description 'alias lock=loginctl' + __fish_help_header (status current-function) $argv; and return 0 + loginctl lock-session end diff --git a/functions/screensleep.fish b/functions/screensleep.fish index 452ff9e..0bb4442 100644 --- a/functions/screensleep.fish +++ b/functions/screensleep.fish @@ -14,6 +14,8 @@ # EXAMPLE # screensleep function screensleep --description 'Turn off the display using KDE PowerDevil' + __fish_help_header (status current-function) $argv; and return 0 + # Optional: 1-second delay to ensure no keystrokes wake it immediately sleep 1 busctl --user call \ diff --git a/functions/sudo-toggle.fish b/functions/sudo-toggle.fish index e46547f..c218e98 100644 --- a/functions/sudo-toggle.fish +++ b/functions/sudo-toggle.fish @@ -19,6 +19,8 @@ # EXAMPLE # sudo-toggle function sudo-toggle --description 'Toggle sudo password requirement on/off' + __fish_help_header (status current-function) $argv; and return 0 + # Check the file size using sudo stat to see if our bypass rule is active set -l file_size (sudo stat -c %s /etc/sudoers.d/nofail-toggle 2>/dev/null) diff --git a/functions/tmux-clean.fish b/functions/tmux-clean.fish index cf775c4..c31765e 100644 --- a/functions/tmux-clean.fish +++ b/functions/tmux-clean.fish @@ -14,6 +14,8 @@ # EXAMPLE # tmux-clean function tmux-clean --description 'Kill all tmux sessions except the current one' + __fish_help_header (status current-function) $argv; and return 0 + # Get a list of all session names that are NOT currently attached set sessions (tmux list-sessions -F '#{session_name} #{session_attached}' | string match -rv ' 1$' | string split -f1 ' ') diff --git a/functions/upgrade.fish b/functions/upgrade.fish index 68702bb..20d2fc9 100644 --- a/functions/upgrade.fish +++ b/functions/upgrade.fish @@ -21,6 +21,8 @@ # EXAMPLE # upgrade function upgrade --description 'Full system upgrade via paru or yay' + __fish_help_header (status current-function) $argv; and return 0 + # Opinionated guard (C4): integrations disabled if not __fish_config_op_enabled (status current-function) set -l c_err (set_color red) diff --git a/tests/functional.fish b/tests/functional.fish index 2f356e4..f027194 100644 --- a/tests/functional.fish +++ b/tests/functional.fish @@ -237,6 +237,58 @@ function test_help_renderer_degrades_safely test $failed -eq 0 end +function test_help_never_executes_destructive_path + # These eight ignore $argv entirely, so before the header-driven help + # landed, `upgrade --help` ran `paru -Syu --noconfirm`. The check has + # to prove --help does NOT reach the destructive path *without* ever + # running it: every external binary the eight can reach is shadowed by + # a recording stub on PATH, and the recorder must stay empty. + # + # WARNING: a silent pass here means a MISSING STUB, not success. If a + # function shows neither an EXECUTED line nor its own help, its + # command is absent from the stub list below -- add it. A test that + # cannot fail proves nothing about a body that runs sudo pacman -Rns. + set -l root (realpath (dirname (status filename))/..) + set -l tmp (mktemp -d) + mkdir -p $tmp/bin + set -l log $tmp/invoked.log + touch $log + + for b in sudo pacman paru yay loginctl busctl tmux systemd-inhibit \ + sudoedit limine-enroll-config limine-mkinitcpio sbctl git fzf steam + printf '#!/bin/sh\necho "$(basename "$0") $*" >> %s\n' $log >$tmp/bin/$b + chmod +x $tmp/bin/$b + end + + set -l failed 0 + for fn in cleanup fzf-update limine-edit lock screensleep sudo-toggle \ + tmux-clean upgrade + set -l out (env TERM=dumb PATH="$tmp/bin:$PATH" HOME=$tmp \ + fish --no-config -c \ + "set -g fish_function_path $root/functions $fish_function_path + $fn --help" 2>/dev/null) + set -l code $status + + if test $code -ne 0 + echo " $fn --help exited $code, expected 0" + set failed 1 + end + if not contains -- $fn $out + echo " $fn --help did not print its own help" + set failed 1 + end + set -l ran (string trim -- (command cat $log)) + if test -n "$ran" + echo " $fn --help EXECUTED: $ran" + set failed 1 + end + echo -n "" >$log + end + + rm -rf $tmp + test $failed -eq 0 +end + function functional_test_main set -l names (functions -a | string match 'test_*' | sort) set -l failed 0