From 21cbbf819490c3017f32e66298e12d0c3ceabd30 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sun, 23 Aug 2026 14:41:56 -0400 Subject: [PATCH] Recognize --exec only after the positionals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A greedy pre-scan matched the first --exec token anywhere on the line, so an untrusted headline or description literally equal to "--exec" could be mistaken for the delimiter. It always failed closed (the following token became a dash-option argv[0] that parseExecArgv rejects), but it was a latent footgun surfaced by an adversarial review. Detect --exec only in the trailing-option loop, after the headline/description positionals are captured. A headline that is the string "--exec" is now kept as text and the real trailing --exec still wins. The residual — a description exactly equal to "--exec" losing its click action — is an inherent, harmless CLI ambiguity for a value identical to the delimiter. --- bin/omarchy-notification-send | 33 ++++++++++---------------- test/shell.d/notification-send-test.sh | 12 ++++++++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/bin/omarchy-notification-send b/bin/omarchy-notification-send index 613f34d7..272c9592 100755 --- a/bin/omarchy-notification-send +++ b/bin/omarchy-notification-send @@ -17,25 +17,6 @@ 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) @@ -101,7 +82,19 @@ if (($# > 0)) && [[ $1 != -* ]]; then fi while (($# > 0)); do - if parse_omarchy_option "$@"; then + if [[ $1 == "--exec" ]]; then + # --exec consumes the rest of the line as the click command's argv. The + # caller's shell already tokenized those words into discrete arguments, and + # the shell runs them as-is (never re-parsed), so untrusted data in an + # argument is only ever one argument and can never become a command. + # Detected only here, after the headline/description positionals are + # captured, so an untrusted headline that is literally "--exec" is taken as + # text and can't be mistaken for the delimiter. --exec therefore comes last. + shift + exec_args=("$@") + exec_present=1 + break + elif parse_omarchy_option "$@"; then shift "$parsed_option_args" else args+=("$1") diff --git a/test/shell.d/notification-send-test.sh b/test/shell.d/notification-send-test.sh index 913163c1..1819032e 100644 --- a/test/shell.d/notification-send-test.sh +++ b/test/shell.d/notification-send-test.sh @@ -85,3 +85,15 @@ if send "Head" --exec 2>/dev/null; then fail "notification wrapper rejects --exec with no command" fi pass "notification wrapper rejects --exec with no command" + +# --exec is recognized only after the positionals, so an untrusted headline or +# description that is literally "--exec" is taken as text and cannot be mistaken +# for the delimiter (the real --exec later still wins). +: >"$args_file" +send "--exec" "a body" --image /tmp/i.png --exec mpv -- /tmp/v.mp4 >/dev/null +argv_hint=$(grep -- "--hint=string:omarchy-exec-argv:" "$args_file") +argv_json=${argv_hint#--hint=string:omarchy-exec-argv:} +[[ $(jq -c '.' <<<"$argv_json") == '["mpv","--","/tmp/v.mp4"]' ]] || fail "notification wrapper ignores a --exec-looking headline as the delimiter" "$argv_json" +grep -qx -- "--exec" "$args_file" || fail "notification wrapper keeps a --exec-looking headline as text" +grep -q 'image-path:/tmp/i.png' "$args_file" || fail "notification wrapper still parses options after a --exec-looking headline" +pass "notification wrapper does not treat a --exec-looking positional as the delimiter"