Files
omarchy/test/shell.d/plugin-add-test.sh
T
OmarchybotandClaude Opus 5 e713ff3166 Share the git URL check, and refuse the transports Omarchy does not clone from (backport of #8174)
Backport of the shared URL check (PR #8174, merged to quattro as 68ab12f7) onto
the v4-0-1 release branch, on top of the #8067 backport it follows.

omarchy-theme-install and omarchy-plugin-add both clone a URL a stranger can
choose, and each carried its own copy of the rule that refuses a git option or a
transport helper before cloning. The rule now lives in omarchy-git-url-check and
both callers ask it: two copies of a security check drift, and the second copy
arrived four months after the first only because someone went looking for it.

That rule was also enforcing half of what it described. <helper>::<address> is
one of two shapes git resolves a remote helper from -- it also runs
git-remote-<scheme> for <scheme>://<address> whenever the scheme is not one it
connects itself, so ext::sh -c id and ext://sh -c id reach the same helper while
only the first was refused. Nothing exploitable follows on a stock system:
protocol.ext.allow defaults to never, and the :// spelling hands git-remote-ext
a command name it cannot exec. But a third-party helper installed on PATH is
reachable through the scheme form alone, and a guard is worth more when it
enforces the rule it states.

The :// shape cannot be refused the way :: is, because it is also how every
legitimate URL arrives, so the scheme is checked against the transports git
still connects itself: ssh git git+ssh ssh+git http https ftp ftps file.
git+ssh and ssh+git are on that list because they are spelled like a helper and
read as plain ssh; leaving them off would refuse a URL that clones today. ext
and fd are off it deliberately. A single colon is always scp-style ssh and a
bare path is always a path, so neither needs constraining.

The check fails closed: the callers read a non-zero status as a refusal, so a
missing omarchy-git-url-check refuses the URL rather than waving it through.

Clean cherry-pick on top of the #8067 backport: every file is byte-identical to
quattro, so merging v4-0-1 into quattro resolves without a conflict. test/shell
passes: 189 files, including the one this adds. test/cli passes, so the new
command's metadata is well-formed. Exercised the check here: https, scp-style,
ssh, git+ssh and scp-style IPv6 all pass, while ext:: ext:// fd:: fd:// and a
leading-dash form are refused, as is an uppercase EXT:// spelling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DEMYa9UWtroz93DhMTtcV
2026-08-25 09:13:35 +02:00

178 lines
6.4 KiB
Bash

#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT
write_plugin() {
local dir="$1"
local id="$2"
local name="$3"
mkdir -p "$dir"
cat >"$dir/manifest.json" <<JSON
{
"schemaVersion": 1,
"id": "$id",
"name": "$name",
"version": "1.0.0",
"kinds": ["bar-widget"],
"entryPoints": { "barWidget": "Widget.qml" },
"barWidget": {
"displayName": "$name",
"category": "Test",
"allowMultiple": false
}
}
JSON
printf 'import QtQuick\nItem {}\n' >"$dir/Widget.qml"
}
stub_dir="$TMPDIR/stubs"
mkdir -p "$stub_dir"
cat >"$stub_dir/omarchy-shell" <<'STUB'
#!/bin/bash
exit 0
STUB
chmod +x "$stub_dir/omarchy-shell"
test_home="$TMPDIR/home"
write_plugin "$test_home/.config/omarchy/plugins/different-folder" "acme.same" "Installed"
incoming="$TMPDIR/incoming"
write_plugin "$incoming" "acme.same" "Incoming"
git -C "$incoming" init -q
git -C "$incoming" add .
git -C "$incoming" -c user.name=Test -c user.email=test@example.com commit -qm "Initial"
output=$(HOME="$test_home" OMARCHY_PATH="$ROOT" PATH="$stub_dir:$ROOT/bin:$PATH" \
omarchy-plugin-add "$incoming" --yes 2>&1) &&
fail "plugin add accepts an id already installed under another directory" "$output"
grep -qF "plugin id 'acme.same' is already used by" <<<"$output" ||
fail "plugin add explains the installed id collision" "$output"
[[ ! -e $test_home/.config/omarchy/plugins/acme.same ]] ||
fail "plugin add leaves a target behind after refusing a duplicate id"
pass "plugin add refuses an installed manifest id regardless of directory name"
# --- URL transport-helper guard -------------------------------------------
#
# The guard refuses git transport helpers (`<name>::…`) and option-shaped URLs
# before `git clone` runs, matching omarchy-theme-install. A git stub records
# whether clone was reached, so the guard is exercised with no network: reaching
# the stub proves a URL passed the guard; not reaching it proves the guard
# rejected the URL first.
guard_stubs="$TMPDIR/guard-stubs"
mkdir -p "$guard_stubs"
cat >"$guard_stubs/omarchy-shell" <<'STUB'
#!/bin/bash
exit 0
STUB
chmod +x "$guard_stubs/omarchy-shell"
clone_marker="$TMPDIR/git-clone-reached"
cat >"$guard_stubs/git" <<STUB
#!/bin/bash
if [[ \$1 == "clone" ]]; then
touch "$clone_marker"
exit 1
fi
exit 0
STUB
chmod +x "$guard_stubs/git"
# A gum stub that answers `gum input` with a caller-chosen value, so a test can
# drive any URL through the interactive prompt path.
cat >"$guard_stubs/gum" <<'STUB'
#!/bin/bash
if [[ $1 == "input" ]]; then
printf '%s\n' "$GUM_INPUT_VALUE"
fi
STUB
chmod +x "$guard_stubs/gum"
add_url() {
HOME="$test_home" OMARCHY_PATH="$ROOT" PATH="$guard_stubs:$ROOT/bin:$PATH" \
omarchy-plugin-add "$1" --yes 2>&1
}
# Transport helpers reach the guard, are named as such, and never reach clone.
for bad in "ext::sh -c touch /tmp/omarchy-guard-test" "fd::17"; do
rm -f "$clone_marker"
output=$(add_url "$bad") &&
fail "plugin add rejects a transport-helper URL: $bad" "$output"
grep -qF "names a git option or transport helper" <<<"$output" ||
fail "plugin add names the transport-helper rejection: $bad" "$output"
[[ ! -e $clone_marker ]] ||
fail "plugin add reached git clone for a transport-helper URL: $bad"
done
pass "plugin add rejects transport-helper URLs before cloning"
# The `://` spelling of the same thing: git resolves git-remote-<scheme> for any
# scheme it does not implement itself, so `ext::` and `ext://` reach the same
# helper and both have to be refused.
for bad in "ext://sh -c id" "gcrypt://example.com/x"; do
rm -f "$clone_marker"
output=$(add_url "$bad") &&
fail "plugin add rejects a transport-scheme URL: $bad" "$output"
grep -qF "which Omarchy does not clone from" <<<"$output" ||
fail "plugin add names the transport-scheme rejection: $bad" "$output"
[[ ! -e $clone_marker ]] ||
fail "plugin add reached git clone for a transport-scheme URL: $bad"
done
pass "plugin add rejects transport-scheme URLs before cloning"
# Option-shaped URLs on argv are refused before clone — by the option parser
# (`-*` falls to "unknown add option"), not the guard. The guard's own
# leading-dash arm is only reachable through the interactive gum prompt and is
# exercised separately below.
for bad in "-oProxyCommand=x" "--upload-pack=x"; do
rm -f "$clone_marker"
output=$(add_url "$bad") &&
fail "plugin add rejects an option-shaped URL: $bad" "$output"
[[ ! -e $clone_marker ]] ||
fail "plugin add reached git clone for an option-shaped URL: $bad"
done
pass "plugin add rejects option-shaped URLs before cloning"
# The guard's leading-dash arm is only reachable through `gum input`: argv
# dashes die in the option parser first. interactive() requires a TTY on stdin
# and stdout, so run this one case on a pty via util-linux `script -qec` (the
# suite's existing pty idiom); gum itself is stubbed, so no rendering happens.
# Probe script's util-linux syntax first and skip cleanly where it is missing.
if script -qec true /dev/null >/dev/null 2>&1; then
rm -f "$clone_marker"
status=0
raw=$(GUM_INPUT_VALUE="-oProxyCommand=x" HOME="$test_home" OMARCHY_PATH="$ROOT" \
PATH="$guard_stubs:$ROOT/bin:$PATH" \
script -qec "omarchy-plugin-add --yes" /dev/null) || status=$?
output=$(tr -d '\r' <<<"$raw")
(( status != 0 )) ||
fail "plugin add rejects an option-shaped URL from the gum prompt" "$output"
grep -qF "names a git option or transport helper" <<<"$output" ||
fail "plugin add names the guard rejection for the gum-prompt URL" "$output"
[[ ! -e $clone_marker ]] ||
fail "plugin add reached git clone for an option-shaped gum-prompt URL"
pass "plugin add guard rejects an option-shaped URL from the interactive prompt"
else
pass "script -qec unavailable; skipping the interactive gum-prompt guard case"
fi
# Legitimate URL forms pass the guard and reach git clone (stubbed, no network).
for good in \
"https://github.com/acme/omarchy-weather.git" \
"git@github.com:acme/repo.git" \
"ssh://git@github.com/acme/repo.git" \
"git@[2001:db8::1]:org/repo.git"; do
rm -f "$clone_marker"
output=$(add_url "$good") || true
! grep -qF "names a git option or transport helper" <<<"$output" ||
fail "plugin add wrongly rejected a legitimate URL: $good" "$output"
[[ -e $clone_marker ]] ||
fail "plugin add did not reach git clone for a legitimate URL: $good" "$output"
done
pass "plugin add lets legitimate git URLs reach git clone"