From 0b672e13a60eea32743578a7d88d29b318940662 Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 14 Apr 2026 19:33:25 +0200 Subject: [PATCH] Validate and sanitize Composer vendor directory in PHP service's entrypoint script --- services/php/docker-entrypoint.sh | 56 ++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/services/php/docker-entrypoint.sh b/services/php/docker-entrypoint.sh index 8c07eaf2..e19ffb28 100644 --- a/services/php/docker-entrypoint.sh +++ b/services/php/docker-entrypoint.sh @@ -11,6 +11,28 @@ AUTO_COMPOSER_INSTALL="${AUTO_COMPOSER_INSTALL:-true}" log() { printf "[entrypoint] %s\n" "$*"; } +vendor_sanity_ok() { + dir="$1" + autoload_file="$dir/vendor/autoload.php" + aws_s3_api_file="$dir/vendor/aws/aws-sdk-php/src/data/s3/2006-03-01/api-2.json.php" + + if [ ! -f "$autoload_file" ]; then + return 1 + fi + + if [ -f "$dir/composer.lock" ] && [ "$dir/composer.lock" -nt "$autoload_file" ]; then + log "composer.lock is newer than vendor/autoload.php in $dir" + return 1 + fi + + if [ -f "$aws_s3_api_file" ] && ! php -l "$aws_s3_api_file" >/dev/null 2>&1; then + log "Vendor sanity check failed for $aws_s3_api_file" + return 1 + fi + + return 0 +} + wait_for_redis() { db_target="${CONFIG_DB_TARGET:-live}" if [ "$db_target" = "debug" ]; then @@ -38,17 +60,23 @@ wait_for_redis() { return 0 fi fi - sleep 1; i=$((i+1)) + sleep 1 + i=$((i+1)) done log "ERROR: Timed out waiting for Redis at ${host}:${port}" return 1 } wait_for_file() { - target="$1"; timeout="${2:-120}"; i=0 + target="$1" + timeout="${2:-120}" + i=0 while [ "$i" -lt "$timeout" ]; do - if [ -e "$target" ]; then return 0; fi - sleep 1; i=$((i+1)) + if [ -e "$target" ]; then + return 0 + fi + sleep 1 + i=$((i+1)) done return 1 } @@ -67,48 +95,44 @@ with_install_lock() { install_if_needed() { dir="$1" if [ -f "$dir/composer.json" ]; then - if [ ! -f "$dir/vendor/autoload.php" ]; then + if ! vendor_sanity_ok "$dir"; then + if [ -d "$dir/vendor" ]; then + log "Removing invalid vendor tree in $dir before reinstall" + rm -rf "$dir/vendor" + fi log "Installing Composer deps in $dir ..." - # Ensure log directory exists mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true - # Run install and log output if ! COMPOSER_ALLOW_SUPERUSER=1 composer install \ --no-dev --prefer-dist --optimize-autoloader --no-interaction \ -d "$dir" 2>&1 | tee -a "$LOG_FILE"; then log "ERROR: composer install failed in $dir. See $LOG_FILE" exit 1 fi - # Verify autoload was created if [ ! -f "$dir/vendor/autoload.php" ]; then log "ERROR: autoload.php still missing after install in $dir. See $LOG_FILE" exit 1 fi - # Best-effort permissions fix (ignore errors on non-Linux filesystems) chown -R www-data:www-data "$dir/vendor" 2>/dev/null || true else - log "vendor already present in $dir — skipping" + log "vendor already present and sane in $dir - skipping" fi fi } -# Optionally perform auto-install (only on the designated container) if [ "$AUTO_COMPOSER_INSTALL" = "true" ]; then - # Wait for the bind mount and composer.json to appear (common on Windows/macOS) if ! wait_for_file "$APP_DIR/composer.json" 120; then - log "WARNING: $APP_DIR/composer.json not found after waiting — skipping auto-install" + log "WARNING: $APP_DIR/composer.json not found after waiting - skipping auto-install" else with_install_lock install_if_needed "$APP_DIR" fi - # Module (optional) if [ -f "$MODULE_DIR/composer.json" ]; then with_install_lock install_if_needed "$MODULE_DIR" fi else - log "AUTO_COMPOSER_INSTALL=false — skipping Composer auto-install" + log "AUTO_COMPOSER_INSTALL=false - skipping Composer auto-install" fi -# Wait for Redis before starting PHP-FPM (if host is defined) if [ -n "${REDIS_CONFIG_HOST:-}" ]; then wait_for_redis fi