@@ -74,6 +74,7 @@ list-devices)
7474 printf '17: %s\n' "$(<"$TEST_ALLOWED_RULE")"
7575 fi
7676 elif [[ ${2:-} == "--blocked" ]]; then
77+ [[ ${BLOCKED_QUERY_FAIL:-0} == 0 ]] || exit 1
7778 [[ ! -f $TEST_ALLOWED_RULE ]] || exit 0
7879 if [[ -n ${BLOCKED_DEVICES_FILE:-} ]]; then cat "$BLOCKED_DEVICES_FILE"; exit 0; fi
7980 if [[ ${BLOCKED_DEVICE_PRESENT:-1} == 1 ]]; then
@@ -84,6 +85,17 @@ list-devices)
8485 printf '17: %s\n' "$rule"
8586 fi
8687 else
88+ [[ ${BLOCKED_QUERY_FAIL:-0} == 0 ]] || exit 1
89+ if [[ -f $TEST_ALLOWED_RULE ]]; then
90+ [[ ${APPROVAL_QUERY_FAIL:-0} == 0 ]] || exit 1
91+ printf '17: %s\n' "$(<"$TEST_ALLOWED_RULE")"
92+ elif [[ -n ${BLOCKED_DEVICE_RULE:-} && ${BLOCKED_DEVICE_PRESENT:-1} == 1 ]]; then
93+ rule="$BLOCKED_DEVICE_RULE"
94+ if [[ -n ${BLOCKED_DEVICE_RULE_FILE:-} && -f $BLOCKED_DEVICE_RULE_FILE ]]; then
95+ rule=$(<"$BLOCKED_DEVICE_RULE_FILE")
96+ fi
97+ printf '17: %s\n' "$rule"
98+ fi
8799 echo '4: allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"'
88100 echo '5: allow id 0627:0001 name "QEMU USB Tablet" hash "tablet"'
89101 fi
@@ -142,6 +154,12 @@ cat >"$stub_bin/gum" <<'STUB'
142154case "$1" in
143155style) exit 0 ;;
144156choose)
157+ echo "gum choose" >>"$CALLS"
158+ [[ ${GUM_CANCEL:-0} == 0 ]] || exit 130
159+ if [[ -n ${GUM_REPLACE_REQUEST:-} ]]; then
160+ jq '.generation = "replacement-generation"' "$GUM_REPLACE_REQUEST" >"$GUM_REPLACE_REQUEST.new"
161+ mv "$GUM_REPLACE_REQUEST.new" "$GUM_REPLACE_REQUEST"
162+ fi
145163 if [[ -n ${GUM_REASSIGN_RULE:-} ]]; then
146164 printf '%s\n' "$GUM_REASSIGN_RULE" >"$BLOCKED_DEVICE_RULE_FILE"
147165 fi
@@ -648,7 +666,7 @@ pass "USB review binds approval to the device snapshot"
648666
649667for choice in ' Allow once' ' Always allow this device' ; do
650668 for failure in APPROVAL_SILENT_FAIL APPROVAL_EXIT_FAIL APPROVAL_QUERY_FAIL APPROVAL_REPLACEMENT_RULE; do
651- rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
669+ rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE " " $request "
652670 USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
653671 value=1
654672 [[ $failure != " APPROVAL_REPLACEMENT_RULE" ]] || value=' allow id 9999:9999 hash "replacement"'
@@ -660,7 +678,8 @@ for choice in 'Allow once' 'Always allow this device'; do
660678 done
661679done
662680for failure in APPROVAL_NO_POLICY APPROVAL_POLICY_QUERY_FAIL; do
663- rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
681+ rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE " " $request "
682+ USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
664683 if env " $failure =1" GUM_CHOICE=' Always allow this device' " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /review-no-policy" 2>&1 ; then
665684 fail " permanent approval must reject $failure "
666685 fi
@@ -672,6 +691,88 @@ GUM_CHOICE='Always allow this device' "$ROOT/bin/omarchy-usb-authorization-revie
672691rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
673692pass " USB approvals verify device and permanent policy before consuming requests"
674693
694+ # Exercise retries without resetting the device state left by the first attempt.
695+ for choice in ' Allow once' ' Always allow this device' ; do
696+ USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
697+ failure=APPROVAL_QUERY_FAIL
698+ [[ $choice != ' Always allow this device' ]] || failure=APPROVAL_POLICY_QUERY_FAIL
699+ if env " $failure =1" GUM_CHOICE=" $choice " " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /retry-first" 2>&1 ; then
700+ fail " transient verification failure must retain the approval"
701+ fi
702+ [[ -s $TEST_ALLOWED_RULE && -f $request ]] || fail " fixture must retain an already-allowed device"
703+ [[ $( jq -r .approval " $request " ) == " $choice " ]] || fail " retry must remember the selected approval"
704+ [[ $( stat -c %a " $request " ) == 600 ]] || fail " saved approval intent stays private"
705+ allow_count=$( grep -c ' ^usbguard <allow-device>' " $calls " )
706+ prompt_count=$( grep -c ' ^gum choose' " $calls " )
707+ GUM_CANCEL=1 " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /retry-second"
708+ [[ ! -e $request ]] || fail " verified approval retry consumes the request"
709+ [[ $( grep -c ' ^usbguard <allow-device>' " $calls " ) == " $allow_count " ]] || fail " already-allowed retry must not reapply authorization"
710+ [[ $( grep -c ' ^gum choose' " $calls " ) == " $prompt_count " ]] || fail " retry must resume the original approval without another choice"
711+ if [[ $choice == ' Always allow this device' ]]; then
712+ grep -Fq ' added to the trusted policy' " $scratch /retry-second" || fail " permanent retry must finish permanent verification"
713+ else
714+ grep -Fq ' allowed until it is disconnected' " $scratch /retry-second" || fail " once retry must finish temporary verification"
715+ fi
716+ rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
717+ done
718+ pass " USB approval retries resume successful temporary and permanent authorization"
719+
720+ USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
721+ if APPROVAL_NO_POLICY=1 GUM_CHOICE=' Always allow this device' " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /missing-policy" 2>&1 ; then
722+ fail " permanent approval requires saved policy"
723+ fi
724+ allow_count=$( grep -c ' ^usbguard <allow-device>' " $calls " )
725+ if GUM_CHOICE=' Allow once' " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /missing-policy-retry" 2>&1 ; then
726+ fail " retry cannot downgrade permanent approval when policy is missing"
727+ fi
728+ [[ -f $request && $( jq -r .approval " $request " ) == ' Always allow this device' ]] || fail " missing policy retains original permanent intent"
729+ [[ $( grep -c ' ^usbguard <allow-device>' " $calls " ) == " $allow_count " ]] || fail " retry must not repeat authorization on an allowed device"
730+ for failure in BLOCKED_QUERY_FAIL APPROVAL_QUERY_FAIL; do
731+ if env " $failure =1" " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /query-retry" 2>&1 ; then
732+ fail " retry must fail when inventory cannot be read"
733+ fi
734+ [[ -f $request ]] || fail " inventory query failures must preserve pending approvals"
735+ done
736+ printf ' %s\n' " allow ${replacement_rule# block } " > " $TEST_ALLOWED_RULE "
737+ if " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /replaced-retry" 2>&1 ; then
738+ fail " retry must reject a replacement allowed device"
739+ fi
740+ [[ ! -e $request ]] || fail " a proven replacement consumes the stale request"
741+ [[ $( grep -c ' ^usbguard <allow-device>' " $calls " ) == " $allow_count " ]] || fail " replacement retry must not authorize anything"
742+ rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
743+ pass " USB approval retries preserve permanent intent and validate allowed identity"
744+
745+ USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
746+ if BLOCKED_QUERY_FAIL=1 " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /fresh-query-failure" 2>&1 ; then
747+ fail " fresh review must fail on inventory query failure"
748+ fi
749+ [[ -f $request ]] || fail " fresh inventory failure must not delete the request"
750+ jq ' .approval = "invalid"' " $request " > " $scratch /invalid-approval"
751+ mv " $scratch /invalid-approval" " $request "
752+ if " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /invalid-intent" 2>&1 ; then
753+ fail " invalid saved intent must not authorize"
754+ fi
755+ jq ' del(.approval)' " $request " > " $scratch /fresh-request"
756+ mv " $scratch /fresh-request" " $request "
757+ if GUM_CANCEL=1 " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /cancel" 2>&1 ; then
758+ fail " canceled review must not authorize"
759+ fi
760+ jq -e ' .approval == null' " $request " > /dev/null || fail " canceling must not store an approval"
761+ if GUM_REPLACE_REQUEST=" $request " GUM_CHOICE=' Always allow this device' " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /replaced-request" 2>&1 ; then
762+ fail " open dialog must not approve a replaced request"
763+ fi
764+ [[ $( jq -r .generation " $request " ) == ' replacement-generation' ]] || fail " stale dialog must leave replacement request intact"
765+ [[ $( grep -c ' ^usbguard <allow-device>' " $calls " ) == " $allow_count " ]] || fail " canceled or superseded requests cannot authorize"
766+ rm -f " $request "
767+ USBGUARD_IPC_SIGNAL=IPC.Connected " $ROOT /bin/omarchy-usb-authorization-event"
768+ if APPROVAL_EXIT_FAIL=1 GUM_CHOICE=' Always allow this device' " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /blocked-retry-first" 2>&1 ; then
769+ fail " failed apply must retain a blocked request"
770+ fi
771+ GUM_CANCEL=1 " $ROOT /bin/omarchy-usb-authorization-review" " $token " > " $scratch /blocked-retry-second"
772+ [[ -s $TEST_SAVED_RULE && ! -e $request ]] || fail " blocked retry must apply its saved permanent choice"
773+ rm -f " $TEST_ALLOWED_RULE " " $TEST_SAVED_RULE "
774+ pass " USB review preserves transient failures and rejects canceled or replaced dialogs"
775+
675776notification_count=$( grep -c ' ^notification' " $calls " )
676777printf ' 17: %s\n18: %s\n' " $malicious_rule " ' block id 1234:5678 name "Second blocked device" hash "second"' > " $scratch /two-blocked"
677778NOTIFICATION_READY_FILE=" $scratch /notification-ready" \
0 commit comments