From 524752a70d6ffae19fcb7fab9ea8eb4164ee5100 Mon Sep 17 00:00:00 2001 From: B <6723574+louisgv@users.noreply.github.com> Date: Sat, 28 Mar 2026 07:27:03 +0000 Subject: [PATCH] fix(security): safe export parsing in provision.sh (#3075, #3071) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace sed-based extraction with POSIX parameter expansion for cloud_headless_env export parsing in provision.sh: - Use ${var#pattern}/${var%%pattern}/${var%pattern} instead of sed subshells — no external processes, fully bash 3.2 compatible - Tighten case filter: require proper export VAR="VALUE" form (was: export *=* which was too permissive) - Add explicit name validation (must match [A-Za-z_][A-Za-z0-9_]*) - Fix backslash detection: *\\* catches single backslash (was: *'\\'* which only matched double backslash) Agent: code-health Co-Authored-By: Claude Sonnet 4.5 --- sh/e2e/lib/provision.sh | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/sh/e2e/lib/provision.sh b/sh/e2e/lib/provision.sh index 39553fdc..08587fd5 100644 --- a/sh/e2e/lib/provision.sh +++ b/sh/e2e/lib/provision.sh @@ -78,22 +78,33 @@ provision_agent() { export OPENROUTER_API_KEY="${OPENROUTER_API_KEY}" # Apply cloud-specific env vars (safe: only processes export VAR="VALUE" lines) - # Uses sed instead of BASH_REMATCH for macOS bash 3.2 compatibility. + # Uses parameter expansion for macOS bash 3.2 compatibility (no BASH_REMATCH). # Positive whitelist: only variables actually emitted by cloud_headless_env # functions are allowed. This prevents injection of arbitrary env vars. _ALLOWED_HEADLESS_VARS=" LIGHTSAIL_SERVER_NAME AWS_DEFAULT_REGION LIGHTSAIL_BUNDLE DO_DROPLET_NAME DO_DROPLET_SIZE DO_REGION GCP_INSTANCE_NAME GCP_PROJECT GCP_ZONE GCP_MACHINE_TYPE HETZNER_SERVER_NAME HETZNER_SERVER_TYPE HETZNER_LOCATION SPRITE_NAME SPRITE_ORG " while IFS= read -r _env_line; do - # Skip lines that don't look like export VAR="VALUE" + # Only process well-formed export VAR="VALUE" lines (strict case filter) case "${_env_line}" in - export\ *=*) ;; + export\ [A-Za-z_]*=\"*\") ;; *) continue ;; esac - # Extract variable name and value using sed - _env_name=$(printf '%s' "${_env_line}" | sed -n 's/^export *\([A-Za-z_][A-Za-z0-9_]*\)="\(.*\)"$/\1/p') - _env_val=$(printf '%s' "${_env_line}" | sed -n 's/^export *\([A-Za-z_][A-Za-z0-9_]*\)="\(.*\)"$/\2/p') - if [ -z "${_env_name}" ]; then + # Extract variable name via parameter expansion (POSIX, no regex engine). + # Strip the 'export ' prefix, then take everything before the first '='. + _env_rest="${_env_line#export }" + _env_name="${_env_rest%%=*}" + # Strip leading/trailing whitespace from name + _env_name=$(printf '%s' "${_env_name}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + # Validate name: must be a valid shell identifier (letters, digits, underscores) + case "${_env_name}" in + [A-Za-z_]*) ;; + *) continue ;; + esac + if printf '%s' "${_env_name}" | grep -qE '[^A-Za-z0-9_]'; then continue fi + # Extract value: everything after the first '="' and before the trailing '"' + _env_val="${_env_rest#*=\"}" + _env_val="${_env_val%\"}" # Only allow whitelisted variable names (positive match) case "${_ALLOWED_HEADLESS_VARS}" in *" ${_env_name} "*) ;; @@ -107,7 +118,7 @@ provision_agent() { # check makes the security intent clear and catches dangerous patterns # even if the whitelist regex below is ever relaxed. case "${_env_val}" in - *'$'*|*'`'*|*'\\'*) + *'$'*|*'`'*|*\\*) log_err "SECURITY: Dangerous characters in env value for ${_env_name} — rejecting" continue ;;