Skip to content

Replace default-on device approval with opt-in USB protection while locked - #14937

Open
acrogenesis wants to merge 1 commit into
omacom:quattrofrom
acrogenesis:feature/usb-lock-screen-only
Open

acrogenesis wants to merge 1 commit into
omacom:quattrofrom
acrogenesis:feature/usb-lock-screen-only

Conversation

@acrogenesis

@acrogenesis acrogenesis commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Proposal

Standalone alternative to #14936, based directly on quattro in one commit. This PR replaces the default-on device approval rollout from #11874 with an opt-in preview under Setup > Security > USB While Locked. It can merge on its own; #14936 does not need to merge first. Nothing enables the preview automatically on installation or update. Product review with David is required before considering a new default.

The change includes withdrawal of automatic USB/Thunderbolt enrollment, removal of the old enablement migrations and menus, and its own cleanup migration for machines already enrolled by Omarchy. Cleanup restores normal USB access, removes optional boot restrictions through the existing verified removal path, and restores the previous Bolt policy while preserving manual rules and saved identities. Failures remain pending with recovery tools installed. Once the migration completes, users can explicitly enable the new lock-screen workflow.

  • Boot and initial login work normally.
  • While unlocked, devices work without approval prompts.
  • Locking preserves the identities of currently connected, allowed devices. They can reconnect through another port or hub; unfamiliar devices stay blocked.
  • Successful unlock automatically allows waiting devices. This is the selected UX: a malicious device left connected can reach its driver at unlock. A new keyboard cannot be used to unlock the screen; existing input must remain available.
  • Thunderbolt/USB4 PCIe behavior remains with Bolt's normal policy; USB functions on docks follow this USB policy.

A separate USBGuard service uses a root-only policy under /run, preserving the frozen identity list across daemon and shell restarts but resetting to permissive after reboot. Existing /etc/usbguard policy is left alone. Protected Bash entrypoints and a narrow Polkit action handle lock transitions; setup/removal require sudo. The screen locks immediately, USB transitions retry on failure, and suspend waits for both acknowledgements within its existing budget.

Machine-wide cleanup runs through one protected Bash root helper under a shared lock. It recovers saved Thunderbolt setup/boot state even when enrollment markers are missing, starts an inactive Bolt daemon before policy inspection, and keeps failed recovery pending. Each user retires the exact watcher links, including dangling wants/requires links that systemd reports as not-found. Factory snapshot repair follows #14938: remove old enrollment hooks and migrations, preserve the original Bolt mode and read-only state across retries, and publish complete files atomically.

USB ownership requires current enrollment evidence. A historical migration-completed marker alone does not trigger removal: those markers survive opt-out and are also stamped by fresh-install provisioning. The lifecycle regression runs the actual removal file cleanup, then configures an independent USBGuard policy and verifies that repeated rollback preserves its service, rules and blocked-device state. Current enrollment and saved recovery still run through the full user-to-root cleanup path.

Validation

  • Based directly on quattro at 04b374a9. The current shared cleanup passes 40 rollback groups, with regression mutations proving the cleanup bugs and both stale-ownership scenarios are detected. USB authorization, boot archive/enrollment, CLI, menu/guards and factory reset suites pass; this branch also passes lock policy and suspend checks. First-run, owner keyboard/regdom, LUKS rekey and native USBGuard matcher checks passed before the cleanup-only changes.
  • Policy lifecycle/failure tests: permissive boot, frozen identities, repeated locks, daemon preparation, failed inventory/restart/removal, preservation of manually managed USBGuard, and injection-resistant privileged startup.
  • Executed Quickshell transition handlers cover rapid lock/unlock ordering, failed acknowledgement and retries. Suspend tests cover pending USB enforcement and an accurate failure warning.
  • CLI, menu/guards, lock recovery/fingerprint tests and the real libusbguard matcher pass; the matcher checks port/hub portability and rejects changed identity attributes.
  • Real Omarchy Lab: setup dialog and menu visually checked; enabled through the graphical confirmation; a hot-plugged QEMU USB mouse was blocked while locked (kernel authorized=0), while the existing tablet remained allowed. The decision survived a USBGuard restart and killing/recovering the shell while locked. Graphical password unlock allowed the waiting mouse (authorized=1). A full reboot from the locked state reset policy to allow and allowed both devices before the desktop shell started. Removal restored controller defaults and connected-device authorization; the first attempt exposed a missing installed legacy helper in the development guest, and succeeded after installing that package-provided dependency.

The current cleanup refactor was tested with isolated failure fixtures and focused suites, without another Lab or physical firmware run. The lock-screen implementation exercised in the Lab remains unchanged.

