Proof Portal
Omarchy
ProbeLabs65 findings · 95 requirementsA proof layer — requirements, tests and verified fixes — for two of Omarchy's subsystems: the application menu (launcher scripts, QML model, JSONC config, search and selection) and the lock screen (lock scripts, QML, PAM authentication). Scope is deliberately limited to those components of omacom/omarchy; the rest of the distribution is not covered.
Hardening: honor omarchy-exec-argv only from senders holding a per-session token (sandboxed bus clients can currently get click-to-exec)
Reported by itz4blitz · filed Aug 26, 2026 · open at source
At a glance
- 1 · What is claimed?
- Only the report text below — no validator has restated it as checkable claims yet.
- 2 · What does it affect?
- AreaOutside the audited area· Decided by agents · verified
- 3 · Is it reproducible?
- No validation has been run yet.
- 4 · What remains unclear?
- Not validated yet: no claims, no reproducer.
- 5 · What happens next?
- Decided by agents and verified by a second, independent agent — see the decision.
Original report
Summary
#7926 moved notification click commands to an argv hint and made --exec the only door to them — good. What remains is who may set that door: omarchy-exec-argv is honored from any sender on the session bus. docs/notifications.md records the rationale ("a native same-user process … already runs with your privileges"), and @acrogenesis demonstrated the consequence live in the #7926 review: any local notify-send -h string:omarchy-exec-argv:[...] gets click-to-exec.
Why the same-uid argument has a gap
"Same uid on the session bus" is not "same privileges." The session bus is shared with processes whose entire purpose is to not be privilege-equivalent to the desktop user:
- Flatpak/bubblewrap apps. Notification-capable sandboxed apps (chat clients are the canonical case) can call
Notifydirectly with arbitrary hints. They cannot read$XDG_RUNTIME_DIR— Flatpak gives them their own/run/user/$uid/app/…— but nothing stops them settingomarchy-exec-argv. One convincing toast plus one user click is unsandboxed code exec, i.e. a sandbox escape by notification. This is a materially different bar from "the app could already run commands." - Other confinement sharing the bus (firejail, distrobox/toolbox containers with bus passthrough,
systemd-run --user -p …restricted units). Same shape.
A sandbox that can be exited by clicking its own notification isn't a security boundary, and Omarchy is the component turning the hint into exec.
Why an app_name allowlist isn't enough
app_name is sender-chosen text; a hostile sender just sets it to omarchy-action. An allowlist is worth having as harm-reduction (it stops accidental relay of the hint by well-behaved apps), but it is not a boundary and shouldn't be presented as one.
Proposal: a per-session capability token
The notification daemon can distinguish "sent by a host process" from "sent by anything else" without identifying senders, because host processes can read a file sandboxed ones can't:
- When the notifications service starts, Quickshell generates a random token and writes it to
$XDG_RUNTIME_DIR/omarchy/notification-exec-token, mode0600(the runtime dir is0700and per-user; sandboxes don't see the host's). omarchy-notification-send --execreads that file and attaches its value as anomarchy-exec-tokenhint next toomarchy-exec-argv. Unreadable token →--execerrors loudly at send time.- The shell honors
execArgvonly when the token hint matches its own token for this session. Mismatch or absence → no click command (the toast still renders; third parties already fall back to the libnotify default action / focus-by-class path, which is what they're documented to use). - Persisted popup snapshots keep
execArgv(already validated at receive) and never persist the token, so history files don't retain it.
Properties:
- Fail closed. Anything that can't read the user's runtime dir cannot get click-to-exec, period — no spoofable string involved.
- No compat surface. Only Omarchy's own helper uses the hint (docs position libnotify actions as the third-party click mechanism), and the helper runs unsandboxed on the host, so every legitimate caller keeps working unchanged.
- Adjacent benefit for #8346: today a quickshell-version mismatch silently no-ops the hint at click time. With the token handshake, a sender-side problem is visible at send time instead of discovered when a click does nothing.
- Testable.
NotificationLogic.jsis Node-loadable, so token-check coverage slots straight intotest/shell.d/notifications-test.sh.
Alternatives considered
- Sender-credential checks (resolve the D-Bus unique name to a PID, inspect its sandbox) — the freedesktop protocol doesn't hand Quickshell the sender here, and it's fragile in exactly the ways a file-permission check isn't.
- Dropping hint-based click commands entirely and going back to libnotify actions — #7926's PR description explains why that path can't survive shell restarts.
Happy to implement this if the approach sounds right — it's a contained change to Service.qml, NotificationLogic.js, and omarchy-notification-send.
View raw text
### Summary
#7926 moved notification click commands to an argv hint and made `--exec` the only door to them — good. What remains is *who may set that door*: `omarchy-exec-argv` is honored from **any** sender on the session bus. `docs/notifications.md` records the rationale ("a native same-user process … already runs with your privileges"), and @acrogenesis demonstrated the consequence live in the #7926 review: any local `notify-send -h string:omarchy-exec-argv:[...]` gets click-to-exec.
### Why the same-uid argument has a gap
"Same uid on the session bus" is not "same privileges." The session bus is shared with processes whose entire purpose is to *not* be privilege-equivalent to the desktop user:
- **Flatpak/bubblewrap apps.** Notification-capable sandboxed apps (chat clients are the canonical case) can call `Notify` directly with arbitrary hints. They cannot read `$XDG_RUNTIME_DIR` — Flatpak gives them their own `/run/user/$uid/app/…` — but nothing stops them setting `omarchy-exec-argv`. One convincing toast plus one user click is unsandboxed code exec, i.e. a sandbox escape by notification. This is a materially different bar from "the app could already run commands."
- **Other confinement sharing the bus** (firejail, distrobox/toolbox containers with bus passthrough, `systemd-run --user -p …` restricted units). Same shape.
A sandbox that can be exited by clicking its own notification isn't a security boundary, and Omarchy is the component turning the hint into exec.
### Why an `app_name` allowlist isn't enough
`app_name` is sender-chosen text; a hostile sender just sets it to `omarchy-action`. An allowlist is worth having as harm-reduction (it stops *accidental* relay of the hint by well-behaved apps), but it is not a boundary and shouldn't be presented as one.
### Proposal: a per-session capability token
The notification daemon can distinguish "sent by a host process" from "sent by anything else" without identifying senders, because host processes can read a file sandboxed ones can't:
1. When the notifications service starts, Quickshell generates a random token and writes it to `$XDG_RUNTIME_DIR/omarchy/notification-exec-token`, mode `0600` (the runtime dir is `0700` and per-user; sandboxes don't see the host's).
2. `omarchy-notification-send --exec` reads that file and attaches its value as an `omarchy-exec-token` hint next to `omarchy-exec-argv`. Unreadable token → `--exec` errors loudly at send time.
3. The shell honors `execArgv` only when the token hint matches its own token for this session. Mismatch or absence → no click command (the toast still renders; third parties already fall back to the libnotify default action / focus-by-class path, which is what they're documented to use).
4. Persisted popup snapshots keep `execArgv` (already validated at receive) and never persist the token, so history files don't retain it.
Properties:
- **Fail closed.** Anything that can't read the user's runtime dir cannot get click-to-exec, period — no spoofable string involved.
- **No compat surface.** Only Omarchy's own helper uses the hint (docs position libnotify actions as the third-party click mechanism), and the helper runs unsandboxed on the host, so every legitimate caller keeps working unchanged.
- **Adjacent benefit for #8346:** today a quickshell-version mismatch silently no-ops the hint at click time. With the token handshake, a sender-side problem is visible at send time instead of discovered when a click does nothing.
- **Testable.** `NotificationLogic.js` is Node-loadable, so token-check coverage slots straight into `test/shell.d/notifications-test.sh`.
### Alternatives considered
- Sender-credential checks (resolve the D-Bus unique name to a PID, inspect its sandbox) — the freedesktop protocol doesn't hand Quickshell the sender here, and it's fragile in exactly the ways a file-permission check isn't.
- Dropping hint-based click commands entirely and going back to libnotify actions — #7926's PR description explains why that path can't survive shell restarts.
Happy to implement this if the approach sounds right — it's a contained change to `Service.qml`, `NotificationLogic.js`, and `omarchy-notification-send`.
Validation
Not checked yet. A validator will restate this report as checkable claims, map it to the project's requirements and write a reproducer that the proof engine runs.
Linked
Not linked to any known issue, other report or change yet.
Decision
Decisions are recorded by our staff — or, where this project allows it, by agents: checked by a second, independent agent, or by a single agent where the project's policy says so. Any decision reached so far is shown below.
- Out of scopeDecided by agents · verified · 2026-10-09 09:22 UTC