Validate the web app URL before anything fetches it
The scheme check ran after the interactive branch, which had already handed the URL to curl: a refused `file://` or `ftp://` URL was dereferenced first, the whole string went out to Google's favicon endpoint in a `domain=` query, and a fetch that happened to succeed left an orphan icon and a rebuilt icon cache behind. Validating immediately after normalization puts the refusal ahead of every dereference in both branches. A leading space also kept the URL out of the scheme test entirely. `normalize_webapp_url` saw no scheme, prefixed it, and ` file:///etc/passwd` became `https:// file:///etc/passwd`, which passes `^https?://`. The desktop `Exec` field is unquoted, so it splits back into two arguments, and `omarchy-launch-webapp` forwards everything after the first to the browser, where a bare argument is another URL to open. Refusing whitespace closes that without touching desktop-entry escaping. Scheme comparison is case-insensitive because schemes are: `HTTPS://example.com` installed before this check existed and has no reason to stop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Codex XHigh <noreply@openai.com>
This commit is contained in:
co-authored by
Claude Opus 5
Codex XHigh
parent
877f1e96ef
commit
9382410026
@@ -54,13 +54,21 @@ normalize_webapp_url() {
|
||||
printf '%s' "$url"
|
||||
}
|
||||
|
||||
# A space in the URL becomes a second desktop Exec argument, which
|
||||
# omarchy-launch-webapp hands to the browser as another URL to open. Schemes
|
||||
# are case-insensitive.
|
||||
require_http_url() {
|
||||
local url=$1
|
||||
if [[ $url =~ ^https?:// ]]; then
|
||||
return 0
|
||||
|
||||
if [[ $url =~ [[:space:]] ]]; then
|
||||
echo "Error: web app URL must not contain whitespace." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! ${url,,} =~ ^https?:// ]]; then
|
||||
echo "Error: web app URL must be http or https." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Error: web app URL must be http or https." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fetch_site_icon() {
|
||||
@@ -91,6 +99,7 @@ if (( $# < 3 )); then
|
||||
APP_NAME=$(gum input --prompt "Name> " --placeholder "My favorite web app")
|
||||
APP_URL=$(gum input --prompt "URL> " --placeholder "https://example.com")
|
||||
APP_URL=$(normalize_webapp_url "$APP_URL")
|
||||
require_http_url "$APP_URL"
|
||||
|
||||
# Try to fetch the site's icon automatically first.
|
||||
mkdir -p "$ICON_DIR"
|
||||
@@ -108,6 +117,7 @@ if (( $# < 3 )); then
|
||||
else
|
||||
APP_NAME="$1"
|
||||
APP_URL=$(normalize_webapp_url "$2")
|
||||
require_http_url "$APP_URL"
|
||||
ICON_REF="$3"
|
||||
CUSTOM_EXEC="$4" # Optional custom exec command
|
||||
MIME_TYPES="$5" # Optional mime types
|
||||
@@ -120,8 +130,6 @@ if [[ -z $APP_NAME || -z $APP_URL ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_http_url "$APP_URL"
|
||||
|
||||
if [[ -z $ICON_REF ]]; then
|
||||
ICON_VALUE=$(safe_icon_name "$APP_NAME")
|
||||
mkdir -p "$ICON_DIR"
|
||||
|
||||
@@ -58,3 +58,63 @@ for url in "javascript:alert(1)" "file:///etc/passwd" "data:text/html,hi" "ftp:/
|
||||
[[ ! -e $(desktop_for Bad) ]] || fail "webapp install does not write a desktop file for '$url'"
|
||||
done
|
||||
pass "webapp install refuses non-http(s) URLs"
|
||||
|
||||
# A leading space keeps the URL out of the scheme test, and the desktop Exec
|
||||
# field splits it back into two arguments the browser both opens.
|
||||
for url in " javascript:alert(1)" " file:///etc/passwd" "https://example.com data:text/html,hi"; do
|
||||
if install_webapp "Sneak" "$url" "webapp" >"$tmpdir/out" 2>"$tmpdir/err"; then
|
||||
fail "webapp install refuses whitespace in '$url'" "$(cat "$(desktop_for Sneak)")"
|
||||
fi
|
||||
grep -Fq 'must not contain whitespace' "$tmpdir/err" ||
|
||||
fail "webapp install names the whitespace refusal for '$url'" "$(cat "$tmpdir/err")"
|
||||
[[ ! -e $(desktop_for Sneak) ]] || fail "webapp install writes no desktop file for '$url'"
|
||||
done
|
||||
pass "webapp install refuses a URL carrying whitespace"
|
||||
|
||||
# Schemes are case-insensitive, and HTTPS://example.com installed before the
|
||||
# scheme test existed.
|
||||
if install_webapp "Upper" "HTTPS://example.com" "webapp" >"$tmpdir/out" 2>"$tmpdir/err"; then
|
||||
:
|
||||
else
|
||||
fail "webapp install accepts an uppercase scheme" "$(cat "$tmpdir/err")"
|
||||
fi
|
||||
grep -Fxq 'Exec=omarchy-launch-webapp HTTPS://example.com' "$(desktop_for Upper)" ||
|
||||
fail "webapp install keeps the uppercase scheme" "$(cat "$(desktop_for Upper)")"
|
||||
pass "webapp install accepts an uppercase http scheme"
|
||||
|
||||
# The interactive prompt fetches the site's icon, so a refused URL must be
|
||||
# refused before anything dereferences it.
|
||||
stubs="$tmpdir/stubs"
|
||||
mkdir -p "$stubs"
|
||||
|
||||
cat >"$stubs/gum" <<'GUM'
|
||||
#!/bin/bash
|
||||
count=$(cat "$GUM_COUNT" 2>/dev/null || echo 0)
|
||||
count=$((count + 1))
|
||||
printf '%s\n' "$count" >"$GUM_COUNT"
|
||||
sed -n "${count}p" "$GUM_ANSWERS"
|
||||
GUM
|
||||
|
||||
cat >"$stubs/curl" <<'CURL'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >>"$CURL_LOG"
|
||||
exit 1
|
||||
CURL
|
||||
|
||||
chmod +x "$stubs/gum" "$stubs/curl"
|
||||
|
||||
printf 'Evil\nfile:///etc/passwd\n' >"$tmpdir/answers"
|
||||
: >"$tmpdir/gum-count"
|
||||
: >"$tmpdir/curl-log"
|
||||
|
||||
if GUM_ANSWERS="$tmpdir/answers" GUM_COUNT="$tmpdir/gum-count" CURL_LOG="$tmpdir/curl-log" \
|
||||
PATH="$stubs:$PATH" HOME="$home" "$ROOT/bin/omarchy-webapp-install" \
|
||||
>"$tmpdir/out" 2>"$tmpdir/err"; then
|
||||
fail "interactive webapp install refuses a file: URL" "$(cat "$tmpdir/out")"
|
||||
fi
|
||||
grep -Fq 'must be http or https' "$tmpdir/err" ||
|
||||
fail "interactive webapp install names the scheme refusal" "$(cat "$tmpdir/err")"
|
||||
[[ ! -s $tmpdir/curl-log ]] ||
|
||||
fail "interactive webapp install refuses before fetching the URL" "$(cat "$tmpdir/curl-log")"
|
||||
[[ ! -e $(desktop_for Evil) ]] || fail "interactive webapp install writes no desktop file"
|
||||
pass "interactive webapp install refuses a bad URL before fetching it"
|
||||
|
||||
Reference in New Issue
Block a user