From 9e20c2a185fdb2e5abb88e507ce443314e2041c8 Mon Sep 17 00:00:00 2001 From: acrogenesis Date: Tue, 15 Sep 2026 14:06:32 -0600 Subject: [PATCH] Retry USB approval delivery when notifications become available --- bin/omarchy-usb-authorization-event | 76 +++++++++++++++----------- test/shell.d/usb-authorization-test.sh | 43 +++++++++++---- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/bin/omarchy-usb-authorization-event b/bin/omarchy-usb-authorization-event index a26fd6c6..8a93a1c0 100755 --- a/bin/omarchy-usb-authorization-event +++ b/bin/omarchy-usb-authorization-event @@ -16,7 +16,7 @@ IPC.Connected) USBGUARD_DEVICE_ID="${BASH_REMATCH[1]}" \ USBGUARD_DEVICE_TARGET_NEW=block \ USBGUARD_DEVICE_RULE="${BASH_REMATCH[2]}" \ - "$OMARCHY_PATH/bin/omarchy-usb-authorization-event" + "$OMARCHY_PATH/bin/omarchy-usb-authorization-event" || true fi done <<<"$blocked" exit 0 @@ -44,40 +44,52 @@ request="$requests/$token.json" # watch executes handlers concurrently. A reconnect scan and a policy event # can refer to the same device; publish only one pending request. exec 9>"$requests/.lock" -flock 9 -[[ ! -e $request ]] || exit 0 +# Keep retrying without needing another USB event. Handlers belong to the +# watcher service and are stopped with the graphical session. +while true; do + flock 9 + [[ ! -e $request ]] || exit 0 -# Discard events overtaken by another policy decision or device replacement. -blocked=$(usbguard list-devices --blocked) || exit 1 -current=0 -while IFS= read -r line; do - if [[ $line == "$USBGUARD_DEVICE_ID: $USBGUARD_DEVICE_RULE" ]]; then - current=1 - break + # Discard events overtaken by another policy decision or device replacement. + blocked=$(usbguard list-devices --blocked) || exit 1 + current=0 + while IFS= read -r line; do + if [[ $line == "$USBGUARD_DEVICE_ID: $USBGUARD_DEVICE_RULE" ]]; then + current=1 + break + fi + done <<<"$blocked" + (( current )) || exit 0 + + # Login and shell restarts can leave D-Bus alive before notifications are ready. + # Bound each wait so disappeared devices are rechecked between attempts. + if ! omarchy-notification-wait 1; then + flock -u 9 + sleep 1 + continue fi -done <<<"$blocked" -(( current )) || exit 0 -temporary=$(mktemp "$requests/.request.XXXXXXXXXX") -trap 'rm -f "$temporary"' EXIT + temporary=$(mktemp "$requests/.request.XXXXXXXXXX") + trap 'rm -f "$temporary"' EXIT -jq -n \ - --arg id "$USBGUARD_DEVICE_ID" \ - --arg rule "$USBGUARD_DEVICE_RULE" \ - '{id: $id, rule: $rule}' >"$temporary" -chmod 600 "$temporary" -mv -f "$temporary" "$request" + jq -n \ + --arg id "$USBGUARD_DEVICE_ID" \ + --arg rule "$USBGUARD_DEVICE_RULE" \ + '{id: $id, rule: $rule}' >"$temporary" + chmod 600 "$temporary" + mv -f "$temporary" "$request" -if ! omarchy-notification-send \ - --app-name omarchy-action \ - --urgency critical \ - --glyph 󰖀 \ - "USB accessory blocked" \ - "Click to review it. Keep it blocked unless you recognize the device." \ - --exec omarchy-launch-floating-terminal-with-presentation \ - omarchy-usb-authorization-review "$token"; then - # Only a delivered notification may suppress subsequent attempts. Keep the - # lock until failure cleanup so another event cannot skip an unsent request. + if omarchy-notification-send \ + --app-name omarchy-action \ + --urgency critical \ + --glyph 󰖀 \ + "USB accessory blocked" \ + "Click to review it. Keep it blocked unless you recognize the device." \ + --exec omarchy-launch-floating-terminal-with-presentation \ + omarchy-usb-authorization-review "$token"; then + exit 0 + fi rm -f "$request" - exit 1 -fi + flock -u 9 + sleep 1 +done diff --git a/test/shell.d/usb-authorization-test.sh b/test/shell.d/usb-authorization-test.sh index fd05258d..6017e18d 100755 --- a/test/shell.d/usb-authorization-test.sh +++ b/test/shell.d/usb-authorization-test.sh @@ -67,6 +67,7 @@ generate-policy) ;; list-devices) if [[ ${2:-} == "--blocked" ]]; then + if [[ -n ${BLOCKED_DEVICES_FILE:-} ]]; then cat "$BLOCKED_DEVICES_FILE"; exit 0; fi if [[ ${BLOCKED_DEVICE_PRESENT:-1} == 1 ]]; then rule="${BLOCKED_DEVICE_RULE:?}" if [[ -n ${BLOCKED_DEVICE_RULE_FILE:-} && -f $BLOCKED_DEVICE_RULE_FILE ]]; then @@ -126,7 +127,17 @@ cat >"$stub_bin/omarchy-notification-send" <<'STUB' printf 'notification' >>"$CALLS" printf ' <%s>' "$@" >>"$CALLS" printf '\n' >>"$CALLS" -exit "${NOTIFICATION_FAIL:-0}" +if [[ -n ${NOTIFICATION_FAILURE_MARKER:-} && ! -e $NOTIFICATION_FAILURE_MARKER ]]; then + touch "$NOTIFICATION_FAILURE_MARKER" + exit 1 +fi +STUB +cat >"$stub_bin/omarchy-notification-wait" <<'STUB' +#!/bin/bash +if [[ -n ${NOTIFICATION_READY_FILE:-} ]]; then + touch "$NOTIFICATION_READY_FILE.waited" + [[ -e $NOTIFICATION_READY_FILE ]] +fi STUB chmod +x "$stub_bin"/* @@ -515,16 +526,26 @@ fi pass "USB review binds approval to the device snapshot" notification_count=$(grep -c '^notification' "$calls") -if NOTIFICATION_FAIL=1 USBGUARD_IPC_SIGNAL=IPC.Connected \ - "$ROOT/bin/omarchy-usb-authorization-event"; then - fail "a failed notification must report failure" -fi -[[ ! -e $request ]] || fail "a failed notification must not suppress retries" -USBGUARD_IPC_SIGNAL=IPC.Connected "$ROOT/bin/omarchy-usb-authorization-event" -[[ -f $request ]] || fail "reconnection retries a failed notification" -[[ $(grep -c '^notification' "$calls") == $((notification_count + 2)) ]] || - fail "reconnection must attempt delivery after an earlier send failure" -pass "failed USB notifications can be retried on reconnection" +printf '17: %s\n18: %s\n' "$malicious_rule" 'block id 1234:5678 name "Second blocked device" hash "second"' >"$scratch/two-blocked" +NOTIFICATION_READY_FILE="$scratch/notification-ready" \ + NOTIFICATION_FAILURE_MARKER="$scratch/first-send-failed" \ + BLOCKED_DEVICES_FILE="$scratch/two-blocked" \ + USBGUARD_IPC_SIGNAL=IPC.Connected \ + timeout 10 "$ROOT/bin/omarchy-usb-authorization-event" & +scan_pid=$! +for ((attempt=0; attempt<50; attempt++)); do + [[ -e $scratch/notification-ready.waited ]] && break + sleep 0.02 +done +[[ -e $scratch/notification-ready.waited ]] || fail "startup waits for notification readiness" +[[ $(grep -c '^notification' "$calls") == "$notification_count" ]] || + fail "startup does not send before the notification server is ready" +touch "$scratch/notification-ready" +wait "$scan_pid" || fail "startup scan must retry and finish without another USB event" +[[ $(grep -c '^notification' "$calls") == $((notification_count + 3)) ]] || + fail "two blocked devices must be notified despite a failed first send" +[[ -f $request ]] || fail "successful retry leaves a reviewable request" +pass "USB startup waits for notifications and automatically retries delivery for all devices" sysfs="$scratch/sys/bus/usb/devices" mkdir -p "$sysfs/usb1" "$sysfs/1-2"