From 5efc506197a08950de4496b68fee746a0cce1057 Mon Sep 17 00:00:00 2001 From: bmottin Date: Sun, 13 Sep 2026 17:20:43 -0400 Subject: [PATCH] fix(url-guard): restore the SSRF guard on bash 3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `${1,,}` is bash 4.0+. macOS ships bash 3.2 as /bin/bash, where it raises "bad substitution"; the subshell then exited 1 — read as "not local" — so `url-guard.sh host` returned rc 0 for localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, 169.254.169.254 and metadata.google.internal. The guard failed OPEN on every Mac, and url-guard.test.sh recorded it as 13 "got[0] want[2]". `shopt -s nocasematch` (bash 3.1+) keeps both the ASCII-only folding that LC_ALL=C gives and the no-fork property the lowercase expansion had. Verified on /bin/bash 3.2: the ten local/private/metadata targets now return rc 2 (case-folded variants included), legitimate hosts still rc 0. --- lib/url-guard.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/url-guard.sh b/lib/url-guard.sh index 587c01b..7e59f44 100644 --- a/lib/url-guard.sh +++ b/lib/url-guard.sh @@ -41,9 +41,14 @@ _rest_charset_ok() ( LC_ALL=C; case "$1" in # Literal local/private/metadata targets. This is a LITERAL check, not a DNS # one: it stops the obvious, not a hostname that resolves inward. _host_is_local() ( LC_ALL=C - # ${1,,} not tr: no fork, and no SC2018/SC2019 noise. Safe because the - # charset guard has already run — the string is [A-Za-z0-9.-] by here. - case "${1,,}" in + # `nocasematch` not ${1,,}: the lowercase expansion is bash 4.0+, and macOS + # ships bash 3.2 as /bin/bash — there it raised "bad substitution" and the + # subshell exited 1, i.e. "not local", so EVERY local/private/metadata host + # was allowed through. Keeps the no-fork property the lowercase form had. + # Safe because the charset guard has already run — the string is + # [A-Za-z0-9.-] by here, and LC_ALL=C keeps the folding ASCII-only. + shopt -s nocasematch + case "$1" in localhost|*.localhost|*.local|0.0.0.0|broadcasthost) exit 0 ;; 127.*|10.*|169.254.*|192.168.*) exit 0 ;; 172.1[6-9].*|172.2[0-9].*|172.3[01].*) exit 0 ;;