From 4d5fa9cf588290078adf390eafe3b1b0ef41a304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20B=C3=BChner?= Date: Fri, 24 Jul 2026 16:40:52 +0200 Subject: [PATCH 1/3] fix: extension prefix collison --- install-extensions.sh | 55 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index 7e98118..2d3a530 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -34,6 +34,31 @@ declare -A _DIR_LISTING_CACHE # extensions can be downloaded directly without the fallback. _RESOLVED_FILENAME_VERSION="" +# Guard against extension-name prefix collisions such as "wps" vs "geofence-wps" +# (GEOS-12131). Given a candidate filename and the extension name we are looking +# for, return 0 when the file's version segment (the part between "geoserver-" +# and "-${extension}-plugin.zip") is a plausible version — i.e. non-empty and +# does NOT contain a hyphen followed by a lowercase letter or underscore. +# GeoServer version suffixes (-SNAPSHOT, -RC*, -M*) all start with an uppercase +# letter, while extension names are validated as [a-z0-9_-]+, so any "-[a-z_]" +# segment inside the version part indicates a different extension leaking into +# the greedy glob (e.g. "2.28.4-geofence" when looking for extension "wps"). +# The caller must have already verified that the filename ends with +# "-${extension}-plugin.zip". +function _extension_filename_matches() { + local filename="$1" + local extension="$2" + local base middle + base=$(basename "$filename") + middle="${base#geoserver-}" + middle="${middle%-${extension}-plugin.zip}" + [ -z "$middle" ] && return 1 + case "$middle" in + *-[a-z_]*) return 1 ;; + esac + return 0 +} + function download_extension() { URL=$1 EXTENSION=$2 @@ -90,7 +115,7 @@ function download_extension() { # Parse HTML to extract href matching the extension plugin pattern LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\.zip\)".*/\1/p' | head -n 1 || true) - + # Basic sanity checks before using the discovered value if [ -n "${FILE}" ]; then # Security: reject absolute URLs or paths (only accept simple filenames) @@ -99,7 +124,7 @@ function download_extension() { FILE="" fi fi - + if [ -n "${FILE}" ]; then # Ensure we only have a bare filename FILE=$(basename "${FILE}") @@ -109,7 +134,16 @@ function download_extension() { FILE="" fi fi - + + # Reject prefix collisions (GEOS-12131): the sed capture is greedy and + # could pick e.g. "geoserver-3.0-SNAPSHOT-geofence-wps-plugin.zip" when + # searching for extension "wps". _extension_filename_matches enforces + # that the version segment is a plausible version. + if [ -n "${FILE}" ] && ! _extension_filename_matches "${FILE}" "${EXTENSION}"; then + echo "Discovered candidate '${FILE}' collides with a different extension prefix; skipping." + FILE="" + fi + if [ -n "${FILE}" ]; then echo "Found candidate file: ${FILE}" NEW_URL="${BASE_URL}/${FILE}" @@ -184,9 +218,18 @@ for EXTENSION in $(echo "${STABLE_EXTENSIONS},${COMMUNITY_EXTENSIONS}" | tr ',' echo "WARNING: Skipping invalid extension name: ${EXTENSION}" >&2 continue fi - # Find downloaded plugin (handles both expected and discovered filenames) - ADDITIONAL_LIB=$(ls -1 "${ADDITIONAL_LIBS_DIR%/}"/geoserver-*-${EXTENSION}-plugin.zip 2>/dev/null | head -n 1 || true) - [ -e "$ADDITIONAL_LIB" ] || continue + # Find downloaded plugin (handles both expected and discovered filenames). + # Guards against prefix collisions (GEOS-12131): the glob geoserver-*-${EXT}-plugin.zip + # is greedy, so for EXT=wps it would also match geoserver-*-geofence-wps-plugin.zip + # and — depending on sort order — install the wrong archive. + ADDITIONAL_LIB="" + for candidate in "${ADDITIONAL_LIBS_DIR%/}"/geoserver-*-"${EXTENSION}"-plugin.zip; do + [ -e "$candidate" ] || continue + _extension_filename_matches "$candidate" "$EXTENSION" || continue + ADDITIONAL_LIB="$candidate" + break + done + [ -n "$ADDITIONAL_LIB" ] && [ -e "$ADDITIONAL_LIB" ] || continue if [[ $ADDITIONAL_LIB == *.zip ]]; then unzip -q -o -d "${GEOSERVER_LIB_DIR}" "${ADDITIONAL_LIB}" "*.jar" From 219431fd4324b5e98f82d3cefe9aab1e3c85a660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20B=C3=BChner?= Date: Wed, 29 Jul 2026 13:20:32 +0200 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- install-extensions.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/install-extensions.sh b/install-extensions.sh index 2d3a530..1a1eb5c 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -48,6 +48,7 @@ _RESOLVED_FILENAME_VERSION="" function _extension_filename_matches() { local filename="$1" local extension="$2" + [[ "$extension" =~ ^[a-z0-9_-]+$ ]] || return 1 local base middle base=$(basename "$filename") middle="${base#geoserver-}" From b4e22e14b85c28412d3f2384c76eef1da826164d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nils=20B=C3=BChner?= Date: Wed, 29 Jul 2026 13:20:57 +0200 Subject: [PATCH 3/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- install-extensions.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/install-extensions.sh b/install-extensions.sh index 1a1eb5c..96f1f1a 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -141,8 +141,23 @@ function download_extension() { # searching for extension "wps". _extension_filename_matches enforces # that the version segment is a plausible version. if [ -n "${FILE}" ] && ! _extension_filename_matches "${FILE}" "${EXTENSION}"; then - echo "Discovered candidate '${FILE}' collides with a different extension prefix; skipping." + echo "Discovered candidate '${FILE}' collides with a different extension prefix; searching for another match." FILE="" + while IFS= read -r candidate; do + [ -n "${candidate}" ] || continue + # Security: reject absolute URLs or paths (only accept simple filenames) + if echo "${candidate}" | grep -qE '://' || echo "${candidate}" | grep -q '/'; then + continue + fi + candidate=$(basename "${candidate}") + # Validate filename matches expected pattern: geoserver---plugin.zip + if ! echo "${candidate}" | grep -qE '^geoserver-[^-][^/]*-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\.zip$'; then + continue + fi + _extension_filename_matches "${candidate}" "${EXTENSION}" || continue + FILE="${candidate}" + break + done < <(echo "${LISTING_ONE}" | grep -oE 'href="[^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\.zip"' | sed 's/^href="//;s/"$//' || true) fi if [ -n "${FILE}" ]; then