Require approval for new USB devices by default

Co-authored-by: Outfoxxed <outfoxxed@outfoxxed.me>
This commit is contained in:
acrogenesisandOutfoxxed committed 2026-09-14 22:13:36 -06:00
1 parent 86a2e5830e
commit 44f68f4329
19 files changed
+1382 -2

No files matched your search

+6
View File
@@ -747,6 +747,12 @@ create_user() {
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
chmod 440 /etc/sudoers.d/00-omarchy-wheel
if omarchy-pkg-present usbguard; then
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
usb_authorization_add_user "$username"
systemctl try-restart usbguard.service
fi
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
[[ -d $dir || -L $dir ]] || continue
+86
View File
@@ -0,0 +1,86 @@
#!/bin/bash
# omarchy:summary=Stop asking before new USB devices can access the system
# omarchy:args=[--boot-only] [--yes]
# omarchy:requires-sudo=true
set -euo pipefail
assume_yes=0
boot_only=0
while (( $# > 0 )); do
case "$1" in
--yes) assume_yes=1 ;;
--boot-only) boot_only=1 ;;
-h | --help)
echo "Usage: omarchy-remove-security-usb-authorization [--boot-only] [--yes]"
echo
echo "Disables Omarchy's USB approval prompts and restores the kernel's"
echo "normal default-allow behavior."
echo "Use --boot-only to keep USBGuard enabled after the system starts."
exit 0
;;
*)
echo "omarchy-remove-security-usb-authorization: unknown option '$1'. Try --help." >&2
exit 2
;;
esac
shift
done
if (( boot_only )); then
if (( ! assume_yes )); then
gum style --foreground 212 --bold "Remove USB authorization from boot"
echo
echo "USBGuard will continue blocking unknown devices after its service starts."
echo "Devices can reach kernel drivers earlier during boot."
echo
gum confirm "Disable USB authorization from kernel boot?" || exit 130
fi
sudo omarchy-usb-authorization-boot disable
echo
echo "Boot-time USB authorization is disabled and takes effect after reboot."
exit 0
fi
state_dir="$HOME/.local/state/omarchy/usb-authorization"
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
if (( ! assume_yes )); then
gum style --foreground 212 --bold "Remove USB device authorization"
echo
echo "New USB devices will be allowed to bind to kernel drivers without approval."
echo
gum confirm "Disable USB device authorization?" || exit 130
fi
# Rebuild a permissive boot image before stopping USBGuard. If rebuilding
# fails, the command stops here and leaves the complete policy enabled.
sudo omarchy-usb-authorization-boot disable
systemctl --user disable --now omarchy-usb-authorization.service 2>/dev/null || true
rm -f "$unit"
systemctl --user daemon-reload
if omarchy-pkg-present usbguard; then
sudo usbguard remove-user "$USER" 2>/dev/null || true
fi
if omarchy-pkg-present usbguard; then
sudo systemctl disable --now usbguard.service 2>/dev/null || true
sudo omarchy-usb-authorization-restore-default
fi
if [[ -d $state_dir/requests ]]; then
find "$state_dir/requests" -mindepth 1 -maxdepth 1 -type f -delete
rmdir "$state_dir/requests" 2>/dev/null || true
fi
rm -f "$state_dir/package-installed-by-omarchy" \
"$state_dir/policy-generated-by-omarchy" \
"$state_dir/service-enabled-by-omarchy"
rmdir "$state_dir" 2>/dev/null || true
echo "USB device authorization is disabled."
+112
View File
@@ -0,0 +1,112 @@
#!/bin/bash
# omarchy:summary=Ask before new USB devices can access the system
# omarchy:args=[--boot] [--yes]
# omarchy:requires-sudo=true
set -euo pipefail
assume_yes=0
from_boot=0
while (( $# > 0 )); do
case "$1" in
--yes) assume_yes=1 ;;
--boot) from_boot=1 ;;
-h | --help)
echo "Usage: omarchy-setup-security-usb-authorization [--boot] [--yes]"
echo
echo "Trusts USB devices that are connected now, then blocks new devices"
echo "until you approve them from an Omarchy notification."
echo
echo "--boot also blocks USB devices from the start of kernel boot. Connected"
echo "devices are added to USBGuard's policy, but remain unavailable before"
echo "USBGuard starts, including at an encrypted-disk password prompt."
exit 0
;;
*)
echo "omarchy-setup-security-usb-authorization: unknown option '$1'. Try --help." >&2
exit 2
;;
esac
shift
done
state_dir="$HOME/.local/state/omarchy/usb-authorization"
unit_source="$OMARCHY_PATH/default/systemd/user/omarchy-usb-authorization.service"
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
rules_file=/etc/usbguard/rules.conf
daemon_config=/etc/usbguard/usbguard-daemon.conf
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
if (( ! assume_yes )); then
if (( from_boot )); then
gum style --foreground 212 --bold "USB authorization from boot"
else
gum style --foreground 212 --bold "USB device authorization"
fi
echo
echo "Devices connected now will be trusted. Disconnect anything you do not trust."
echo "New USB devices will remain blocked until you approve them."
if (( from_boot )); then
echo
echo "Boot-time protection blocks all USB devices until USBGuard starts."
echo "A USB keyboard will not work at an encrypted-disk password prompt, even"
echo "when it is connected now. Continue only if unlock and recovery do not"
echo "depend on USB input."
fi
echo
if (( from_boot )); then
gum confirm "Enable USB authorization from kernel boot?" || exit 130
else
gum confirm "Enable USB device authorization?" || exit 130
fi
fi
omarchy-pkg-add usbguard
usb_authorization_require_secure_settings "$daemon_config"
mkdir -p "$state_dir" "${unit%/*}"
chmod 700 "$state_dir"
if ! usb_authorization_rules_present "$rules_file"; then
policy=$(mktemp "${TMPDIR:-/tmp}/omarchy-usb-policy.XXXXXXXXXX")
trap 'rm -f "$policy"' EXIT
chmod 600 "$policy"
usb_authorization_generate_policy "$policy"
sudo install -Dm600 -o root -g root "$policy" "$rules_file"
touch "$state_dir/policy-generated-by-omarchy"
fi
# This user can inspect USBGuard events and approve the device named by an
# event. Policy and daemon parameters remain unavailable through the IPC ACL.
sudo usbguard add-user "$USER" \
--devices=list,listen,modify \
--policy=list \
--exceptions=listen
sudo systemctl enable usbguard.service
# add-user ACL changes are loaded only when the daemon starts, so restart an
# existing service as well as starting a fresh one.
sudo systemctl restart usbguard.service
ln -sfn "$unit_source" "$unit"
systemctl --user daemon-reload
systemctl --user enable --now omarchy-usb-authorization.service
if (( from_boot )); then
# Take the trust snapshot only after USBGuard is enforcing the policy. Every
# ID is resolved by the daemon, and no device-controlled descriptor text is
# passed to a privileged command.
usb_authorization_trust_present_devices
sudo omarchy-usb-authorization-boot enable
fi
echo
echo "USB device authorization is enabled."
echo "Devices connected during setup remain trusted; new devices require approval."
if (( from_boot )); then
echo "Boot-time authorization is enabled and takes effect after reboot."
fi
+432
View File
@@ -0,0 +1,432 @@
#!/bin/bash
# omarchy:summary=Manage USB authorization from the start of kernel boot
# omarchy:args=<enable|disable>
# omarchy:requires-sudo=true
# omarchy:hidden=true
set -euo pipefail
drop_in=/etc/limine-entry-tool.d/usb-authorization.conf
setting='KERNEL_CMDLINE[default]+=" usbcore.authorized_default=0"'
limine_conf=/boot/limine.conf
limine_defaults=/etc/default/limine
boot_path=/boot
kernel_parameter=usbcore.authorized_default=0
snapshot_begin='# Omarchy USB authorization begin'
snapshot_setting='SNAPSHOT_KERNEL_PARAMETERS+=usbcore.authorized_default=0'
snapshot_end='# Omarchy USB authorization end'
usb_authorization_cmdline_has_parameter() {
local cmdline="$1" parameter="$2" word
for word in $cmdline; do
[[ $word == "$parameter" ]] && return 0
done
return 1
}
usb_authorization_secure_boot_enabled() {
local value variable
for variable in /sys/firmware/efi/efivars/SecureBoot-*; do
[[ -r $variable ]] || continue
value=$(od -An -t u1 -j 4 -N 1 "$variable")
value=${value//[[:space:]]/}
[[ $value == "1" ]] && return 0
done
return 1
}
usb_authorization_verify_boot_entries() {
local expected="$1" config="$2" esp="$3"
local machine_id="${4:-$(</etc/machine-id)}"
local secure_boot="${5:-auto}"
local actual_hash embedded_cmdline expected_hash image mode path paths section
if [[ $secure_boot == "auto" ]]; then
if usb_authorization_secure_boot_enabled; then
secure_boot=enabled
else
secure_boot=disabled
fi
fi
if [[ ! -r $config ]]; then
echo "Cannot verify the generated Limine boot entries in $config." >&2
return 1
fi
paths=$(mktemp /tmp/omarchy-usb-boot-paths.XXXXXXXXXX)
section=$(mktemp /tmp/omarchy-usb-boot-cmdline.XXXXXXXXXX)
if ! awk -v expected="$expected" -v machine_id="$machine_id" -v parameter="$kernel_parameter" '
function contains_parameter(value, count, fields, field_index) {
count = split(value, fields, /[[:space:]]+/)
for (field_index = 1; field_index <= count; field_index++) {
if (fields[field_index] == parameter) return 1
}
return 0
}
function finish_entry(found) {
if (!target_os || !is_kernel) return
entries++
if (has_cmdline) {
found = contains_parameter(cmdline)
if ((expected == "enabled" && !found) || (expected == "disabled" && found)) bad = 1
}
if (path == "") {
bad = 1
} else {
print (has_cmdline ? "external" : "embedded") "\t" path
}
}
/^\/[^/]/ {
finish_entry()
target_os = 0
is_kernel = 0
has_cmdline = 0
cmdline = ""
path = ""
next
}
/^[[:space:]]*comment:[[:space:]]*machine-id=/ {
target_os = index($0, "machine-id=" machine_id) > 0
next
}
/^[[:space:]]*\/\// {
finish_entry()
is_kernel = 0
has_cmdline = 0
cmdline = ""
path = ""
next
}
/^[[:space:]]*comment:[[:space:]]*kernel-id=/ {
is_kernel = target_os
next
}
is_kernel && /^[[:space:]]*path:[[:space:]]*/ {
path = $0
sub(/^[[:space:]]*path:[[:space:]]*/, "", path)
next
}
is_kernel && /^[[:space:]]*cmdline:[[:space:]]*/ {
has_cmdline = 1
cmdline = $0
sub(/^[[:space:]]*cmdline:[[:space:]]*/, "", cmdline)
next
}
END {
finish_entry()
if (entries == 0) bad = 1
exit bad
}
' "$config" >"$paths"; then
rm -f "$paths" "$section"
echo "The generated Limine kernel entries do not all have USB authorization $expected." >&2
return 1
fi
while IFS=$'\t' read -r mode path; do
if [[ $path != 'boot():/'* ]]; then
rm -f "$paths" "$section"
echo "Cannot verify the kernel image referenced by Limine: $path" >&2
return 1
fi
image="$esp/${path#'boot():/'}"
image=${image%%#*}
if [[ ! -r $image ]]; then
rm -f "$paths" "$section"
echo "Cannot verify the kernel image referenced by Limine: $image" >&2
return 1
fi
if [[ $path == *'#'* ]]; then
expected_hash=${path##*#}
actual_hash=$(b2sum "$image")
actual_hash=${actual_hash%% *}
if [[ $actual_hash != "$expected_hash" ]]; then
rm -f "$paths" "$section"
echo "The verification hash for $image does not match the generated Limine entry." >&2
return 1
fi
fi
: >"$section"
if ! objcopy -O binary --only-section=.cmdline "$image" "$section" 2>/dev/null; then
[[ $mode == "external" ]] && continue
rm -f "$paths" "$section"
echo "Cannot inspect the embedded kernel command line in $image." >&2
return 1
fi
embedded_cmdline=$(tr '\0' '\n' <"$section")
# Without Secure Boot, Limine's external command line takes precedence.
# Under Secure Boot, systemd-stub uses a nonempty embedded .cmdline.
if [[ $mode == "external" && $secure_boot == "disabled" ]]; then
continue
fi
# A signed UKI can ignore Limine's external cmdline and use its embedded
# .cmdline instead. If one exists, both representations must agree.
if [[ -z $embedded_cmdline ]]; then
if [[ $mode == "external" || $expected == "disabled" ]]; then
continue
fi
rm -f "$paths" "$section"
echo "The generated kernel image $image has no verifiable kernel command line." >&2
return 1
fi
if [[ $expected == "enabled" ]]; then
if ! usb_authorization_cmdline_has_parameter "$embedded_cmdline" "$kernel_parameter"; then
rm -f "$paths" "$section"
echo "The generated kernel image $image does not enable boot-time USB authorization." >&2
return 1
fi
elif usb_authorization_cmdline_has_parameter "$embedded_cmdline" "$kernel_parameter"; then
rm -f "$paths" "$section"
echo "The generated kernel image $image still enables boot-time USB authorization." >&2
return 1
fi
done <"$paths"
rm -f "$paths" "$section"
}
usb_authorization_rebuild_and_verify() {
local expected="$1" config="$2" esp="$3"
local machine_id="${4:-$(</etc/machine-id)}" secure_boot="${5:-auto}"
if ! limine-mkinitcpio; then
return 1
fi
# limine-mkinitcpio-hook can swallow an individual kernel build failure and
# still return success, so its exit status alone is not a safety boundary.
usb_authorization_verify_boot_entries "$expected" "$config" "$esp" "$machine_id" "$secure_boot"
}
usb_authorization_snapshot_block_status() {
local config="$1"
[[ -r $config ]] || return 2
awk -v begin="$snapshot_begin" -v setting="$snapshot_setting" -v end="$snapshot_end" '
$0 == begin { begin_count++; state = 1; next }
$0 == setting { setting_count++; if (state == 1) state = 2; next }
$0 == end { end_count++; if (state == 2) valid_count++; state = 0; next }
{ state = 0 }
END {
if (begin_count == 0 && setting_count == 0 && end_count == 0) exit 1
if (begin_count == 1 && setting_count == 1 && end_count == 1 && valid_count == 1) exit 0
exit 2
}
' "$config"
}
usb_authorization_enable_snapshot_setting() {
local config="$1" status temporary
if usb_authorization_snapshot_block_status "$config"; then
return 0
else
status=$?
fi
if (( status != 1 )); then
echo "Refusing to modify the USB authorization block in $config." >&2
return 1
fi
temporary=$(mktemp "${config}.XXXXXXXXXX")
cp --preserve=mode,ownership "$config" "$temporary"
{
cat "$config"
printf '\n%s\n%s\n%s\n' "$snapshot_begin" "$snapshot_setting" "$snapshot_end"
} >"$temporary"
mv -f "$temporary" "$config"
}
usb_authorization_disable_snapshot_setting() {
local config="$1" status temporary
if usb_authorization_snapshot_block_status "$config"; then
status=0
else
status=$?
fi
(( status == 1 )) && return 0
if (( status != 0 )); then
echo "Refusing to modify the USB authorization block in $config." >&2
return 1
fi
temporary=$(mktemp "${config}.XXXXXXXXXX")
cp --preserve=mode,ownership "$config" "$temporary"
awk -v begin="$snapshot_begin" '
$0 == begin { skip = 2; next }
skip > 0 { skip--; next }
{ print }
' "$config" >"$temporary"
mv -f "$temporary" "$config"
}
usb_authorization_rewrite_boot_cmdlines() {
local expected="$1" config="$2" machine_id="${3:-$(</etc/machine-id)}"
local backup temporary
backup=$(mktemp "${config}.omarchy-usb-backup.XXXXXXXXXX")
temporary=$(mktemp "${config}.omarchy-usb-new.XXXXXXXXXX")
cp --preserve=mode,ownership "$config" "$backup"
cp --preserve=mode,ownership "$config" "$temporary"
if ! awk -v expected="$expected" -v machine_id="$machine_id" -v parameter="$kernel_parameter" '
function rewrite_cmdline(line, prefix, value, count, fields, field_index, result, found) {
prefix = line
sub(/cmdline:.*/, "cmdline:", prefix)
value = line
sub(/^[[:space:]]*cmdline:[[:space:]]*/, "", value)
count = split(value, fields, /[[:space:]]+/)
result = ""
found = 0
for (field_index = 1; field_index <= count; field_index++) {
if (fields[field_index] == parameter) {
found = 1
continue
}
if (fields[field_index] != "") result = result (result == "" ? "" : " ") fields[field_index]
}
if (expected == "enabled") result = result (result == "" ? "" : " ") parameter
return prefix (result == "" ? "" : " ") result
}
/^\/[^/]/ { target_os = 0 }
/^[[:space:]]*comment:[[:space:]]*machine-id=/ {
target_os = index($0, "machine-id=" machine_id) > 0
}
target_os && /^[[:space:]]*cmdline:[[:space:]]*/ {
print rewrite_cmdline($0)
next
}
{ print }
' "$config" >"$temporary"; then
rm -f "$backup" "$temporary"
return 1
fi
mv -f "$temporary" "$config"
if ! limine-enroll-config; then
mv -f "$backup" "$config"
limine-enroll-config >/dev/null 2>&1 || true
echo "Restored $config because its bootloader verification data could not be updated." >&2
return 1
fi
rm -f "$backup"
}
main() {
local had_drop_in=0 had_snapshot_setting=0 snapshot_status temporary
if (( EUID != 0 )); then
echo "Error: omarchy-usb-authorization-boot must run as root" >&2
exit 1
fi
case "${1:-}" in
enable)
if usb_authorization_snapshot_block_status "$limine_defaults"; then
snapshot_status=0
else
snapshot_status=$?
fi
if (( snapshot_status == 2 )); then
echo "Refusing to modify the USB authorization block in $limine_defaults." >&2
exit 1
fi
# Quote the expected value because the Limine key contains glob brackets.
if [[ -e $drop_in ]] && [[ $(<"$drop_in") != "$setting" ]]; then
echo "Refusing to replace the existing $drop_in." >&2
exit 1
fi
usb_authorization_enable_snapshot_setting "$limine_defaults"
if [[ ! -e $drop_in ]]; then
install -d -m755 "${drop_in%/*}"
temporary=$(mktemp "${drop_in}.XXXXXXXXXX")
trap 'rm -f "$temporary"' EXIT
printf '%s\n' "$setting" >"$temporary"
chmod 644 "$temporary"
mv -f "$temporary" "$drop_in"
trap - EXIT
fi
if ! limine-mkinitcpio ||
! usb_authorization_rewrite_boot_cmdlines enabled "$limine_conf" ||
! usb_authorization_verify_boot_entries enabled "$limine_conf" "$boot_path"; then
echo "Boot-time USB authorization remains configured in $drop_in; rerun this command after fixing the boot-image error." >&2
exit 1
fi
;;
disable)
if usb_authorization_snapshot_block_status "$limine_defaults"; then
had_snapshot_setting=1
else
snapshot_status=$?
if (( snapshot_status == 2 )); then
echo "Refusing to modify the USB authorization block in $limine_defaults." >&2
exit 1
fi
fi
if [[ -e $drop_in ]]; then
if [[ $(<"$drop_in") != "$setting" ]]; then
echo "Refusing to remove the modified $drop_in." >&2
exit 1
fi
had_drop_in=1
rm -f "$drop_in"
elif (( ! had_snapshot_setting )) && usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
exit 0
fi
usb_authorization_disable_snapshot_setting "$limine_defaults"
if ! limine-mkinitcpio ||
! usb_authorization_rewrite_boot_cmdlines disabled "$limine_conf" ||
! usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
if (( had_drop_in )); then
printf '%s\n' "$setting" >"$drop_in"
chmod 644 "$drop_in"
fi
if (( had_snapshot_setting )); then
usb_authorization_enable_snapshot_setting "$limine_defaults"
fi
echo "USBGuard remains required because the boot images could not be verified as default-allow." >&2
exit 1
fi
;;
*)
echo "Usage: omarchy-usb-authorization-boot <enable|disable>" >&2
exit 2
;;
esac
}
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
main "$@"
fi
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Create an approval request for a blocked USB device
set -euo pipefail
[[ ${USBGUARD_IPC_SIGNAL:-} == "Device.PresenceChanged" ]] || exit 0
[[ ${USBGUARD_DEVICE_EVENT:-} == "Insert" ]] || exit 0
[[ ${USBGUARD_DEVICE_TARGET:-} == "block" ]] || exit 0
[[ ${USBGUARD_DEVICE_ID:-} =~ ^[0-9]+$ ]] || exit 0
[[ -n ${USBGUARD_DEVICE_RULE:-} ]] || exit 0
requests="$HOME/.local/state/omarchy/usb-authorization/requests"
mkdir -p "$requests"
chmod 700 "${requests%/*}" "$requests"
# The token is safe to carry through the presentation launcher's command
# string. The device-controlled rule itself stays in a mode-0600 JSON file and
# is never interpolated into a shell command.
token="request-$(printf '%s\0%s' "$USBGUARD_DEVICE_ID" "$USBGUARD_DEVICE_RULE" | sha256sum | cut -d' ' -f1)"
request="$requests/$token.json"
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"
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"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# omarchy:summary=Restore the kernel's default USB authorization state
# omarchy:requires-sudo=true
# omarchy:hidden=true
set -euo pipefail
if (( EUID != 0 )); then
echo "Error: omarchy-usb-authorization-restore-default must run as root" >&2
exit 1
fi
# USBGuard intentionally leaves root hubs in default-deny mode when it exits.
# Restore both the hub defaults and devices that are still connected.
shopt -s nullglob
for attribute in /sys/bus/usb/devices/usb*/authorized_default; do
echo 1 >"$attribute"
done
for attribute in /sys/bus/usb/devices/*/authorized; do
echo 1 >"$attribute"
done
shopt -u nullglob
+79
View File
@@ -0,0 +1,79 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Review a blocked USB device authorization request
set -euo pipefail
token="${1:-}"
if [[ ! $token =~ ^request-[0-9a-f]{64}$ ]]; then
echo "This USB authorization request is invalid or expired." >&2
exit 1
fi
request="$HOME/.local/state/omarchy/usb-authorization/requests/$token.json"
if [[ ! -f $request || -L $request ]]; then
echo "This USB authorization request is no longer available." >&2
exit 1
fi
id=$(jq -er '.id | select(test("^[0-9]+$"))' "$request")
expected_rule=$(jq -er '.rule | select(type == "string" and length > 0)' "$request")
read_blocked_rule() {
local line
while IFS= read -r line; do
if [[ $line == "$id: "* ]]; then
printf '%s\n' "${line#"$id: "}"
return 0
fi
done < <(usbguard list-devices --blocked)
return 1
}
current_rule=$(read_blocked_rule || true)
if [[ -z $current_rule || $current_rule != "$expected_rule" ]]; then
rm -f "$request"
echo "That USB device was removed or changed. Connect it again to review it." >&2
exit 1
fi
gum style --foreground 212 --bold "Blocked USB accessory"
echo
echo "The following details come from the device and can be forged:"
printf '%s\n' "$current_rule" | LC_ALL=C tr -c '\11\12\15\40-\176' '?'
echo
choice=$(gum choose \
"Allow once" \
"Always allow this device" \
"Keep blocked") || exit 130
if [[ $choice != "Keep blocked" ]]; then
current_rule=$(read_blocked_rule || true)
if [[ -z $current_rule || $current_rule != "$expected_rule" ]]; then
rm -f "$request"
echo "That USB device was removed or changed. Connect it again to review it." >&2
exit 1
fi
fi
case "$choice" in
"Allow once")
# Target the complete identity rule instead of USBGuard's numeric ID. IDs can
# be reassigned when the daemon restarts while the review dialog is open.
usbguard allow-device "$expected_rule"
echo "USB accessory allowed until it is disconnected."
;;
"Always allow this device")
usbguard allow-device --permanent "$expected_rule"
echo "USB accessory added to the trusted policy."
;;
"Keep blocked")
echo "USB accessory remains blocked."
;;
esac
rm -f "$request"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# omarchy:hidden=true
# omarchy:summary=Watch USBGuard for devices awaiting user approval
set -euo pipefail
event_handler="$OMARCHY_PATH/bin/omarchy-usb-authorization-event"
# A device inserted while the graphical session was stopped is already safely
# blocked by USBGuard. Surface it now so the user can approve it after login.
while IFS= read -r line; do
if [[ $line =~ ^([0-9]+):[[:space:]](block.*)$ ]]; then
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
USBGUARD_DEVICE_ID="${BASH_REMATCH[1]}" \
USBGUARD_DEVICE_EVENT=Insert \
USBGUARD_DEVICE_TARGET=block \
USBGUARD_DEVICE_RULE="${BASH_REMATCH[2]}" \
"$event_handler"
fi
done < <(usbguard list-devices --blocked)
exec usbguard watch --wait --exec "$event_handler"
+4
View File
@@ -181,6 +181,8 @@
"setup.config": {"icon":"","label":"Config"},
"setup.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","when":"omarchy-hw-fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"},
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
"setup.security.usb-authorization": {"icon":"󰖀","label":"USB Device Authorization","checked":"systemctl is-enabled --quiet usbguard.service && systemctl --user is-enabled --quiet omarchy-usb-authorization.service","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-usb-authorization"},
"setup.security.usb-authorization-boot": {"icon":"󰖀","label":"USB at Boot","checked":"grep -Fqx 'KERNEL_CMDLINE[default]+=\" usbcore.authorized_default=0\"' /etc/limine-entry-tool.d/usb-authorization.conf 2>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-setup-security-usb-authorization --boot'"},
"setup.security.sshd": {"icon":"󰣀","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
"setup.security.passwordless-sudo": {"icon":"󰟵","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
"setup.security.sudoless-docker": {"icon":"󰡨","label":"Sudoless Docker","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
@@ -298,6 +300,8 @@
"remove.security": {"icon":"","label":"Security","title":"Remove"},
"remove.security.fingerprint": {"icon":"󰈷","label":"Fingerprint","when":"omarchy-pkg-present fprintd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fingerprint"},
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
"remove.security.usb-authorization": {"icon":"󰖀","label":"USB Device Authorization","when":"systemctl is-enabled --quiet usbguard.service","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-usb-authorization"},
"remove.security.usb-authorization-boot": {"icon":"󰖀","label":"USB at Boot","when":"grep -Fqx 'KERNEL_CMDLINE[default]+=\" usbcore.authorized_default=0\"' /etc/limine-entry-tool.d/usb-authorization.conf 2>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-security-usb-authorization --boot-only'"},
"remove.security.sshd": {"icon":"󰣀","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
"remove.security.sudoless-docker": {"icon":"󰡨","label":"Sudoless Docker","when":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
"remove.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","when":"omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-hermes"},
@@ -0,0 +1,17 @@
[Unit]
Description=Ask before allowing new USB devices
After=graphical-session.target dbus.socket
Requires=dbus.socket
PartOf=graphical-session.target
ConditionEnvironment=OMARCHY_PATH
ConditionEnvironment=WAYLAND_DISPLAY
[Service]
Type=simple
ExecCondition=/usr/bin/systemctl is-active --quiet usbguard.service
ExecStart=/usr/bin/bash -lc 'exec "$OMARCHY_PATH/bin/omarchy-usb-authorization-watch"'
Restart=always
RestartSec=2
[Install]
WantedBy=graphical-session.target
+1
View File
@@ -7,5 +7,6 @@ run_logged "$OMARCHY_INSTALL/config/ssh-command-path.sh"
run_logged "$OMARCHY_INSTALL/config/ssh-keepalive.sh"
run_logged "$OMARCHY_INSTALL/config/docker.sh"
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
run_logged "$OMARCHY_INSTALL/config/usb-authorization.sh"
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
+28
View File
@@ -0,0 +1,28 @@
echo "Configure USB device authorization"
rules_file="${OMARCHY_USB_AUTHORIZATION_RULES_FILE:-/etc/usbguard/rules.conf}"
daemon_config="${OMARCHY_USB_AUTHORIZATION_DAEMON_CONFIG:-/etc/usbguard/usbguard-daemon.conf}"
source "$OMARCHY_INSTALL/helpers/usb-authorization.sh"
usb_authorization_require_secure_settings "$daemon_config"
if [[ ! -s $rules_file ]]; then
policy=$(mktemp "${TMPDIR:-/tmp}/omarchy-usb-policy.XXXXXXXXXX")
chmod 600 "$policy"
if usb_authorization_generate_policy "$policy"; then
install -Dm600 -o root -g root "$policy" "$rules_file"
rm -f "$policy"
else
rm -f "$policy"
return 1
fi
fi
if [[ -n ${OMARCHY_INSTALL_USER:-} ]]; then
usb_authorization_add_user "$OMARCHY_INSTALL_USER"
fi
# Installs are followed by a reboot, so enable the daemon without starting it
# in the live ISO's chroot.
systemctl enable usbguard.service
+99
View File
@@ -0,0 +1,99 @@
usb_authorization_config_value() {
local config="$1" key="$2"
local awk_command=(awk)
if [[ ! -r $config ]]; then
if [[ $config == "/etc/usbguard/usbguard-daemon.conf" ]]; then
awk_command=(sudo awk)
else
echo "Cannot read USBGuard configuration: $config" >&2
return 1
fi
fi
"${awk_command[@]}" -F= -v key="$key" '
/^[[:space:]]*#/ { next }
$1 ~ "^[[:space:]]*" key "[[:space:]]*$" {
value=$0
sub(/^[^=]*=[[:space:]]*/, "", value)
sub(/[[:space:]]*$/, "", value)
print value
exit
}
' "$config"
}
usb_authorization_rules_present() {
local rules="$1"
if [[ -r $rules ]]; then
[[ -s $rules ]]
elif [[ $rules == "/etc/usbguard/rules.conf" ]]; then
sudo test -s "$rules"
else
return 1
fi
}
usb_authorization_require_secure_setting() {
local config="$1" key="$2" expected="$3" actual
actual=$(usb_authorization_config_value "$config" "$key")
if [[ $actual != "$expected" ]]; then
echo "USBGuard's $key must be '$expected' before Omarchy enables it." >&2
echo "Current value: ${actual:-<unset>} in $config" >&2
return 1
fi
}
usb_authorization_require_secure_settings() {
local config="$1"
usb_authorization_require_secure_setting "$config" ImplicitPolicyTarget block
usb_authorization_require_secure_setting "$config" PresentDevicePolicy apply-policy
usb_authorization_require_secure_setting "$config" InsertedDevicePolicy apply-policy
usb_authorization_require_secure_setting "$config" AuthorizedDefault none
usb_authorization_require_secure_setting "$config" RestoreControllerDeviceState false
}
usb_authorization_generate_policy() {
local policy="$1"
usbguard generate-policy >"$policy"
if [[ ! -s $policy ]] || ! grep -q '^allow ' "$policy"; then
echo "USBGuard did not generate a usable policy; nothing was enabled." >&2
return 1
fi
}
usb_authorization_add_user() {
local user="$1"
# This user can inspect USBGuard events and approve the device named by an
# event. Policy and daemon parameters remain unavailable through the IPC ACL.
usbguard add-user "$user" \
--devices=list,listen,modify \
--policy=list \
--exceptions=listen
}
usb_authorization_trust_present_devices() {
local line id
local count=0
local -a devices
mapfile -t devices < <(usbguard list-devices)
for line in "${devices[@]}"; do
if [[ $line =~ ^([0-9]+):[[:space:]] ]]; then
id="${BASH_REMATCH[1]}"
usbguard allow-device --permanent "$id"
(( ++count ))
fi
done
if (( count == 0 )); then
echo "USBGuard did not report any connected USB devices; boot authorization was not enabled." >&2
return 1
fi
}
+1
View File
@@ -133,6 +133,7 @@ udiskie
ufw
ufw-docker
unzip
usbguard
usage
uwsm
vi
+2 -1
View File
@@ -18,4 +18,5 @@ systemctl --user enable --now \
omarchy-sleep-lock.service \
omarchy-migrate-notify.service \
omarchy-fcitx5.service \
omarchy-crash-watch.service
omarchy-crash-watch.service \
omarchy-usb-authorization.service
+14 -1
View File
@@ -6,7 +6,20 @@ Omarchy takes security extremely seriously. This is meant to be an operating sys
2. *Firewall is enabled by default*: All incoming traffic is blocked by default except for port 53317 for [LocalSend](https://localsend.org/). Even ssh is off until you turn it on via _Setup > Security > SSHD_, which opens port 22 (rate limited against brute force) as part of the setup. We even lock down Docker access using the [ufw-docker](https://github.com/chaifeng/ufw-docker) setup to prevent that your containers are accidentally exposed to the world.
3. *Arch always have the latest updates*: Arch, the underlying distro that Omarchy is built on, is a rolling distribution. This means that any security vulnerability that's discovered and patched in any package is quickly available for install using `omarchy-update`. You're always running the latest, most secure versions of everything that way.
4. *Omarchy maintains its own packages and mirror*: Omarchy only relies on packages from Arch's own core/extra/multilib repositories and its own Omarchy Package Repository by default. You can install software directly from AUR, but the base install doesn't — only a few optional installs, like the third-party browsers, pull from the AUR.
5. *Cloudflare protects us from DDoS*: All the Omarchy distribution infrastructure — the ISOs, the Omarchy packages, the Arch mirror — is protected behind Cloudflare's formidable DDoS shield and hosted on their CDN. This provides superb availability.
5. *New USB devices require approval*: Omarchy trusts the USB devices present during installation, then blocks newly connected accessories until you approve them. This keeps an unknown accessory from reaching a matching kernel driver merely because it claims a supported device identity.
6. *Cloudflare protects us from DDoS*: All the Omarchy distribution infrastructure — the ISOs, the Omarchy packages, the Arch mirror — is protected behind Cloudflare's formidable DDoS shield and hosted on their CDN. This provides superb availability.
## USB Device Authorization
USB device authorization is enabled by default. Omarchy uses USBGuard to trust the devices connected during installation and block USB devices it has not seen before. When a device is blocked, click the persistent notification and choose whether to allow it once, always allow it, or keep it blocked.
You can turn the protection off from _Remove > Security > USB Device Authorization_. _Setup > Security > USB Device Authorization_ turns it back on with the existing trusted-device policy.
Only approve hardware you recognize. A malicious USB device can forge the manufacturer, model, serial number, and device class shown during review. Permanent approval is a convenience for accessories you control, not proof of the device's identity.
The policy protects devices connected after USBGuard starts during boot. It does not replace operating-system and driver security updates, and USB devices needed before the service starts, such as a keyboard used to unlock an encrypted disk, remain governed by the kernel's boot-time policy.
For machines whose disk unlock and recovery never depend on USB input, _Setup > Security > USB at Boot_ can extend the block to the beginning of kernel boot. Omarchy adds every currently connected device to USBGuard's trusted policy, sets `usbcore.authorized_default=0`, and updates current and Limine snapshot boot entries. The trusted policy applies only once USBGuard starts: even a trusted USB keyboard remains unavailable at an earlier encrypted-disk password prompt. _Remove > Security > USB at Boot_ restores the normal early-boot behavior while keeping USBGuard active after startup.
## Changing your passwords
+3
View File
@@ -0,0 +1,3 @@
echo "Enable approval for newly connected USB devices"
omarchy-setup-security-usb-authorization --yes
+7
View File
@@ -324,6 +324,13 @@ assert(
defaultById['setup.security.passwordless-sudo'].action.includes('omarchy-sudo-passwordless'),
'menu places Passwordless Sudo under Setup > Security'
)
assert(
defaultById['setup.security.usb-authorization'].action.includes('omarchy-setup-security-usb-authorization')
&& defaultById['remove.security.usb-authorization'].action.includes('omarchy-remove-security-usb-authorization')
&& defaultById['setup.security.usb-authorization-boot'].action.includes('omarchy-setup-security-usb-authorization --boot')
&& defaultById['remove.security.usb-authorization-boot'].action.includes('omarchy-remove-security-usb-authorization --boot-only'),
'menu can enable and remove USB device authorization under Security'
)
assert(
!defaultById['trigger.toggle.direct-boot'] && !defaultById['trigger.toggle.passwordless-sudo'],
'menu removes the relocated toggles from Trigger > Toggle'
+405
View File
@@ -0,0 +1,405 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
scratch=$(mktemp -d)
trap 'rm -rf "$scratch"' EXIT
stub_bin="$scratch/bin"
home="$scratch/home"
rules="$scratch/etc/usbguard/rules.conf"
daemon_config="$scratch/etc/usbguard/usbguard-daemon.conf"
calls="$scratch/calls"
mkdir -p "$stub_bin" "$home" "${rules%/*}"
: >"$rules"
: >"$calls"
cat >"$daemon_config" <<'CONF'
ImplicitPolicyTarget=block
PresentDevicePolicy=apply-policy
InsertedDevicePolicy=apply-policy
AuthorizedDefault=none
RestoreControllerDeviceState=false
CONF
cat >"$stub_bin/omarchy-pkg-missing" <<'STUB'
#!/bin/bash
exit 0
STUB
cat >"$stub_bin/omarchy-pkg-present" <<'STUB'
#!/bin/bash
exit 0
STUB
cat >"$stub_bin/omarchy-pkg-add" <<'STUB'
#!/bin/bash
printf 'pkg-add <%s>\n' "$*" >>"$CALLS"
STUB
cat >"$stub_bin/omarchy-pkg-drop" <<'STUB'
#!/bin/bash
printf 'pkg-drop <%s>\n' "$*" >>"$CALLS"
STUB
cat >"$stub_bin/install" <<'STUB'
#!/bin/bash
if [[ $1 == -Dm600 && $2 == -o && $3 == root && $4 == -g && $5 == root ]]; then
exec /usr/bin/install -Dm600 "$6" "$7"
fi
exec /usr/bin/install "$@"
STUB
cat >"$stub_bin/systemctl" <<'STUB'
#!/bin/bash
printf 'systemctl <%s>\n' "$*" >>"$CALLS"
if [[ $* == "is-enabled --quiet usbguard.service" ]]; then
exit 1
fi
STUB
cat >"$stub_bin/usbguard" <<'STUB'
#!/bin/bash
case "$1" in
generate-policy)
echo 'usbguard <generate-policy>' >>"$CALLS"
echo 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"'
;;
list-devices)
if [[ ${2:-} == "--blocked" ]]; then
if [[ ${BLOCKED_DEVICE_PRESENT:-1} == 1 ]]; then
rule="${BLOCKED_DEVICE_RULE:?}"
if [[ -n ${BLOCKED_DEVICE_RULE_FILE:-} && -f $BLOCKED_DEVICE_RULE_FILE ]]; then
rule=$(<"$BLOCKED_DEVICE_RULE_FILE")
fi
printf '17: %s\n' "$rule"
fi
else
echo '4: allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"'
echo '5: allow id 0627:0001 name "QEMU USB Tablet" hash "tablet"'
fi
;;
*)
printf 'usbguard' >>"$CALLS"
printf ' <%s>' "$@" >>"$CALLS"
printf '\n' >>"$CALLS"
;;
esac
STUB
cat >"$stub_bin/sudo" <<'STUB'
#!/bin/bash
printf 'sudo' >>"$CALLS"
printf ' <%s>' "$@" >>"$CALLS"
printf '\n' >>"$CALLS"
if [[ $1 == awk && ${!#} == /etc/usbguard/usbguard-daemon.conf ]]; then
args=("$@")
args[-1]="$TEST_DAEMON_CONFIG"
exec "${args[@]}"
elif [[ $1 == test && $2 == -s && $3 == /etc/usbguard/rules.conf ]]; then
exec /usr/bin/test -s "$TEST_RULES"
elif [[ $1 == install && $2 == -Dm600 && $3 == -o && $4 == root && $5 == -g && $6 == root && $8 == /etc/usbguard/rules.conf ]]; then
exec /usr/bin/install -Dm600 "$7" "$TEST_RULES"
elif [[ $1 == omarchy-usb-authorization-restore-default ]]; then
echo 1 >"$TEST_SYSFS/usb1/authorized_default"
echo 1 >"$TEST_SYSFS/1-2/authorized"
exit 0
elif [[ $1 == omarchy-usb-authorization-boot ]]; then
exit 0
fi
exec "$@"
STUB
cat >"$stub_bin/gum" <<'STUB'
#!/bin/bash
case "$1" in
style) exit 0 ;;
choose)
if [[ -n ${GUM_REASSIGN_RULE:-} ]]; then
printf '%s\n' "$GUM_REASSIGN_RULE" >"$BLOCKED_DEVICE_RULE_FILE"
fi
printf '%s\n' "${GUM_CHOICE:-Keep blocked}"
;;
confirm) exit 0 ;;
esac
STUB
cat >"$stub_bin/omarchy-notification-send" <<'STUB'
#!/bin/bash
printf 'notification' >>"$CALLS"
printf ' <%s>' "$@" >>"$CALLS"
printf '\n' >>"$CALLS"
STUB
chmod +x "$stub_bin"/*
export HOME="$home"
export USER=tester
export OMARCHY_PATH="$ROOT"
export CALLS="$calls"
export PATH="$stub_bin:$ROOT/bin:$PATH"
export TEST_DAEMON_CONFIG="$daemon_config"
export TEST_RULES="$rules"
if "$ROOT/bin/omarchy-usb-authorization-restore-default" >/dev/null 2>&1; then
fail "the fixed-path USB authorization restore helper requires root"
fi
if "$ROOT/bin/omarchy-usb-authorization-boot" enable >/dev/null 2>&1; then
fail "the fixed-path boot authorization helper requires root"
fi
! grep -q 'OMARCHY_USB_AUTHORIZATION_.*ROOT\|OMARCHY_USB_AUTHORIZATION_.*FILE\|OMARCHY_USB_AUTHORIZATION_.*CONFIG' \
"$ROOT/bin/omarchy-setup-security-usb-authorization" \
"$ROOT/bin/omarchy-remove-security-usb-authorization" \
"$ROOT/bin/omarchy-usb-authorization-boot" ||
fail "user-facing USB authorization commands do not elevate caller-selected paths"
pass "USB authorization keeps privileged paths fixed"
"$ROOT/bin/omarchy-setup-security-usb-authorization" --yes >"$scratch/setup-output"
grep -qx 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"' "$rules" ||
fail "USB authorization setup trusts devices present during enrollment"
[[ $(stat -c %a "$rules") == 600 ]] || fail "USB authorization policy is root-private"
grep -Fqx 'usbguard <add-user> <tester> <--devices=list,listen,modify> <--policy=list> <--exceptions=listen>' "$calls" ||
fail "USB authorization grants only the IPC access needed by the approval flow"
grep -Fqx 'systemctl <restart usbguard.service>' "$calls" ||
fail "USB authorization reloads its user ACL before the watcher starts"
[[ -L $home/.config/systemd/user/omarchy-usb-authorization.service ]] ||
fail "USB authorization installs the graphical-session watcher"
grep -Fqx 'pkg-add <usbguard>' "$calls" || fail "USB authorization ensures USBGuard is installed"
pass "USB authorization enrolls present devices before enabling default-deny"
generate_count=$(grep -c '^usbguard <generate-policy>$' "$calls")
"$ROOT/bin/omarchy-setup-security-usb-authorization" --yes >"$scratch/setup-existing-output"
generate_count_after=$(grep -c '^usbguard <generate-policy>$' "$calls")
(( generate_count_after == generate_count )) || fail "re-enabling preserves the existing trusted-device policy"
pass "USB authorization setup preserves an existing policy"
: >"$calls"
"$ROOT/bin/omarchy-setup-security-usb-authorization" --boot --yes >"$scratch/setup-boot-output"
grep -Fqx 'usbguard <allow-device> <--permanent> <4>' "$calls" ||
fail "boot authorization trusts the connected root controller"
grep -Fqx 'usbguard <allow-device> <--permanent> <5>' "$calls" ||
fail "boot authorization trusts the connected USB devices"
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <enable>' "$calls" ||
fail "boot authorization uses the fixed-path boot-image helper"
grep -Fq 'takes effect after reboot' "$scratch/setup-boot-output" ||
fail "boot authorization explains when the change becomes active"
pass "USB authorization can snapshot connected devices for early-boot denial"
boot_conf="$scratch/limine.conf"
machine_id=$(</etc/machine-id)
touch "$scratch/vmlinuz-linux"
boot_hash=$(b2sum "$scratch/vmlinuz-linux")
boot_hash=${boot_hash%% *}
printf 'quiet splash\0' >"$scratch/embedded-cmdline"
historical_uki="$scratch/embedded.efi_sha256_fixture"
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
embedded_hash=$(b2sum "$historical_uki")
embedded_hash=${embedded_hash%% *}
cat >"$boot_conf" <<CONF
/+Omarchy
comment: machine-id=$machine_id
//linux
comment: kernel-id=linux
path: boot():/vmlinuz-linux#$boot_hash
cmdline: quiet splash
//linux-embedded
comment: kernel-id=linux-embedded
path: boot():/embedded.efi_sha256_fixture#$embedded_hash
cmdline: quiet splash
///Snapshots
////linux
comment: kernel-id=linux
path: boot():/vmlinuz-linux#$boot_hash
cmdline: rootflags=subvol=/snapshot quiet splash
/Other Linux
comment: machine-id=ffffffffffffffffffffffffffffffff
//linux
comment: kernel-id=linux
path: boot():/other-linux
cmdline: quiet splash
CONF
source "$ROOT/bin/omarchy-usb-authorization-boot"
limine-mkinitcpio() { return 0; }
limine-enroll-config() { return 0; }
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
fail "boot authorization rejects Limine's false-success status when entries were not rebuilt"
fi
usb_authorization_rewrite_boot_cmdlines enabled "$boot_conf" "$machine_id"
usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" disabled ||
fail "a non-Secure-Boot snapshot uses its updated external command line"
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
fail "an external allow parameter cannot hide a stale embedded UKI command line"
fi
printf 'quiet splash usbcore.authorized_default=0\0' >"$scratch/embedded-cmdline"
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
updated_embedded_hash=$(b2sum "$historical_uki")
updated_embedded_hash=${updated_embedded_hash%% *}
sed -i "s/embedded.efi_sha256_fixture#$embedded_hash/embedded.efi_sha256_fixture#$updated_embedded_hash/" "$boot_conf"
usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled ||
fail "boot authorization updates current and snapshot entries for this machine"
[[ $(grep -c 'usbcore.authorized_default=0' "$boot_conf") == 3 ]] ||
fail "boot authorization leaves another operating system's kernel entries unchanged"
printf 'stale image' >"$scratch/vmlinuz-linux"
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
fail "boot authorization rejects a stale Limine verification hash"
fi
: >"$scratch/vmlinuz-linux"
if usb_authorization_rebuild_and_verify disabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
fail "boot authorization detects a stale deny-by-default boot entry during removal"
fi
usb_authorization_rewrite_boot_cmdlines disabled "$boot_conf" "$machine_id"
printf 'quiet splash\0' >"$scratch/embedded-cmdline"
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
disabled_embedded_hash=$(b2sum "$historical_uki")
disabled_embedded_hash=${disabled_embedded_hash%% *}
sed -i "s/embedded.efi_sha256_fixture#$updated_embedded_hash/embedded.efi_sha256_fixture#$disabled_embedded_hash/" "$boot_conf"
usb_authorization_rebuild_and_verify disabled "$boot_conf" "$scratch" "$machine_id" enabled ||
fail "boot authorization removes the parameter from current and snapshot entries"
snapshot_config="$scratch/limine-defaults"
printf 'TARGET_OS_NAME="Omarchy"\n' >"$snapshot_config"
usb_authorization_enable_snapshot_setting "$snapshot_config"
grep -Fqx 'SNAPSHOT_KERNEL_PARAMETERS+=usbcore.authorized_default=0' "$snapshot_config" ||
fail "future Limine snapshots inherit boot-time USB authorization"
usb_authorization_enable_snapshot_setting "$snapshot_config"
[[ $(grep -Fxc '# Omarchy USB authorization begin' "$snapshot_config") == 1 ]] ||
fail "the snapshot setting remains idempotent"
usb_authorization_disable_snapshot_setting "$snapshot_config"
! grep -Fq 'usbcore.authorized_default=0' "$snapshot_config" ||
fail "removing boot authorization restores future snapshot defaults"
pass "boot authorization verifies generated entries despite Limine false-success exits"
: >"$calls"
"$ROOT/bin/omarchy-remove-security-usb-authorization" --boot-only --yes >"$scratch/remove-boot-output"
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <disable>' "$calls" ||
fail "boot-only removal rebuilds the permissive boot policy"
! grep -Fq 'systemctl <--user disable --now omarchy-usb-authorization.service>' "$calls" ||
fail "boot-only removal leaves the userspace approval watcher enabled"
grep -Fq 'takes effect after reboot' "$scratch/remove-boot-output" ||
fail "boot-only removal explains when the change becomes active"
pass "early-boot denial can be removed without disabling USBGuard"
grep -qx 'usbguard' "$ROOT/install/omarchy-base.packages" || fail "USBGuard is installed by default"
grep -Fq 'config/usb-authorization.sh' "$ROOT/install/config/all.sh" ||
fail "fresh installs configure the USB authorization policy"
grep -Fq 'omarchy-usb-authorization.service' "$ROOT/install/user/first-run/enable-user-units.sh" ||
fail "fresh installs enable the USB approval watcher"
grep -Fq 'usb_authorization_add_user "$username"' "$ROOT/bin/omarchy-provision-owner" ||
fail "deferred provisioning grants the new owner approval access"
grep -Fq 'systemctl try-restart usbguard.service' "$ROOT/bin/omarchy-provision-owner" ||
fail "deferred provisioning reloads the new owner ACL"
grep -Fqx 'omarchy-setup-security-usb-authorization --yes' "$ROOT/migrations/1789433473.sh" ||
fail "existing installs enable USB authorization during migration"
: >"$rules"
: >"$calls"
export OMARCHY_INSTALL="$ROOT/install"
export OMARCHY_INSTALL_USER=tester
export OMARCHY_USB_AUTHORIZATION_RULES_FILE="$rules"
export OMARCHY_USB_AUTHORIZATION_DAEMON_CONFIG="$daemon_config"
bash -euo pipefail -c 'source "$OMARCHY_INSTALL/config/usb-authorization.sh"' >"$scratch/install-output"
grep -qx 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"' "$rules" ||
fail "fresh installation enrolls devices present during installation"
grep -Fqx 'usbguard <add-user> <tester> <--devices=list,listen,modify> <--policy=list> <--exceptions=listen>' "$calls" ||
fail "fresh installation grants the owner narrowly scoped approval access"
grep -Fqx 'systemctl <enable usbguard.service>' "$calls" ||
fail "fresh installation enables USBGuard for the first boot"
pass "USB authorization is the default for fresh and existing installations"
malicious_rule='block id 07a6:8513 name "$(touch '"$scratch"'/injected)" hash "attacker" with-interface 02:06:00'
export BLOCKED_DEVICE_RULE="$malicious_rule"
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
USBGUARD_DEVICE_ID=17 \
USBGUARD_DEVICE_EVENT=Insert \
USBGUARD_DEVICE_TARGET=block \
USBGUARD_DEVICE_RULE="$malicious_rule" \
"$ROOT/bin/omarchy-usb-authorization-event"
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
[[ -n $request ]] || fail "a blocked USB device creates a review request"
[[ $(stat -c %a "$request") == 600 ]] || fail "device-controlled request data stays private"
[[ $(jq -r .id "$request") == 17 ]] || fail "the request records the USBGuard device id"
[[ $(jq -r .rule "$request") == "$malicious_rule" ]] || fail "the request preserves the identity snapshot"
! grep -Fq "$malicious_rule" "$calls" || fail "device text never enters the notification's launch command"
[[ ! -e $scratch/injected ]] || fail "device text cannot execute during notification"
token=$(basename "${request%.json}")
grep -Eq "notification .*<omarchy-usb-authorization-review> <$token>$" "$calls" ||
fail "the notification action carries only an opaque request token"
pass "blocked device metadata remains data across the desktop notification boundary"
GUM_CHOICE='Allow once' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-once-output"
grep -Fqx "usbguard <allow-device> <$malicious_rule>" "$calls" || fail "review can allow the exact device once"
[[ ! -e $request ]] || fail "a completed review consumes its request"
pass "USB review allows a still-matching device once"
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
USBGUARD_DEVICE_ID=17 \
USBGUARD_DEVICE_EVENT=Insert \
USBGUARD_DEVICE_TARGET=block \
USBGUARD_DEVICE_RULE="$malicious_rule" \
"$ROOT/bin/omarchy-usb-authorization-event"
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
token=$(basename "${request%.json}")
GUM_CHOICE='Always allow this device' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-always-output"
grep -Fqx "usbguard <allow-device> <--permanent> <$malicious_rule>" "$calls" || fail "review can persist an exact-device rule"
pass "USB review supports explicit persistent trust"
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
USBGUARD_DEVICE_ID=17 \
USBGUARD_DEVICE_EVENT=Insert \
USBGUARD_DEVICE_TARGET=block \
USBGUARD_DEVICE_RULE="$malicious_rule" \
"$ROOT/bin/omarchy-usb-authorization-event"
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
token=$(basename "${request%.json}")
device_rule_file="$scratch/blocked-device-rule"
printf '%s\n' "$malicious_rule" >"$device_rule_file"
replacement_rule='block id 1d50:60c7 name "Replacement gadget" hash "replacement"'
allow_count=$(grep -c '^usbguard <allow-device>' "$calls")
if BLOCKED_DEVICE_RULE_FILE="$device_rule_file" \
GUM_REASSIGN_RULE="$replacement_rule" \
GUM_CHOICE='Always allow this device' \
"$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-reassigned-output" 2>&1; then
fail "review rejects a USBGuard ID reassigned while the approval dialog is open"
fi
[[ $(grep -c '^usbguard <allow-device>' "$calls") == "$allow_count" ]] ||
fail "an ID reassigned during review cannot authorize a different device"
[[ ! -e $request ]] || fail "a reassigned-ID review request is discarded"
pass "USB review revalidates identity after the approval dialog"
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
USBGUARD_DEVICE_ID=17 \
USBGUARD_DEVICE_EVENT=Insert \
USBGUARD_DEVICE_TARGET=block \
USBGUARD_DEVICE_RULE="$malicious_rule" \
"$ROOT/bin/omarchy-usb-authorization-event"
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
token=$(basename "${request%.json}")
allow_count=$(grep -c '^usbguard <allow-device>' "$calls")
if BLOCKED_DEVICE_PRESENT=0 "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-stale-output" 2>&1; then
fail "review rejects a request after its device disappears"
fi
[[ $(grep -c '^usbguard <allow-device>' "$calls") == "$allow_count" ]] ||
fail "a reused USBGuard id cannot authorize a different device"
[[ ! -e $request ]] || fail "a stale review request is discarded"
pass "USB review binds approval to the device snapshot"
sysfs="$scratch/sys/bus/usb/devices"
mkdir -p "$sysfs/usb1" "$sysfs/1-2"
echo 0 >"$sysfs/usb1/authorized_default"
echo 0 >"$sysfs/1-2/authorized"
export TEST_SYSFS="$sysfs"
"$ROOT/bin/omarchy-remove-security-usb-authorization" --yes >"$scratch/remove-output"
[[ $(<"$sysfs/usb1/authorized_default") == 1 ]] || fail "removal restores root-hub default authorization"
[[ $(<"$sysfs/1-2/authorized") == 1 ]] || fail "removal reauthorizes devices left connected"
grep -Fqx 'systemctl <disable --now usbguard.service>' "$calls" || fail "removal stops a service Omarchy enabled"
grep -Fqx 'usbguard <remove-user> <tester>' "$calls" || fail "removal drops the user's USBGuard IPC access"
grep -Fqx 'sudo <omarchy-usb-authorization-restore-default>' "$calls" ||
fail "removal restores USB authorization only through the fixed-path root helper"
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <disable>' "$calls" ||
fail "removing USB authorization also removes early-boot denial"
! grep -Fqx 'pkg-drop <usbguard>' "$calls" || fail "disabling keeps the default USBGuard package installed"
[[ ! -e $home/.config/systemd/user/omarchy-usb-authorization.service ]] ||
fail "removal deletes the graphical-session watcher"
pass "USB authorization removal restores the original default-allow behavior"