From 8a13eac872988c722be4cb4765211593b61d6de9 Mon Sep 17 00:00:00 2001 From: rattatui <88578725+Wheel-Smith@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:41:53 +0300 Subject: [PATCH 1/4] Defer to system-auth in the polkit stack written by fingerprint/FIDO2 setup The fingerprint and FIDO2 setup commands create /etc/pam.d/polkit-1 from scratch on Arch, where the polkit package ships its stack in /usr/lib/pam.d/polkit-1 and /etc/pam.d/polkit-1 does not exist. The hand-rolled stack listed pam_unix directly instead of including system-auth, which dropped pam_faillock from the polkit path: polkit prompts had no brute-force lockout, their failures were not recorded, and they did not count toward the lockout protecting login and sudo. Defer to system-auth, matching the vendor file and the sudo stack, keeping the clamshell gate and the pam_fprintd / pam_u2f sufficient lines in front. Add a migration to repair installs the old setup already configured, since the forward fix does not rewrite an existing polkit-1. It acts only on an Omarchy-created polkit-1 that lacks the system-auth include and carries a hardware-auth marker, preserves the configured auth lines, backs up the original, and is idempotent. Add a test asserting the stack each setup creates defers to system-auth; the created polkit content was previously untested. Co-Authored-By: Claude Opus 4.8 --- bin/omarchy-setup-security-fido2 | 8 +-- bin/omarchy-setup-security-fingerprint | 8 +-- migrations/1788256455.sh | 52 +++++++++++++++++++ test/shell.d/security-polkit-faillock-test.sh | 32 ++++++++++++ 4 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 migrations/1788256455.sh create mode 100644 test/shell.d/security-polkit-faillock-test.sh diff --git a/bin/omarchy-setup-security-fido2 b/bin/omarchy-setup-security-fido2 index 85fe2039..75a87124 100755 --- a/bin/omarchy-setup-security-fido2 +++ b/bin/omarchy-setup-security-fido2 @@ -31,11 +31,11 @@ setup_pam_config() { echo "Creating polkit configuration with FIDO2 authentication..." sudo tee /etc/pam.d/polkit-1 >/dev/null <<'EOF' auth sufficient pam_u2f.so cue authfile=/etc/fido2/fido2 -auth required pam_unix.so +auth include system-auth -account required pam_unix.so -password required pam_unix.so -session required pam_unix.so +account include system-auth +password include system-auth +session include system-auth EOF fi } diff --git a/bin/omarchy-setup-security-fingerprint b/bin/omarchy-setup-security-fingerprint index 383aa376..fd84d2db 100755 --- a/bin/omarchy-setup-security-fingerprint +++ b/bin/omarchy-setup-security-fingerprint @@ -47,11 +47,11 @@ setup_pam_config() { sudo tee /etc/pam.d/polkit-1 >/dev/null </dev/null && + ! grep -qE '^auth[[:space:]]+include[[:space:]]+system-auth' "$polkit" && + grep -qE 'omarchy-hw-laptop-closed|pam_fprintd\.so|authfile=/etc/fido2/fido2' "$polkit"; then + + echo "Rewriting $polkit to defer to system-auth (restores pam_faillock lockout)..." + + # Keep the configured hardware-auth auth lines verbatim (the clamshell gate and + # the pam_fprintd / pam_u2f 'sufficient' lines); only the bare pam_unix stack is + # replaced with the system-auth includes the vendor file and the sudo stack use. + hw_auth=$(grep -E '^auth' "$polkit" | grep -vE 'pam_unix\.so') + + rebuilt=$( + [[ -n $hw_auth ]] && printf '%s\n' "$hw_auth" + printf 'auth include system-auth\n' + printf 'account include system-auth\n' + printf 'password include system-auth\n' + printf 'session include system-auth\n' + ) + + backup="$polkit.omarchy-bak.$(date +%s)" + if sudo cp -a "$polkit" "$backup"; then + printf '%s\n' "$rebuilt" | sudo tee "$polkit" >/dev/null + + if grep -qE '^auth[[:space:]]+include[[:space:]]+system-auth' "$polkit"; then + echo "Restored polkit brute-force protection. Previous file saved at $backup." + else + echo "polkit repair could not be verified; restoring the original file." >&2 + sudo cp -a "$backup" "$polkit" + fi + else + echo "Administrator privileges are required to repair $polkit. Run omarchy-migrate again from a terminal." >&2 + fi +fi diff --git a/test/shell.d/security-polkit-faillock-test.sh b/test/shell.d/security-polkit-faillock-test.sh new file mode 100644 index 00000000..74582457 --- /dev/null +++ b/test/shell.d/security-polkit-faillock-test.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +# The fingerprint and FIDO2 setup commands each create /etc/pam.d/polkit-1 from +# scratch when the file does not already exist -- which is the normal case on +# Arch, where the polkit package ships its PAM stack in /usr/lib/pam.d/polkit-1 +# and /etc/pam.d/polkit-1 is absent. A hand-rolled stack that lists pam_unix +# directly instead of `include system-auth` silently drops pam_faillock, so +# polkit prompts would have no brute-force lockout and their failures would not +# count toward the shared tally. Assert the created stack defers to system-auth. + +for setup in omarchy-setup-security-fingerprint omarchy-setup-security-fido2; do + script="$ROOT/bin/$setup" + + # Pull the here-doc body the setup writes to /etc/pam.d/polkit-1. + body=$(sed -n "/tee \/etc\/pam.d\/polkit-1/,/^EOF\$/p" "$script") + + [[ -n $body ]] || fail "$setup writes a polkit-1 stack" + + for phase in auth account password session; do + grep -qE "^${phase}[[:space:]]+include[[:space:]]+system-auth" <<<"$body" || + fail "$setup polkit-1 $phase defers to system-auth (keeps faillock)" "$body" + done + + ! grep -qE "^(account|password|session)[[:space:]]+required[[:space:]]+pam_unix" <<<"$body" || + fail "$setup polkit-1 does not hand-roll a bare pam_unix stack" "$body" + + pass "$setup creates a polkit-1 stack that includes system-auth" +done From 21e7975352da486e2ff30820a9abab4c020fa28e Mon Sep 17 00:00:00 2001 From: rattatui <88578725+Wheel-Smith@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:18:18 +0300 Subject: [PATCH 2/4] Harden the polkit migration: exact-layout match, retry on failure, and tests Address review feedback on the polkit faillock migration: - Match only the exact stack the setup commands wrote and replace just the bare pam_unix lines, preserving comments and the hardware-auth lines. An administrator-authored polkit-1 carrying any other directive is left untouched, rather than rebuilt from scratch. - Also repair the markerless post-removal layout: both remove commands strip their own marker lines but leave the bare pam_unix stack behind, so keying on a hardware-auth marker skipped those machines permanently. - Exit non-zero when the backup cannot be created or the rewrite cannot be verified (after restoring). omarchy-migrate runs under set -e and records a migration complete unconditionally after it returns, so a failed repair must fail loudly to be retried instead of silently marked done. - Add test/shell.d/security-polkit-migration-test.sh covering the fingerprint, FIDO2, combined, and markerless layouts, comment preservation, idempotence, an untouched administrator stack, and the refused-sudo and unverifiable-write failure paths. Co-Authored-By: Claude Opus 4.8 --- migrations/1788256455.sh | 91 +++++---- .../shell.d/security-polkit-migration-test.sh | 173 ++++++++++++++++++ 2 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 test/shell.d/security-polkit-migration-test.sh diff --git a/migrations/1788256455.sh b/migrations/1788256455.sh index 6c7463ec..e133604e 100644 --- a/migrations/1788256455.sh +++ b/migrations/1788256455.sh @@ -10,43 +10,70 @@ polkit=/etc/pam.d/polkit-1 # failures never counted toward the shared tally. The setup commands now defer to # system-auth; this repairs the files the old ones already wrote. # -# In scope only when the file exists, no package owns it (Omarchy wrote it), it -# does not already defer to system-auth, and it carries an Omarchy hardware-auth -# marker (the clamshell gate, pam_fprintd, or the FIDO2 authfile). That pins the -# repair to the exact stack the setup commands produced and leaves an -# administrator's own polkit-1 untouched. Idempotent by construction: once -# system-auth is included, this run and every other user's run no-op. -if [[ -f $polkit ]] && - ! pacman -Qo "$polkit" &>/dev/null && - ! grep -qE '^auth[[:space:]]+include[[:space:]]+system-auth' "$polkit" && - grep -qE 'omarchy-hw-laptop-closed|pam_fprintd\.so|authfile=/etc/fido2/fido2' "$polkit"; then +# Only the exact stack those commands produced is touched. Every non-blank, +# non-comment line must be one of the known hardware-auth auth lines (the +# clamshell gate, pam_fprintd, or the FIDO2 pam_u2f line) or a bare +# `X required pam_unix.so`; all four bare pam_unix lines must be present; and +# system-auth must not already be included. This matches the fingerprint, FIDO2, +# combined, and post-removal (markerless, both remove commands strip only their +# own marker lines) layouts, and refuses any administrator-authored stack that +# carries other directives. The repair replaces only the bare pam_unix lines, so +# comments and the hardware-auth lines are preserved verbatim. +is_omarchy_vulnerable_stack() { + local file=$1 line + local re_gate='^auth[[:space:]]+\[success=1 default=ignore\][[:space:]]+pam_exec\.so quiet /usr/bin/omarchy-hw-laptop-closed[[:space:]]*$' + local re_fprintd='^auth[[:space:]]+sufficient[[:space:]]+pam_fprintd\.so[[:space:]]*$' + local re_u2f='^auth[[:space:]]+sufficient[[:space:]]+pam_u2f\.so cue authfile=/etc/fido2/fido2[[:space:]]*$' + local re_bare='^(auth|account|password|session)[[:space:]]+required[[:space:]]+pam_unix\.so[[:space:]]*$' + local phase + + # Already fixed, or partially converted: leave it alone (also makes reruns no-op). + if grep -qE '(auth|account|password|session)[[:space:]]+include[[:space:]]+system-auth' "$file"; then + return 1 + fi + + # The vulnerable signature: all four phases delegated to a bare pam_unix. + for phase in auth account password session; do + grep -qE "^${phase}[[:space:]]+required[[:space:]]+pam_unix\.so[[:space:]]*\$" "$file" || return 1 + done + + # Every meaningful line must be one Omarchy itself wrote; anything else means + # an administrator has edited this file, so it is not ours to rewrite. + while IFS= read -r line || [[ -n $line ]]; do + [[ -z ${line//[[:space:]]/} ]] && continue + [[ $line == \#* ]] && continue + [[ $line =~ $re_gate || $line =~ $re_fprintd || $line =~ $re_u2f || $line =~ $re_bare ]] && continue + return 1 + done <"$file" + + return 0 +} + +if [[ -f $polkit ]] && is_omarchy_vulnerable_stack "$polkit"; then echo "Rewriting $polkit to defer to system-auth (restores pam_faillock lockout)..." - # Keep the configured hardware-auth auth lines verbatim (the clamshell gate and - # the pam_fprintd / pam_u2f 'sufficient' lines); only the bare pam_unix stack is - # replaced with the system-auth includes the vendor file and the sudo stack use. - hw_auth=$(grep -E '^auth' "$polkit" | grep -vE 'pam_unix\.so') - - rebuilt=$( - [[ -n $hw_auth ]] && printf '%s\n' "$hw_auth" - printf 'auth include system-auth\n' - printf 'account include system-auth\n' - printf 'password include system-auth\n' - printf 'session include system-auth\n' - ) - backup="$polkit.omarchy-bak.$(date +%s)" - if sudo cp -a "$polkit" "$backup"; then - printf '%s\n' "$rebuilt" | sudo tee "$polkit" >/dev/null + if ! sudo cp -a "$polkit" "$backup"; then + echo "Could not back up $polkit; leaving it unchanged so the migration retries." >&2 + exit 1 + fi - if grep -qE '^auth[[:space:]]+include[[:space:]]+system-auth' "$polkit"; then - echo "Restored polkit brute-force protection. Previous file saved at $backup." - else - echo "polkit repair could not be verified; restoring the original file." >&2 - sudo cp -a "$backup" "$polkit" - fi + # Replace only the bare pam_unix lines; keep comments, blank lines, and the + # hardware-auth (gate / pam_fprintd / pam_u2f) lines exactly as they are. + rebuilt=$(sed -E 's/^(auth|account|password|session)([[:space:]]+)required[[:space:]]+pam_unix\.so[[:space:]]*$/\1\2include system-auth/' "$polkit") + + if ! printf '%s\n' "$rebuilt" | sudo tee "$polkit" >/dev/null; then + echo "Could not write $polkit; restoring the original." >&2 + sudo cp -a "$backup" "$polkit" || true + exit 1 + fi + + if grep -qE '^auth[[:space:]]+include[[:space:]]+system-auth' "$polkit"; then + echo "Restored polkit brute-force protection. Previous file saved at $backup." else - echo "Administrator privileges are required to repair $polkit. Run omarchy-migrate again from a terminal." >&2 + echo "polkit repair could not be verified; restoring the original file." >&2 + sudo cp -a "$backup" "$polkit" + exit 1 fi fi diff --git a/test/shell.d/security-polkit-migration-test.sh b/test/shell.d/security-polkit-migration-test.sh new file mode 100644 index 00000000..b00e4681 --- /dev/null +++ b/test/shell.d/security-polkit-migration-test.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +# Exercises migrations/1788256455.sh, which repairs an Omarchy-created +# /etc/pam.d/polkit-1 that lists pam_unix directly (dropping pam_faillock) +# instead of including system-auth. The migration keeps its production path +# fixed; as in sshd-hardening-migration-test.sh, this test rewrites that one +# assignment in the input fed to bash and stubs sudo so nothing touches the +# host's /etc. + +test_dir=$(mktemp -d) +trap 'rm -rf "$test_dir"' EXIT + +migration="$ROOT/migrations/1788256455.sh" +stub_bin="$test_dir/bin" +mkdir -p "$stub_bin" + +# sudo stub: log, optionally refuse (SUDO_ALLOWED=0), and optionally make a +# `tee` write vanish (WRITE_BREAKS=1) so the verification/restore path is +# exercised. Otherwise run the real command so cp/tee act on the temp file. +cat >"$stub_bin/sudo" <<'STUB' +#!/bin/bash +printf 'sudo %s\n' "$*" >>"${CALL_LOG:?}" +if [[ ${SUDO_ALLOWED:-1} != 1 ]]; then + exit 1 +fi +if [[ ${WRITE_BREAKS:-0} == 1 && $1 == tee ]]; then + cat >/dev/null + exit 0 +fi +exec "$@" +STUB +chmod +x "$stub_bin"/* + +# Run the migration against a polkit-1 file seeded with $1; leaves the result in +# "$test_dir//polkit-1" and records the exit status in migrate_rc. +migrate_rc=0 +run_migration() { + local scenario=$1 content=$2 + local dir="$test_dir/$scenario" + local polkit="$dir/polkit-1" + mkdir -p "$dir" + printf '%s' "$content" >"$polkit" + : >"$test_dir/$scenario.calls" + + migrate_rc=0 + sed "s|^polkit=/etc/pam.d/polkit-1\$|polkit=$polkit|" "$migration" | + CALL_LOG="$test_dir/$scenario.calls" PATH="$stub_bin:$PATH" \ + SUDO_ALLOWED="${SUDO_ALLOWED:-1}" WRITE_BREAKS="${WRITE_BREAKS:-0}" \ + bash -euo pipefail >/dev/null 2>&1 || migrate_rc=$? +} + +result() { cat "$test_dir/$1/polkit-1"; } + +# The four layouts the old setup / remove commands leave behind. +fingerprint_stack='auth [success=1 default=ignore] pam_exec.so quiet /usr/bin/omarchy-hw-laptop-closed +auth sufficient pam_fprintd.so +auth required pam_unix.so + +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +' +fido2_stack='auth sufficient pam_u2f.so cue authfile=/etc/fido2/fido2 +auth required pam_unix.so + +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +' +both_stack='auth [success=1 default=ignore] pam_exec.so quiet /usr/bin/omarchy-hw-laptop-closed +auth sufficient pam_fprintd.so +auth sufficient pam_u2f.so cue authfile=/etc/fido2/fido2 +auth required pam_unix.so + +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +' +# What both remove commands leave: their own marker lines stripped, the bare +# pam_unix stack (and no marker) behind. +markerless_stack='auth required pam_unix.so + +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +' +fixed_stack='auth sufficient pam_fprintd.so +auth include system-auth +account include system-auth +password include system-auth +session include system-auth +' +# An administrator's own stack that happens to use pam_fprintd but carries an +# extra directive Omarchy never writes. +admin_stack='auth sufficient pam_fprintd.so +auth required pam_unix.so +auth optional pam_permit.so +account required pam_unix.so +password required pam_unix.so +session required pam_unix.so +' + +# Every phase of a repaired stack must defer to system-auth and no bare pam_unix +# may remain in the account/password/session block. +assert_repaired() { + local scenario=$1 phase + for phase in auth account password session; do + grep -qE "^${phase}[[:space:]]+include[[:space:]]+system-auth" <<<"$(result "$scenario")" || + fail "$scenario: $phase defers to system-auth" "$(result "$scenario")" + done + ! grep -qE '^(account|password|session)[[:space:]]+required[[:space:]]+pam_unix' <<<"$(result "$scenario")" || + fail "$scenario: no bare pam_unix remains" "$(result "$scenario")" +} + +run_migration fingerprint "$fingerprint_stack" +(( migrate_rc == 0 )) || fail "fingerprint stack migrates cleanly" +assert_repaired fingerprint +grep -qF 'pam_fprintd.so' <<<"$(result fingerprint)" || fail "fingerprint line is preserved" +grep -qF 'omarchy-hw-laptop-closed' <<<"$(result fingerprint)" || fail "clamshell gate is preserved" +pass "migration repairs the fingerprint stack and keeps its hardware-auth lines" + +run_migration fido2 "$fido2_stack" +(( migrate_rc == 0 )) || fail "fido2 stack migrates cleanly" +assert_repaired fido2 +grep -qF 'pam_u2f.so cue authfile=/etc/fido2/fido2' <<<"$(result fido2)" || fail "FIDO2 line is preserved" +pass "migration repairs the FIDO2 stack and keeps its hardware-auth line" + +run_migration both "$both_stack" +(( migrate_rc == 0 )) || fail "combined stack migrates cleanly" +assert_repaired both +grep -qF 'pam_fprintd.so' <<<"$(result both)" && grep -qF 'pam_u2f.so' <<<"$(result both)" || + fail "both hardware-auth lines are preserved" +pass "migration repairs a combined fingerprint+FIDO2 stack" + +run_migration markerless "$markerless_stack" +(( migrate_rc == 0 )) || fail "markerless stack migrates cleanly" +assert_repaired markerless +pass "migration repairs the markerless post-removal stack" + +run_migration comment "# managed by omarchy +$fingerprint_stack" +(( migrate_rc == 0 )) || fail "commented stack migrates cleanly" +assert_repaired comment +grep -qxF '# managed by omarchy' <<<"$(result comment)" || fail "comments are preserved through the rewrite" +pass "migration repairs a commented stack and preserves the comment" + +run_migration fixed "$fixed_stack" +[[ "$(result fixed)" == "$(printf '%s' "$fixed_stack")" ]] || fail "an already-fixed stack is left byte-for-byte unchanged" +! grep -q '^sudo ' "$test_dir/fixed.calls" || fail "an already-fixed stack triggers no privileged writes" +pass "migration is idempotent: an already-fixed stack is untouched" + +run_migration admin "$admin_stack" +[[ "$(result admin)" == "$(printf '%s' "$admin_stack")" ]] || fail "an administrator-authored stack is left unchanged" +! grep -q '^sudo ' "$test_dir/admin.calls" || fail "an administrator-authored stack triggers no privileged writes" +pass "migration refuses a stack carrying non-Omarchy directives" + +# Privilege failure is the retryable case: the migration must exit non-zero so +# omarchy-migrate does not record it complete, and must leave the file unchanged. +SUDO_ALLOWED=0 run_migration no-sudo "$fingerprint_stack" +(( migrate_rc != 0 )) || fail "the migration stays pending when privileges are unavailable" +[[ "$(result no-sudo)" == "$(printf '%s' "$fingerprint_stack")" ]] || fail "a failed repair leaves the original file intact" +pass "migration exits non-zero and preserves the file when sudo is refused" + +# A write that does not take effect must be caught by verification, restored, +# and reported as a failure rather than silently marked complete. +WRITE_BREAKS=1 run_migration verify-fail "$fingerprint_stack" +(( migrate_rc != 0 )) || fail "a failed verification exits non-zero" +[[ "$(result verify-fail)" == "$(printf '%s' "$fingerprint_stack")" ]] || fail "a failed verification restores the original file" +pass "migration exits non-zero and restores when the write cannot be verified" From 60c73865e4732eb8717fae8f960d2c786e8f387d Mon Sep 17 00:00:00 2001 From: Omarchybot <317366263+omarchybot@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:04:07 +0200 Subject: [PATCH 3/4] Rewrite polkit-1 in place with sed -i so an interrupted migration cannot truncate it `sudo tee` truncates the live PAM file before writing it, so a migration interrupted in between left /etc/pam.d/polkit-1 empty; the next run then failed the layout check, exited 0, and was marked complete with polkit authentication broken. sed -i writes a temporary file and renames it into place, as the setup commands already do, so the original stays intact until the rewrite is whole and a failed sed needs no restore. The test's broken-write stub now empties the file instead of discarding the write, so the restore assertion fails if the restore is removed. Co-Authored-By: Claude Opus 5.5 Co-Authored-By: Codex Medium --- migrations/1788256455.sh | 8 +++----- test/shell.d/security-polkit-migration-test.sh | 18 +++++++++--------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/migrations/1788256455.sh b/migrations/1788256455.sh index e133604e..d4a8d355 100644 --- a/migrations/1788256455.sh +++ b/migrations/1788256455.sh @@ -61,11 +61,9 @@ if [[ -f $polkit ]] && is_omarchy_vulnerable_stack "$polkit"; then # Replace only the bare pam_unix lines; keep comments, blank lines, and the # hardware-auth (gate / pam_fprintd / pam_u2f) lines exactly as they are. - rebuilt=$(sed -E 's/^(auth|account|password|session)([[:space:]]+)required[[:space:]]+pam_unix\.so[[:space:]]*$/\1\2include system-auth/' "$polkit") - - if ! printf '%s\n' "$rebuilt" | sudo tee "$polkit" >/dev/null; then - echo "Could not write $polkit; restoring the original." >&2 - sudo cp -a "$backup" "$polkit" || true + # sed -i renames a complete file into place, so an interrupted run never leaves polkit-1 truncated. + if ! sudo sed -i -E 's/^(auth|account|password|session)([[:space:]]+)required[[:space:]]+pam_unix\.so[[:space:]]*$/\1\2include system-auth/' "$polkit"; then + echo "Could not rewrite $polkit; leaving it unchanged so the migration retries." >&2 exit 1 fi diff --git a/test/shell.d/security-polkit-migration-test.sh b/test/shell.d/security-polkit-migration-test.sh index b00e4681..42481e7d 100644 --- a/test/shell.d/security-polkit-migration-test.sh +++ b/test/shell.d/security-polkit-migration-test.sh @@ -18,20 +18,20 @@ migration="$ROOT/migrations/1788256455.sh" stub_bin="$test_dir/bin" mkdir -p "$stub_bin" -# sudo stub: log, optionally refuse (SUDO_ALLOWED=0), and optionally make a -# `tee` write vanish (WRITE_BREAKS=1) so the verification/restore path is -# exercised. Otherwise run the real command so cp/tee act on the temp file. +# sudo stub: log, optionally refuse (SUDO_ALLOWED=0), and optionally make the +# `sed` rewrite empty the file and report success (WRITE_BREAKS=1) so the +# verification/restore path is exercised. Otherwise run the real command so +# cp/sed act on the temp file. cat >"$stub_bin/sudo" <<'STUB' #!/bin/bash printf 'sudo %s\n' "$*" >>"${CALL_LOG:?}" -if [[ ${SUDO_ALLOWED:-1} != 1 ]]; then +if [[ ${SUDO_ALLOWED:-1} != "1" ]]; then exit 1 +elif [[ ${WRITE_BREAKS:-0} == "1" && $1 == "sed" ]]; then + : >"${!#}" +else + exec "$@" fi -if [[ ${WRITE_BREAKS:-0} == 1 && $1 == tee ]]; then - cat >/dev/null - exit 0 -fi -exec "$@" STUB chmod +x "$stub_bin"/* From d95c68f9fbd1d05bad37dfaba5cd62bfdf72833d Mon Sep 17 00:00:00 2001 From: Omarchybot <317366263+omarchybot@users.noreply.github.com> Date: Fri, 2 Oct 2026 03:12:21 +0200 Subject: [PATCH 4/4] Match only active include lines when deciding polkit-1 is already fixed The layout check skips comments, but the already-fixed check matched `include system-auth` anywhere on a line, so a comment such as `# auth include system-auth` made the migration skip a stack that still listed bare pam_unix, and it was marked complete with the lockout still missing. Co-Authored-By: Claude Opus 5.5 --- migrations/1788256455.sh | 2 +- test/shell.d/security-polkit-migration-test.sh | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/migrations/1788256455.sh b/migrations/1788256455.sh index d4a8d355..38022bc3 100644 --- a/migrations/1788256455.sh +++ b/migrations/1788256455.sh @@ -29,7 +29,7 @@ is_omarchy_vulnerable_stack() { local phase # Already fixed, or partially converted: leave it alone (also makes reruns no-op). - if grep -qE '(auth|account|password|session)[[:space:]]+include[[:space:]]+system-auth' "$file"; then + if grep -qE '^(auth|account|password|session)[[:space:]]+include[[:space:]]+system-auth' "$file"; then return 1 fi diff --git a/test/shell.d/security-polkit-migration-test.sh b/test/shell.d/security-polkit-migration-test.sh index 42481e7d..d9b14fa1 100644 --- a/test/shell.d/security-polkit-migration-test.sh +++ b/test/shell.d/security-polkit-migration-test.sh @@ -148,6 +148,12 @@ assert_repaired comment grep -qxF '# managed by omarchy' <<<"$(result comment)" || fail "comments are preserved through the rewrite" pass "migration repairs a commented stack and preserves the comment" +run_migration commented-include "# auth include system-auth +$fingerprint_stack" +(( migrate_rc == 0 )) || fail "a stack with a commented-out include migrates cleanly" +assert_repaired commented-include +pass "migration repairs a stack whose only include is commented out" + run_migration fixed "$fixed_stack" [[ "$(result fixed)" == "$(printf '%s' "$fixed_stack")" ]] || fail "an already-fixed stack is left byte-for-byte unchanged" ! grep -q '^sudo ' "$test_dir/fixed.calls" || fail "an already-fixed stack triggers no privileged writes"