Proof Portal

Project overview

Omarchy

ProbeLabs65 findings · 95 requirements

A 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.

Inbox
ReportR-1412GitHub issuePublicDecided· Out of scope

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

Decided: Out of scope — decided by agents · verified 4 days ago.

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

Hardening: honor omarchy-exec-argv only from senders holding a per-session token (sandboxed bus clients can currently get click-to-exec)revision 1 · captured 2026-09-28 21:24 UTC via poll

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.

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