Files
omarchy/test/shell.d/update-hook-security-test.sh
T

118 lines
5.2 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
source "$SHELL_TEST_DIR/fixtures/sudo-boundary-test.sh"
copy_boundary_file bin/omarchy-update
copy_boundary_file bin/omarchy-refresh-pacman
# Replace the step symlink, preserving the real fixture dispatcher.
rm "$SUDO_TEST_ROOT/bin/omarchy-update-aur-pkgs"
copy_boundary_file bin/omarchy-update-aur-pkgs
export OMARCHY_UPDATE_LOGGED=1
run_update() {
"$SUDO_TEST_ROOT/bin/omarchy-update" "$@" >"$boundary_tmp/output" 2>&1
}
for args in '-y' ''; do
reset_boundary
touch "$SUDO_TEST_CACHE"
run_update $args || fail "update failed" "$(<"$boundary_tmp/output")"
assert_boundary_cold "successful update"
grep -q '^sudo -N /usr/bin/true$' "$SUDO_TEST_LOG" || fail "update package helpers must use no-update sudo"
python3 - "$SUDO_TEST_LOG" <<'PY'
import sys
s=open(sys.argv[1]).read().splitlines()
positions=[next(i for i,line in enumerate(s) if line.startswith(prefix)) for prefix in ['step:omarchy-update-restart --services-only','step:omarchy-update-stay-awake stop','step:yay','step:omarchy-hook post-update','step:omarchy-update-mise','step:omarchy-update-restart --reboot-only']]
assert positions==sorted(positions), s
assert not any(line.startswith('sudo -N ') for line in s[positions[3]:]), s
PY
pass "update $args runs privileged phases before hooks and exits cold"
done
for step in omarchy-update-system-pkgs yay omarchy-hook omarchy-update-mise; do
reset_boundary
export SUDO_TEST_FAIL_STEP=$step
if run_update -y; then fail "$step failure must fail the update"; fi
assert_boundary_cold "failed $step"
pass "update revokes credentials after $step fails"
done
reset_boundary
export SUDO_TEST_SIGNAL_STEP=omarchy-hook
if run_update -y; then fail "interrupted update must fail"; fi
assert_boundary_cold "interrupted update"
pass "update revokes credentials on TERM"
reset_boundary
export SUDO_TEST_REVOKE_FAIL=1
if run_update -y; then fail "failed initial revocation must fail the update"; fi
if grep -q '^step:' "$SUDO_TEST_LOG"; then fail "failed revocation must precede update work"; fi
pass "a failed cold start prevents update work"
reset_boundary
export SUDO_TEST_UNSUPPORTED=1
if run_update -y; then fail "unsupported sudo must prevent mixed-trust work"; fi
assert_boundary_cold "unsupported sudo"
pass "unsupported sudo fails without running update steps"
for mode in normal defer-hook run-deferred; do
reset_boundary
"$SUDO_TEST_ROOT/bin/omarchy-refresh-pacman" stable "$mode" >"$boundary_tmp/output" 2>&1 || fail "refresh $mode failed" "$(<"$boundary_tmp/output")"
assert_boundary_cold "refresh $mode"
python3 - "$SUDO_TEST_LOG" "$mode" <<'PY'
import sys
s=open(sys.argv[1]).read().splitlines();mode=sys.argv[2]
hooks=[i for i,l in enumerate(s) if l.startswith('step:omarchy-hook')]
priv=[i for i,l in enumerate(s) if l.startswith('sudo -N ')]
assert bool(hooks)==(mode!='defer-hook'), s
assert bool(priv)==(mode!='run-deferred'), s
if hooks: assert not any(i>hooks[0] for i in priv),s
PY
pass "refresh $mode preserves the final cold hook boundary"
done
for step in pacman omarchy-hook; do
reset_boundary
export SUDO_TEST_FAIL_STEP=$step
if "$SUDO_TEST_ROOT/bin/omarchy-refresh-pacman" stable >"$boundary_tmp/output" 2>&1; then fail "refresh must propagate $step failure"; fi
assert_boundary_cold "failed refresh $step"
pass "refresh revokes after $step failure"
done
# The wrapper must preserve sudo's own option parser, including validation and
# explicit --, while standalone timestamp maintenance cannot be combined with N.
for args in '-v' '-n /usr/bin/true' '--user test -- /usr/bin/true' '-- /usr/bin/true' '-k' '-K'; do
reset_boundary
"$SUDO_TEST_ROOT/default/omarchy/sudo-no-update/sudo" $args
case "$args" in
-k|-K) expected="sudo $args" ;;
*) expected="sudo -N $args" ;;
esac
[[ $(<"$SUDO_TEST_LOG") == "$expected" ]] || fail "wrapper changed options: $args" "$(<"$SUDO_TEST_LOG")"
[[ ! -e $SUDO_TEST_CACHE ]] || fail "wrapper refreshed credentials"
pass "sudo wrapper preserves $args"
done
for script in bin/omarchy-update bin/omarchy-refresh-pacman default/omarchy/sudo-no-update/sudo; do
reset_boundary
if /usr/bin/bash "$SUDO_TEST_ROOT/$script" -p >"$boundary_tmp/output" 2>&1; then fail "$script accepted an ordinary Bash launch"; fi
[[ ! -s $SUDO_TEST_LOG ]] || fail "$script reached sudo through an invalid interpreter"
pass "$script rejects a decoy privileged-mode argument"
done
reset_boundary
printf '%s\n' 'printf startup-ran >>"$SUDO_TEST_ROOT/startup-marker"' >"$boundary_tmp/startup"
BASH_ENV="$boundary_tmp/startup" ENV="$boundary_tmp/startup" run_update -y || fail "sanitized update failed" "$(<"$boundary_tmp/output")"
[[ ! -e $SUDO_TEST_ROOT/startup-marker ]] || fail "startup code leaked into an update helper"
pass "inherited startup files do not run in the updater or its child scripts"
reset_boundary
function printf() { /usr/bin/touch "$SUDO_TEST_ROOT/function-marker"; }
export -f printf
run_update -y || fail "update failed with inherited function" "$(<"$boundary_tmp/output")"
unset -f printf
[[ ! -e $SUDO_TEST_ROOT/function-marker ]] || fail "an inherited function reached an update helper"
pass "exported functions do not reach update helper interpreters"