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
80 lines
3.1 KiB
Bash
Executable File
80 lines
3.1 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
# omarchy-git-url-check decides which URLs omarchy-theme-install and
|
|
# omarchy-plugin-add are willing to hand to `git clone`. git resolves a remote
|
|
# helper -- a program it runs at clone time -- from exactly two URL shapes,
|
|
# `<helper>::<address>` and `<scheme>://<address>`, so those are the two shapes
|
|
# asserted here, alongside every legitimate form a user is likely to paste.
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
check() {
|
|
"$ROOT/bin/omarchy-git-url-check" "$@" 2>&1
|
|
}
|
|
|
|
# `<helper>::<address>`, the shape that runs a program. `ext::` is the dangerous
|
|
# one: git runs the rest as a shell command once protocol.ext.allow permits it.
|
|
for url in "ext::sh -c id" "fd::0,1" "gcrypt::x" "a+b::x" "a.b::x" "a-b::x" "1::x"; do
|
|
output=$(check "$url") &&
|
|
fail "omarchy-git-url-check refuses the transport helper '$url'" "$output"
|
|
grep -qF "names a git option or transport helper" <<<"$output" ||
|
|
fail "omarchy-git-url-check names the helper rejection for '$url'" "$output"
|
|
done
|
|
|
|
pass "a <helper>::<address> URL is refused"
|
|
|
|
# `<scheme>://<address>`, the shape #8067 left open: git looks up
|
|
# git-remote-<scheme> for any scheme it does not implement itself, so an
|
|
# allowlist is the only form of this check that holds.
|
|
for url in "ext://sh -c id" "fd://17" "gcrypt://example.com/x" "zzz://a" "ZZZ://a" "HTTPS://github.com/a/b"; do
|
|
output=$(check "$url") &&
|
|
fail "omarchy-git-url-check refuses the '$url' transport" "$output"
|
|
grep -qF "which Omarchy does not clone from" <<<"$output" ||
|
|
fail "omarchy-git-url-check names the transport rejection for '$url'" "$output"
|
|
done
|
|
|
|
pass "a <scheme>://<address> URL outside git's own transports is refused"
|
|
|
|
# A leading dash is an option to git, not a URL.
|
|
for url in "-x" "--upload-pack=touch /tmp/pwned" "-oProxyCommand=x"; do
|
|
output=$(check "$url") &&
|
|
fail "omarchy-git-url-check refuses the option '$url'" "$output"
|
|
done
|
|
|
|
pass "a URL shaped like a git option is refused"
|
|
|
|
output=$(check "") && fail "omarchy-git-url-check refuses an empty URL" "$output"
|
|
output=$(check) && fail "omarchy-git-url-check refuses a missing URL" "$output"
|
|
|
|
pass "an empty URL is refused"
|
|
|
|
# Everything a user actually pastes. The scp-style forms carry a single colon,
|
|
# which git never reads as a helper, and the IPv6 host carries `::` inside
|
|
# brackets rather than at the start.
|
|
for url in \
|
|
"https://github.com/acme/omarchy-weather.git" \
|
|
"http://example.com/a/b.git" \
|
|
"https://user:token@github.com/acme/repo.git" \
|
|
"ssh://git@github.com/acme/repo.git" \
|
|
"ssh://git@[2001:db8::1]:22/org/repo.git" \
|
|
"git://example.com/repo.git" \
|
|
"git+ssh://git@example.com/acme/repo.git" \
|
|
"ssh+git://git@example.com/acme/repo.git" \
|
|
"ftp://example.com/repo.git" \
|
|
"ftps://example.com/repo.git" \
|
|
"file:///home/me/repo" \
|
|
"git@github.com:acme/repo.git" \
|
|
"git@[2001:db8::1]:org/repo.git" \
|
|
"host:-s/foo.git" \
|
|
"/home/me/repo" \
|
|
"./repo" \
|
|
"../repo" \
|
|
"repo"; do
|
|
output=$(check "$url") ||
|
|
fail "omarchy-git-url-check accepts the legitimate URL '$url'" "$output"
|
|
done
|
|
|
|
pass "the URL forms a user pastes are accepted"
|