Remove --exec entirely; --exec-arg is the only click-command form

A free-form shell-string --exec sitting next to the safe --exec-arg is a
standing invitation for the next caller to interpolate untrusted data and
reintroduce the RCE. Remove it: omarchy-notification-send --exec now errors and
points at --exec-arg, and the shell drops the omarchy-exec string hint and its
bash -lc execution path, leaving only the argv path.

Migrate the remaining string callers (the first-run invitation hooks, wifi and
welcome prompts) to --exec-arg, and update their notification mocks. Trim the
verbose security comments added along the way.
This commit is contained in:
Ryan Hughes
2026-08-23 13:35:02 -04:00
parent d2fd2e11c6
commit eb988b42e6
17 changed files with 97 additions and 158 deletions
+3 -4
View File
@@ -27,7 +27,7 @@ valid_url() {
# A printed path is only usable if it is a regular file inside DOWNLOAD_DIR.
# Forged records (leading-dash mpv options, paths with control chars, or
# anything that escaped the download directory) must not reach --exec.
# anything that escaped the download directory) must not reach the click command.
resolve_download_file() {
local candidate=$1 file_real dir_real
@@ -70,9 +70,8 @@ title_from_file() {
fi
}
# The click action is passed to the shell as an argv vector (--exec-arg), so the
# path is one literal argument and never reaches a shell. `--` still guards mpv
# itself against parsing a leading-dash filename as an option.
# Click action as argv (--exec-arg): the path is one literal argument, and `--`
# keeps mpv from parsing a leading-dash filename as an option.
playback_exec_args=(--exec-arg mpv --exec-arg -- --exec-arg)
# Drive the Quickshell OSD — a single overlay that updates in place (like the
+3 -4
View File
@@ -33,10 +33,9 @@ announce() {
omarchy-notification-wait || return 1
# --exec-arg rather than a libnotify action: the shell runs clicks from its own
# omarchy-exec-argv hint and never emits ActionInvoked. Keeps the default
# "omarchy-action" app name too, the only one shouldBypassDnd() lets through.
# The argv form carries the crash details as literal arguments, so a hostile
# process name can't be reparsed as a command when the toast is clicked.
# hint and never emits ActionInvoked. Keeps the default "omarchy-action" app
# name, the only one shouldBypassDnd() lets through. Crash details ride as
# literal argv, so a hostile process name can't be reparsed as a command.
omarchy-notification-send \
--urgency critical \
--glyph "$CRASH_GLYPH" \
+13 -27
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# omarchy:summary=Send an Omarchy desktop notification
# omarchy:args=[--exec-arg <arg>]... [--exec <command>] [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]
# omarchy:args=[--exec-arg <arg>]... [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]
# omarchy:examples=omarchy notification send "Reminder" "5 minutes are up" -g 󰢌
set -euo pipefail
@@ -12,7 +12,6 @@ glyph=
urgency="low"
app_name="omarchy-action"
image=
exec_command=
exec_args=()
args=()
parsed_option_args=0
@@ -56,23 +55,17 @@ parse_omarchy_option() {
return 0
;;
--exec)
if (($# < 2)); then
echo "Missing value for $1" >&2
exit 1
fi
exec_command=$2
parsed_option_args=2
return 0
# Removed: a free-form shell string is safe only if the caller quotes every
# value, and its existence invites the next caller to skip that. Use --exec-arg.
echo "--exec is no longer supported: pass each argument with --exec-arg (e.g. --exec-arg mpv --exec-arg -- --exec-arg \"\$file\")" >&2
exit 1
;;
--exec-arg)
if (($# < 2)); then
echo "Missing value for $1" >&2
exit 1
fi
# Each --exec-arg contributes one literal argument to the click command.
# The shell runs the resulting argv vector directly (no shell), so callers
# pass untrusted data as its own --exec-arg rather than quoting it into a
# command string. Value is taken verbatim, even when it starts with "-".
# One literal argument of the click command, taken verbatim (even a "-value").
exec_args+=("$2")
parsed_option_args=2
return 0
@@ -91,7 +84,7 @@ while (($# > 0)); do
done
if (($# < 1)); then
echo "Usage: omarchy-notification-send [--exec-arg <arg>]... [--exec <command>] [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]"
echo "Usage: omarchy-notification-send [--exec-arg <arg>]... [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options]"
exit 1
fi
@@ -123,22 +116,15 @@ if [[ -n $image ]]; then
args+=("--hint=string:image-path:$image")
fi
# The shell runs the click command itself, from a copy it keeps alongside the
# on-screen popup. A libnotify action would instead keep this process blocked
# until the click, and die unanswered whenever the shell restarts underneath it.
#
# --exec-arg builds an argv vector the shell runs directly, so a value carrying
# untrusted data is only ever one argument and never reaches a shell. It wins
# over the legacy free-form --exec string, which is run through `bash -lc` and
# is only safe when the caller quoted every interpolated value itself.
# The click command travels with the popup as an argv hint the shell runs
# itself, so restored toasts stay clickable and senders don't block on a
# libnotify action (which dies when the shell restarts). --exec-arg is the only
# form: no free-form shell string to interpolate into unsafely.
if ((${#exec_args[@]} > 0)); then
# NUL-delimit the args into jq so every byte survives as data jq's own
# --args would eat a bare "--", and a title with a newline must stay one
# element, not split the vector.
# NUL-delimit into jq so every byte survives as data: jq's own --args would eat
# a bare "--", and a newline in an arg must not split the vector.
exec_argv_json=$(printf '%s\0' "${exec_args[@]}" | jq -Rsc 'split("\u0000")[:-1]')
args+=("--hint=string:omarchy-exec-argv:$exec_argv_json")
elif [[ -n $exec_command ]]; then
args+=("--hint=string:omarchy-exec:$exec_command")
fi
if [[ -n $description ]]; then