Make --exec take the command as rest-of-line words
Replace --exec-arg with an ergonomic --exec that consumes the rest of the line as the click command. The caller's shell tokenizes the words into discrete arguments before the tool sees them, and the shell runs them as positional parameters (never a re-parsed string), so safety is identical to the argv form while the call sites read naturally: `--exec omarchy toggle something`. Crucially the tool never splits a string itself — a single quoted whole-command argument is rejected and points at the unquoted form, because whitespace- splitting a string hands argument boundaries to whoever controls its content (the injection we are avoiding). --exec must come last; migrate every caller.
This commit is contained in:
@@ -228,7 +228,7 @@ stop_screenrecording() {
|
||||
|
||||
omarchy-notification-send "Screen recording saved" "Open with Super + Alt + , (or click this)" \
|
||||
-t 10000 --image "${preview:-$filename}" \
|
||||
--exec-arg mpv --exec-arg -- --exec-arg "$filename"
|
||||
--exec mpv -- "$filename"
|
||||
|
||||
# The shell loads the thumbnail into memory when the toast appears and never
|
||||
# re-reads the file, so the preview only has to outlive that load -- not the
|
||||
|
||||
@@ -70,7 +70,7 @@ case "$PROCESSING" in
|
||||
# notification outage must not report the capture itself as failed.
|
||||
omarchy-notification-send "Screenshot saved to clipboard and file" "Edit with Super + Alt + , (or click this)" \
|
||||
--image "$FILEPATH" \
|
||||
--exec-arg "$SCREENSHOT_EDITOR" --exec-arg "$FILEPATH" || true
|
||||
--exec "$SCREENSHOT_EDITOR" "$FILEPATH" || true
|
||||
;;
|
||||
copy)
|
||||
grim -g "$SELECTION" - | wl-copy --type image/png
|
||||
|
||||
@@ -70,10 +70,6 @@ title_from_file() {
|
||||
fi
|
||||
}
|
||||
|
||||
# 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
|
||||
# volume/brightness bar), so download progress never stacks like notifications.
|
||||
osd_progress() {
|
||||
@@ -150,9 +146,11 @@ download_url() {
|
||||
|
||||
# Best-effort: the download already succeeded, and under `set -e` a failed
|
||||
# toast would exit before the thumbnail cleanup below is ever scheduled.
|
||||
# `--` keeps mpv from parsing a leading-dash filename as an option; the path
|
||||
# is one discrete argument, so it never reaches a shell.
|
||||
omarchy-notification-send -g "Download complete" "$title" \
|
||||
-t 10000 --image "${preview:-$filepath}" \
|
||||
"${playback_exec_args[@]}" "$filepath" || true
|
||||
--exec mpv -- "$filepath" || true
|
||||
|
||||
# The shell loads the thumbnail into memory when the toast appears and never
|
||||
# re-reads the file, so the preview only has to outlive that load, not the
|
||||
|
||||
@@ -32,16 +32,17 @@ announce() {
|
||||
# likely to be delivered is the one most worth reporting.
|
||||
omarchy-notification-wait || return 1
|
||||
|
||||
# --exec-arg rather than a libnotify action: the shell runs clicks from its own
|
||||
# --exec rather than a libnotify action: the shell runs clicks from its own
|
||||
# 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.
|
||||
# discrete argv words, so a hostile process name can't be reparsed as a
|
||||
# command. --exec consumes the rest of the line, so it comes last.
|
||||
omarchy-notification-send \
|
||||
--urgency critical \
|
||||
--glyph "$CRASH_GLYPH" \
|
||||
--exec-arg omarchy-agent-crash --exec-arg "$pid" --exec-arg "$comm" --exec-arg "$exe" --exec-arg "$signal" \
|
||||
"Process crashed: $comm" \
|
||||
"Click to diagnose with AI"
|
||||
"Click to diagnose with AI" \
|
||||
--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"
|
||||
}
|
||||
|
||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||
|
||||
@@ -41,7 +41,7 @@ fi
|
||||
# The shell keeps the click command with the toast, so this oneshot can hand the
|
||||
# invitation over and exit instead of staying activated until it is answered.
|
||||
omarchy-notification-send -u critical -g "Pending Omarchy Migrations" "$message" \
|
||||
--exec-arg omarchy-launch-floating-terminal-with-presentation --exec-arg omarchy-migrate && exit 0
|
||||
--exec omarchy-launch-floating-terminal-with-presentation omarchy-migrate && exit 0
|
||||
|
||||
# Reached when the notification could not be handed off, so fall back to telling
|
||||
# the user in the terminal.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Send an Omarchy desktop notification
|
||||
# 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:args=[--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options] [--exec <program> [args...]]
|
||||
# omarchy:examples=omarchy notification send "Reminder" "5 minutes are up" -g
|
||||
|
||||
set -euo pipefail
|
||||
@@ -13,9 +13,29 @@ urgency="low"
|
||||
app_name="omarchy-action"
|
||||
image=
|
||||
exec_args=()
|
||||
exec_present=0
|
||||
args=()
|
||||
parsed_option_args=0
|
||||
|
||||
# Split off the click command first: everything after --exec is its argv. The
|
||||
# caller's shell has already tokenized those words into discrete arguments, and
|
||||
# the shell runs them as-is (never re-parsed by a shell), so a value carrying
|
||||
# untrusted data — a title, a filename — is only ever one argument and can never
|
||||
# become a command. --exec therefore consumes the rest of the line and must
|
||||
# come last.
|
||||
pre_args=()
|
||||
while (($# > 0)); do
|
||||
if [[ $1 == "--exec" ]]; then
|
||||
shift
|
||||
exec_args=("$@")
|
||||
exec_present=1
|
||||
break
|
||||
fi
|
||||
pre_args+=("$1")
|
||||
shift
|
||||
done
|
||||
set -- "${pre_args[@]}"
|
||||
|
||||
parse_omarchy_option() {
|
||||
case $1 in
|
||||
-g | --glyph)
|
||||
@@ -54,22 +74,6 @@ parse_omarchy_option() {
|
||||
parsed_option_args=2
|
||||
return 0
|
||||
;;
|
||||
--exec)
|
||||
# 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
|
||||
# One literal argument of the click command, taken verbatim (even a "-value").
|
||||
exec_args+=("$2")
|
||||
parsed_option_args=2
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
@@ -84,7 +88,7 @@ while (($# > 0)); do
|
||||
done
|
||||
|
||||
if (($# < 1)); then
|
||||
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]"
|
||||
echo "Usage: omarchy-notification-send [--app-name <app-name>] [-g <glyph>] [-u <low|normal|critical>] [--image <path-or-uri>] <headline> [description] [notify-send options] [--exec <program> [args...]]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -118,9 +122,21 @@ fi
|
||||
|
||||
# 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
|
||||
# libnotify action (which dies when the shell restarts).
|
||||
if ((exec_present)); then
|
||||
if ((${#exec_args[@]} == 0)); then
|
||||
echo "--exec needs a command: --exec <program> [args...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
# A single word with a space is almost always a whole command passed as one
|
||||
# quoted string — which would run a program literally named that. Splitting it
|
||||
# ourselves is exactly the injection we avoid, so reject it and point at the
|
||||
# unquoted form instead.
|
||||
if ((${#exec_args[@]} == 1)) && [[ ${exec_args[0]} == *[[:space:]]* ]]; then
|
||||
echo "--exec takes the command as separate words, not one quoted string." >&2
|
||||
echo "Write: --exec ${exec_args[0]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
# 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]')
|
||||
|
||||
@@ -72,7 +72,7 @@ announce() {
|
||||
|
||||
# Announcing is best-effort: the file is already delivered, and under `set -e`
|
||||
# a notification outage would otherwise kill the long-running receiver.
|
||||
omarchy-notification-send "${args[@]}" --exec-arg xdg-open --exec-arg "$path" || true
|
||||
omarchy-notification-send "${args[@]}" --exec xdg-open "$path" || true
|
||||
}
|
||||
|
||||
deliver() {
|
||||
|
||||
Reference in New Issue
Block a user