Files
dokku/plugins/openresty-vhosts/core-post-extract
Jose Diaz-Gonzalez a70728f61e fix: sanitize openresty include filenames to prevent eval injection
Add defense-in-depth sanitization for OpenResty include files to prevent
OS command injection via malicious filenames that break shell quoting in eval.

- Add filename validation in core-post-extract using regex [^a-zA-Z0-9_.-]
- Validate both http-includes and location-includes paths
- Abort deploy via dokku_log_fail on unsafe filenames
- Skip non-regular files (symlinks, directories) during extraction
- Add security regression test with unsafe filename containing space
- Keep existing guards in docker-args-process-deploy as belt-and-suspenders
- Update documentation to clarify allowed filename characters

Addresses CVSS 9.9 vulnerability where filenames like poc'$(cmd)'x.conf
could escape shell quoting and execute arbitrary commands during deploy.
2026-05-09 12:20:50 -04:00

107 lines
4.8 KiB
Bash
Executable File

#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/openresty-vhosts/internal-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
fn-openresty-vhosts-copy-includes-from-source() {
declare desc="copy regular include files from the app repo; abort on unsafe filenames"
declare SRC_DIR="$1" DEST_DIR="$2"
local file filename
[[ -d "$SRC_DIR" ]] || return 0
for file in "$SRC_DIR"/*; do
[[ -e "$file" ]] || continue
filename="$(basename "$file")"
if [[ "$filename" =~ [^a-zA-Z0-9_.-] ]]; then
dokku_log_fail "Aborting deploy: include file has unsafe filename: $filename"
fi
[[ -L "$file" ]] && continue
[[ -f "$file" ]] || continue
cp -f "$file" "$DEST_DIR/"
done
}
fn-openresty-vhosts-sanitize-extracted-includes() {
declare desc="post-extract: abort on unsafe filenames; remove non-regular files"
declare TARGET_DIR="$1"
local file filename
[[ -d "$TARGET_DIR" ]] || return 0
for file in "$TARGET_DIR"/*; do
[[ -e "$file" ]] || continue
filename="$(basename "$file")"
if [[ "$filename" =~ [^a-zA-Z0-9_.-] ]]; then
dokku_log_fail "Aborting deploy: include file has unsafe filename: $filename"
fi
if [[ -L "$file" ]] || [[ ! -f "$file" ]]; then
rm -rf "$file"
fi
done
}
fn-openresty-vhosts-copy-from-image() {
declare APP="$1" IMAGE_NAME="$2"
local CONF_PATH="openresty/http-includes"
local LOCATION_CONF_PATH="openresty/http-location-includes"
mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP"
find "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/" -maxdepth 1 -name 'openresty-http-includes.*' -type d -exec rm -r {} +
copy_dir_from_image "$IMAGE_NAME" "$CONF_PATH" "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" 2>/dev/null || true
fn-openresty-vhosts-sanitize-extracted-includes "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID"
if [[ ! -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID" ]]; then
touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing"
fi
find "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/" -maxdepth 1 -name 'openresty-location-includes.*' -type d -exec rm -r {} +
copy_dir_from_image "$IMAGE_NAME" "$LOCATION_CONF_PATH" "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID" 2>/dev/null || true
fn-openresty-vhosts-sanitize-extracted-includes "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID"
if [[ ! -f "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID" ]]; then
touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID.missing"
fi
}
fn-openresty-vhosts-copy-from-directory() {
declare APP="$1" SOURCECODE_WORK_DIR="$2"
local CONF_PATH="openresty/http-includes"
local LOCATION_CONF_PATH="openresty/http-location-includes"
if [[ -d "$CONF_PATH" ]]; then
pushd "$SOURCECODE_WORK_DIR" >/dev/null
find "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/" -maxdepth 1 -name 'openresty-http-includes.*' -type d -exec rm -r {} +
mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID/"
fn-openresty-vhosts-copy-includes-from-source "$CONF_PATH" "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID"
popd &>/dev/null || pushd "/tmp" >/dev/null
else
touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-http-includes.$DOKKU_PID.missing"
fi
if [[ -d "$LOCATION_CONF_PATH" ]]; then
pushd "$SOURCECODE_WORK_DIR" >/dev/null
find "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/" -maxdepth 1 -name 'openresty-location-includes.*' -type d -exec rm -r {} +
mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID/"
fn-openresty-vhosts-copy-includes-from-source "$LOCATION_CONF_PATH" "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID"
popd &>/dev/null || pushd "/tmp" >/dev/null
else
touch "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP/openresty-location-includes.$DOKKU_PID.missing"
fi
}
trigger-openresty-vhosts-core-post-extract() {
declare desc="openresty-vhosts post-extract plugin trigger"
declare trigger="post-extract"
declare APP="$1" SOURCECODE_WORK_DIR="$2"
local app_source_image
mkdir -p "${DOKKU_LIB_ROOT}/data/openresty-vhosts/app-$APP"
app_source_image="$(plugn trigger git-get-property "$APP" "source-image")"
if [[ -n "$app_source_image" ]]; then
fn-openresty-vhosts-copy-from-image "$APP" "$app_source_image"
else
fn-openresty-vhosts-copy-from-directory "$APP" "$SOURCECODE_WORK_DIR"
fi
}
trigger-openresty-vhosts-core-post-extract "$@"