Draft limits

This is not a Pegasus driver fix and does not protect an unlocked machine or authenticate forgeable USB identities. USB enforcement follows the visible screen lock, so there is a transition window. Newly appearing controllers during a daemon outage are not boot-level protected. The active local user can request policy transitions; this does not defend against a compromised user session.

Physical docking, real suspend/resume, multiple seats/sessions, and interrupted requests across session/process boundaries need further review and validation before rollout. The Lab exercises simulated USB hardware, not a fresh ISO or physical Thunderbolt/DMA behavior. The prototype remains opt-in and draft for these decisions.

Implementation details: USB while locked.

@acrogenesis
acrogenesis force-pushed the feature/usb-lock-screen-only branch from b9360ff to e14c0c4 Compare October 11, 2026 00:19
@acrogenesis acrogenesis changed the title Preview USB protection only while the screen is locked Replace default-on device approval with opt-in USB protection while locked Oct 11, 2026
@acrogenesis
acrogenesis force-pushed the feature/usb-lock-screen-only branch from e14c0c4 to b397975 Compare October 11, 2026 00:35
@acrogenesis
acrogenesis force-pushed the feature/usb-lock-screen-only branch from b397975 to 97aa7b0 Compare October 11, 2026 00:49
@acrogenesis
acrogenesis marked this pull request as ready for review October 11, 2026 00:51
@greptile-apps

greptile-apps Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Critical impact] Replaces automatic device approval with opt-in lock-screen USB blocking.

Fix recovery from a stopped USB daemon before merging.

Findings

  1. P1 USB retries cannot recover ▶
  2. P2 Missing files escape tests ▶

Summary

This PR replaces automatic device approval with an opt-in USB lock-screen preview.

  • USB devices stay restricted only while the screen is locked.
  • Omarchy stops enrolling devices automatically and restores earlier policies.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Update[Update] --> Cleanup[Restore old USB and Bolt settings]
  Cleanup --> Factory[Repair factory enrollment]
  OptIn[User enables preview] --> Allow[Permissive USB policy]
  Allow --> Lock[Screen locks]
  Lock --> Freeze[Freeze allowed device identities]
  Freeze --> Restart[Restart USBGuard]
  Restart --> Ready[Confirm USB policy]
  Ready --> Unlock[Successful screen unlock]
  Unlock --> Allow
  Restart --> Failure[Transition fails]
  Failure --> Retry[Shell retries]
  Retry --> Active{Service active?}
  Active -->|Yes| Restart
  Active -->|No| Failure
Loading

Reviews (1) · Last reviewed commit: "Replace default-on device approval with ..." · Reviewed by Greptile

local action=$1 inventory line rule policy first
[[ $action == "lock" || $action == "unlock" ]] || return 64
if [[ ! -e $USB_LOCK_ENABLED ]]; then echo off; return 0; fi
systemctl is-active --quiet "$USB_LOCK_SERVICE" || return 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 USB retries cannot recover

usb_lock_change exits immediately when omarchy-usb-lock.service is inactive. If a restart leaves the service failed—for example, after repeated startup failures hit systemd's restart limit—every later lock or unlock retry exits here without trying to start it. Even after the startup problem is fixed, USB devices can remain blocked after unlock until someone manually starts the service.

Let retries recover the service while preserving the frozen policy.

Comment on lines +26 to +38
elif [[ $1 == "restart" ]]; then
[[ $failure != "restart" ]] || return 1
cp "$USB_LOCK_DIR/rules.conf" "$scratch/loaded"
elif [[ $1 == "disable" ]]; then
[[ $failure != "stop" ]] || return 1
active=0
fi
}
omarchy-usb-authorization-restore-default() { echo restore-default >>"$calls"; }

device='allow id 046d:c53a serial "" name "USB Receiver" hash "KCUFt1MumW4Pfs/8YXWzGQB6Hsnm8qkDqFVjfY0NIBY=" parent-hash "m7yTNWlczwBbYn+uRP6TRsY5AceKmAZ0Et6mgy58+/o=" via-port "3-1.1.2.1" with-interface { 03:01:01 03:01:02 03:00:00 } with-connect-type "unknown"'
printf '7: %s\n8: block id 1234:5678 name "Blocked"\n' "$device" >"$device_inventory_file"
: >"$calls"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing files escape tests

Setup relies on omarchy-usb-lock.service and 40-omarchy-usb-lock.rules already being installed, but this test mocks systemctl and never checks that either file ships. The existing package checks cover a fixed file list and user services, not these new files. A packaging omission could therefore pass the tests while leaving setup or lock transitions unusable.

Add package coverage checks for both required files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant