From 925cd3a1fc98f6f2e32e4521d78dbd535fd0bbfa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 5 Dec 2025 13:32:02 -0600 Subject: [PATCH 01/15] Refactor Laravel migration script to improve isolation handling and database connection checks - Removed migration isolation argument from test-db-connection script. - Introduced a helper function for running migrations per database, enhancing clarity and reusability. - Updated migration flag assembly logic to better manage isolation settings. - Improved error handling and logging for database connection issues. --- .../entrypoint.d/50-laravel-automations.sh | 75 ++++++++++++++----- .../lib/laravel/test-db-connection.php | 33 ++------ 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 1e86e034..9cd349ca 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -53,8 +53,6 @@ fi ############################################################################ artisan_migrate() { - migrate_flags="" - debug_log "Starting migrations (isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" echo "🚀 Clearing Laravel cache before attempting migrations..." @@ -73,7 +71,8 @@ artisan_migrate() { ;; esac - # Build migration flags (used for all databases) + # Determine if isolation is intended to be used + isolation_enabled="false" if [ "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" = "true" ]; then # Isolation only works in default mode if [ "$AUTORUN_LARAVEL_MIGRATION_MODE" != "default" ]; then @@ -87,9 +86,12 @@ artisan_migrate() { return 1 fi - migrate_flags="$migrate_flags --isolated" + isolation_enabled="true" fi + # Start assembling migration flags + migrate_flags="" + if [ "$AUTORUN_LARAVEL_MIGRATION_FORCE" = "true" ]; then migrate_flags="$migrate_flags --force" fi @@ -98,30 +100,55 @@ artisan_migrate() { migrate_flags="$migrate_flags --seed" fi - # Determine if multiple databases are specified + # Helper function to run migrations for a specific database + run_migration_for_db() { + db_name="${1:-}" + + # Build display name and database flag for messages/commands + if [ -n "$db_name" ]; then + db_display_name="'$db_name'" + db_flag="--database=$db_name" + else + db_display_name="default database" + db_flag="" + fi + + # Wait for database connection + if ! wait_for_database_connection $db_name; then + echo "❌ $script_name: Failed to connect to $db_display_name" + return 1 + fi + + # Determine if --isolated can be used for this database + db_migrate_flags="$migrate_flags" + if [ "$isolation_enabled" = "true" ]; then + if db_has_migrations_table $db_name; then + db_migrate_flags="$db_migrate_flags --isolated" + debug_log "Using --isolated flag for $db_display_name" + else + echo "â„šī¸ Skipping --isolated flag for $db_display_name: migrations table not ready (normal for first deployment)" + echo " The --isolated flag will be used on subsequent deployments." + fi + fi + + echo "🚀 Running migrations for $db_display_name" + php "$APP_BASE_DIR/artisan" $migration_command $db_flag $db_migrate_flags + } + + # Run migrations for specified database(s) if [ -n "$AUTORUN_LARAVEL_MIGRATION_DATABASE" ]; then databases=$(convert_comma_delimited_to_space_separated "$AUTORUN_LARAVEL_MIGRATION_DATABASE") database_list=$(echo "$databases" | tr ',' ' ') for db in $database_list; do - # Wait for this specific database to be ready - if ! wait_for_database_connection "$db"; then - echo "❌ $script_name: Failed to connect to database: $db" + if ! run_migration_for_db "$db"; then return 1 fi - - echo "🚀 Running migrations for database: $db" - php "$APP_BASE_DIR/artisan" $migration_command --database=$db $migrate_flags done else - # Wait for default database connection - if ! wait_for_database_connection; then - echo "❌ $script_name: Failed to connect to default database" + if ! run_migration_for_db; then return 1 fi - - # Run migration with default database connection - php "$APP_BASE_DIR/artisan" $migration_command $migrate_flags fi } @@ -316,6 +343,16 @@ laravel_version_is_at_least() { fi } +db_has_migrations_table() { + database_arg="${1:-}" + + if [ -n "$database_arg" ]; then + php "$APP_BASE_DIR/artisan" migrate:status --database="$database_arg" > /dev/null 2>&1 + else + php "$APP_BASE_DIR/artisan" migrate:status > /dev/null 2>&1 + fi +} + test_db_connection() { if [ "$AUTORUN_LARAVEL_MIGRATION_SKIP_DB_CHECK" = "true" ]; then return 0 @@ -324,9 +361,9 @@ test_db_connection() { # Pass database connection name only if specified (not empty) database_arg="${1:-}" if [ -n "$database_arg" ]; then - php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" "$database_arg" + php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$database_arg" else - php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" "$AUTORUN_LARAVEL_MIGRATION_ISOLATION" + php "$AUTORUN_LIB_DIR/laravel/test-db-connection.php" "$APP_BASE_DIR" "$AUTORUN_LARAVEL_MIGRATION_MODE" fi } diff --git a/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php b/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php index e4817fd3..0c0b5225 100644 --- a/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php +++ b/src/common/etc/entrypoint.d/lib/laravel/test-db-connection.php @@ -5,12 +5,11 @@ * This script tests if the Laravel application can connect to its configured database. * It's designed to be called from shell scripts during container initialization. * - * Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [migration_isolation] [database_connection] + * Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [database_connection] * * Arguments: - * app_base_dir - Path to Laravel application root - * migration_mode - Migration mode: 'default', 'fresh', or 'refresh' (optional, defaults to 'default') - * migration_isolation - Whether to run migrations in isolation (optional, defaults to 'false') + * app_base_dir - Path to Laravel application root + * migration_mode - Migration mode: 'default', 'fresh', or 'refresh' (optional, defaults to 'default') * database_connection - Name of the database connection to test (optional, defaults to 'default') * * Exit codes: @@ -21,15 +20,14 @@ */ // Validate arguments -if ($argc < 2 || $argc > 5) { - fwrite(STDERR, "Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [migration_isolation] [database_connection]\n"); +if ($argc < 2 || $argc > 4) { + fwrite(STDERR, "Usage: php test-db-connection.php /path/to/app/base/dir [migration_mode] [database_connection]\n"); exit(1); } $appBaseDir = $argv[1]; $migrationMode = $argc >= 3 ? $argv[2] : 'default'; -$migrationIsolation = $argc >= 4 ? $argv[3] : 'false'; -$databaseConnection = $argc >= 5 ? $argv[4] : null; +$databaseConnection = $argc >= 4 ? $argv[3] : null; // Validate migration mode $validModes = ['default', 'fresh', 'refresh']; @@ -38,13 +36,6 @@ exit(1); } -// Validate migration isolation -$validIsolations = ['true', 'false']; -if (!in_array($migrationIsolation, $validIsolations)) { - fwrite(STDERR, "Error: Invalid migration isolation '{$migrationIsolation}'. Must be one of: " . implode(', ', $validIsolations) . "\n"); - exit(1); -} - // Validate that the app base directory exists if (!is_dir($appBaseDir)) { fwrite(STDERR, "Error: App base directory does not exist: {$appBaseDir}\n"); @@ -126,17 +117,7 @@ exit(1); } - // For isolated migrations, the database file must exist (even in default mode) - if ($migrationIsolation === 'true') { - fwrite(STDERR, "SQLite database file does not exist: {$dbPath}\n"); - fwrite(STDERR, "Isolated migrations require the database file to exist before running.\n"); - fwrite(STDERR, "Either:\n"); - fwrite(STDERR, " 1. Create the database (ensure it has read and write permissions for your user): touch {$dbPath}\n"); - fwrite(STDERR, " 2. Set AUTORUN_LARAVEL_MIGRATION_ISOLATION=false to let migrations create it\n"); - exit(1); - } - - // Directory exists and is writable - migrations can create the database file (default mode only) + // Directory exists and is writable - migrations can create the database file fwrite(STDOUT, "SQLite database directory is ready - migrations will create database\n"); exit(0); } From 06452213179d8201dbe7fb11d2f70a70e206ce45 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 5 Dec 2025 14:19:43 -0600 Subject: [PATCH 02/15] Remove debug log from Laravel migration script to streamline output during migrations --- src/common/etc/entrypoint.d/50-laravel-automations.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 9cd349ca..354080b2 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -53,8 +53,6 @@ fi ############################################################################ artisan_migrate() { - debug_log "Starting migrations (isolation: $AUTORUN_LARAVEL_MIGRATION_ISOLATION)" - echo "🚀 Clearing Laravel cache before attempting migrations..." php "$APP_BASE_DIR/artisan" config:clear From 9d3f0a43b7b3c64436a00ee56d6fe276df5d3068 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 14 Jan 2026 09:27:47 -0600 Subject: [PATCH 03/15] Update Laravel migration script to change error message from error to warning for version check --- src/common/etc/entrypoint.d/50-laravel-automations.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 354080b2..4f437e79 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -80,8 +80,7 @@ artisan_migrate() { # Isolation requires Laravel 9.38.0+ if ! laravel_version_is_at_least "9.38.0"; then - echo "❌ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" - return 1 + echo "âš ī¸ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" fi isolation_enabled="true" From 9c2d6891c1882296621ce272ed84d8150d5e2df2 Mon Sep 17 00:00:00 2001 From: alloylab Date: Wed, 14 Jan 2026 09:36:38 -0600 Subject: [PATCH 04/15] Update PHP base opertating systems. Add Alpine 3.23 support (#638) PHP repo no longer has images available for alpine3.21 for latest minor versions, this removes 3.21 if its no longer available and adds 3.23 if available --- scripts/conf/php-versions-base-config.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 373f0897..9b3a614e 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -35,42 +35,43 @@ php_versions: - minor: "8.1" base_os: - name: alpine3.21 + - name: alpine3.22 - name: bookworm - name: trixie patch_versions: - # - 8.1.28 # Pull latest from Official PHP source + # - 8.1.34 # Pull latest from Official PHP source - minor: "8.2" base_os: - - name: alpine3.21 - name: alpine3.22 + - name: alpine3.23 - name: bookworm - name: trixie patch_versions: - # - 8.2.18 # Pull latest from Official PHP source + # - 8.2.30 # Pull latest from Official PHP source - minor: "8.3" base_os: - - name: alpine3.21 - name: alpine3.22 + - name: alpine3.23 - name: bookworm - name: trixie patch_versions: - # - 8.3.6 # Pull latest from Official PHP source + # - 8.3.29 # Pull latest from Official PHP source - minor: "8.4" base_os: - - name: alpine3.21 - name: alpine3.22 + - name: alpine3.23 - name: bookworm - name: trixie patch_versions: - # - 8.4.1 # Pull latest from Official PHP source + # - 8.4.16 # Pull latest from Official PHP source - minor: "8.5" base_os: - - name: alpine3.21 - name: alpine3.22 + - name: alpine3.23 - name: bookworm - name: trixie patch_versions: - # - 8.5.0 # Pull latest from Official PHP source + # - 8.5.1 # Pull latest from Official PHP source operating_systems: - family: alpine @@ -103,6 +104,10 @@ operating_systems: version: alpine3.22 number: 3.22 nginx_version: 1.28.0-r1 + - name: "Alpine 3.23" + version: alpine3.23 + number: 3.23 + nginx_version: 1.28.0-r1 - family: debian default: true versions: From 02605421cab86ed8d58ed138ff01613319d18cc5 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 14 Jan 2026 09:41:22 -0600 Subject: [PATCH 05/15] Update PHP extension installer version to 2.9.27 --- .../local/bin/docker-php-serversideup-install-php-ext-installer | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer index 2d7ddac4..a3c3cfc0 100644 --- a/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer +++ b/src/common/usr/local/bin/docker-php-serversideup-install-php-ext-installer @@ -11,7 +11,7 @@ script_name="docker-php-serversideup-install-php-ext-installer" ############ # Environment variables ############ -PHP_EXT_INSTALLER_VERSION="2.9.18" +PHP_EXT_INSTALLER_VERSION="2.9.27" ############ # Main From 7f2836a89c06aaa44e5f9d6f451965aff66d7b67 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Wed, 14 Jan 2026 10:05:58 -0600 Subject: [PATCH 06/15] Update NGINX version to 1.28.1 --- scripts/conf/php-versions-base-config.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/conf/php-versions-base-config.yml b/scripts/conf/php-versions-base-config.yml index 9b3a614e..03394995 100644 --- a/scripts/conf/php-versions-base-config.yml +++ b/scripts/conf/php-versions-base-config.yml @@ -95,31 +95,31 @@ operating_systems: - name: "Alpine 3.20" version: alpine3.20 number: 3.20 - nginx_version: 1.28.0-r1 + nginx_version: 1.28.1-r1 - name: "Alpine 3.21" version: alpine3.21 number: 3.21 - nginx_version: 1.28.0-r1 + nginx_version: 1.28.1-r1 - name: "Alpine 3.22" version: alpine3.22 number: 3.22 - nginx_version: 1.28.0-r1 + nginx_version: 1.28.1-r1 - name: "Alpine 3.23" version: alpine3.23 number: 3.23 - nginx_version: 1.28.0-r1 + nginx_version: 1.28.1-r1 - family: debian default: true versions: - name: "Debian Bullseye" version: bullseye number: 11 - nginx_version: 1.28.0-1~bullseye + nginx_version: 1.28.1-1~bullseye - name: "Debian Bookworm" version: bookworm number: 12 - nginx_version: 1.28.0-1~bookworm + nginx_version: 1.28.1-1~bookworm - name: "Debian Trixie" version: trixie number: 13 - nginx_version: 1.28.0-1~trixie + nginx_version: 1.28.1-1~trixie From 7a1b6b983bdc4180de27e02e4a05572480f8ef66 Mon Sep 17 00:00:00 2001 From: Marcel Arns <38068686+marns93@users.noreply.github.com> Date: Thu, 15 Jan 2026 20:21:00 +0100 Subject: [PATCH 07/15] Refactor and improve security headers, file blocks, etc (#631) * Fix blocking .well-known path for FrankenPHP * Remove specific endpoint checks because the whole folder should be allowed * Enhance security configurations across Apache, NGINX, and Caddy by implementing best practices for HTTP headers and file access restrictions. Added protections against clickjacking, MIME type sniffing, and sensitive file exposure while allowing necessary access to well-known URIs as per RFC 8615. --------- Co-authored-by: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> Co-authored-by: Jay Rogers --- .../etc/apache2/conf-available/security.conf | 137 ++++++++---------- .../etc/nginx/server-opts.d/security.conf | 49 +++++-- .../frankenphp/etc/frankenphp/Caddyfile | 34 +++-- 3 files changed, 120 insertions(+), 100 deletions(-) diff --git a/src/variations/fpm-apache/etc/apache2/conf-available/security.conf b/src/variations/fpm-apache/etc/apache2/conf-available/security.conf index 43957f47..990648e7 100644 --- a/src/variations/fpm-apache/etc/apache2/conf-available/security.conf +++ b/src/variations/fpm-apache/etc/apache2/conf-available/security.conf @@ -1,98 +1,77 @@ +## +# Security Configuration +## + +# This configuration follows security best practices from: # -# Disable access to the entire file system except for the directories that -# are explicitly allowed later. +# H5BP Server Configs (Apache) +# https://github.com/h5bp/server-configs-apache # -# This currently breaks the configurations that come with some web application -# Debian packages. +# OWASP Secure Headers Project +# https://owasp.org/www-project-secure-headers/ # -# -# AllowOverride None -# Require all denied -# - +# RFC 8615 - Well-Known URIs +# https://www.rfc-editor.org/rfc/rfc8615 +# +# ############################################################################## -# Changing the following options will not really affect the security of the -# server, but might make attacks slightly more difficult in some cases. +# ------------------------------------------------------------------------------ +# | Server Software Information | +# ------------------------------------------------------------------------------ -# -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minimal | Minor | Major | Prod -# where Full conveys the most information, and Prod the least. -#ServerTokens Minimal -# ServerTokens OS -# #ServerTokens Full +# Minimize information sent about the server +# https://httpd.apache.org/docs/current/mod/core.html#servertokens ServerTokens Prod -# -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail +# Disable server signature on error pages +# https://httpd.apache.org/docs/current/mod/core.html#serversignature ServerSignature Off -# ServerSignature On -# -# Allow TRACE method -# -# Set to "extended" to also reflect the request body (only for testing and -# diagnostic purposes). -# -# Set to one of: On | Off | extended +# Disable TRACE HTTP method to prevent XST attacks +# https://owasp.org/www-community/attacks/Cross_Site_Tracing TraceEnable Off -#TraceEnable On -# -# Forbid access to version control directories -# -# If you use version control systems in your document root, you should -# probably deny access to their directories. For example, for subversion: -# - - Require all denied +# ------------------------------------------------------------------------------ +# | Security Headers | +# ------------------------------------------------------------------------------ + +# Prevent clickjacking attacks by disabling iframe embedding +# https://owasp.org/www-project-secure-headers/#x-frame-options +Header always set X-Frame-Options "SAMEORIGIN" + +# Prevent MIME type sniffing attacks +# https://owasp.org/www-project-secure-headers/#x-content-type-options +Header always set X-Content-Type-Options "nosniff" + +# Control referrer information sent with requests +# https://owasp.org/www-project-secure-headers/#referrer-policy +Header always set Referrer-Policy "strict-origin-when-cross-origin" + +# Enable HTTP Strict Transport Security (HSTS) +# https://owasp.org/www-project-secure-headers/#strict-transport-security +Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + +# ------------------------------------------------------------------------------ +# | File Access Restrictions | +# ------------------------------------------------------------------------------ + +# Block access to all hidden files and directories (dotfiles) +# EXCEPT for the "/.well-known/" directory which is required by RFC 8615 +# for ACME challenges, security.txt, and other standardized endpoints. +# https://www.rfc-editor.org/rfc/rfc8615 +# https://github.com/h5bp/server-configs-apache + + Require all denied -# Prevent Apache from serving Gitlab files - - Require all denied +# Block access to files that may expose sensitive information +# Based on H5BP server configs: https://github.com/h5bp/server-configs-apache + + Require all denied # Disable XML-RPC on all wordpress sites Require all denied # allow from xxx.xxx.xxx.xxx - - -# -# Setting this header will prevent MSIE from interpreting files as something -# else than declared by the content type in the HTTP headers. -# Requires mod_headers to be enabled. -# -Header always set X-Content-Type-Options: "nosniff" - -# -# Setting this header will prevent other sites from embedding pages from this -# site as frames. This defends against clickjacking attacks. -# Requires mod_headers to be enabled. -# -Header always set X-Frame-Options: "sameorigin" - -# -# Referrer policy -# -Header always set Referrer-Policy "no-referrer-when-downgrade" - -# -# Content Security Policy -# UPDATE - September 2020: Commenting this out until we grasp better security requirements -# -#Header always set Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" - -# -# Strict-Transport-Security Policy (set HSTS) -# -Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains" \ No newline at end of file + \ No newline at end of file diff --git a/src/variations/fpm-nginx/etc/nginx/server-opts.d/security.conf b/src/variations/fpm-nginx/etc/nginx/server-opts.d/security.conf index e9056298..19986ee3 100644 --- a/src/variations/fpm-nginx/etc/nginx/server-opts.d/security.conf +++ b/src/variations/fpm-nginx/etc/nginx/server-opts.d/security.conf @@ -1,24 +1,51 @@ +## +# Security Configuration +## + +# This configuration follows security best practices from: +# +# H5BP Server Configs (nginx) +# https://github.com/h5bp/server-configs-nginx # -# Security Headers +# OWASP Secure Headers Project +# https://owasp.org/www-project-secure-headers/ # +# RFC 8615 - Well-Known URIs +# https://www.rfc-editor.org/rfc/rfc8615 +# +# ############################################################################## -# Prevent IFRAME spoofing attacks +# Prevent clickjacking attacks by disabling iframe embedding +# https://owasp.org/www-project-secure-headers/#x-frame-options add_header X-Frame-Options "SAMEORIGIN" always; -# Prevent MIME attacks +# Prevent MIME type sniffing attacks +# https://owasp.org/www-project-secure-headers/#x-content-type-options add_header X-Content-Type-Options "nosniff" always; -# Prevent Referrer URL from being leaked -add_header Referrer-Policy "no-referrer-when-downgrade" always; - -# Configure Content Security Policy -# UPDATE - September 2020: Commenting this out until we grasp better security requirements -#add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; +# Control referrer information sent with requests +# https://owasp.org/www-project-secure-headers/#referrer-policy +add_header Referrer-Policy "strict-origin-when-cross-origin" always; -# Enable HSTS +# Enable HTTP Strict Transport Security (HSTS) +# https://owasp.org/www-project-secure-headers/#strict-transport-security add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; -# Prevent access to . files (the well-known directory) +# ------------------------------------------------------------------------------ +# | File Access Restrictions | +# ------------------------------------------------------------------------------ + +# Block access to hidden files and directories (dotfiles) +# EXCEPT for the "/.well-known/" directory which is required by RFC 8615 +# for ACME challenges, security.txt, and other standardized endpoints. +# https://www.rfc-editor.org/rfc/rfc8615 +# https://github.com/h5bp/server-configs-nginx location ~ /\.(?!well-known) { deny all; +} + +# Block access to files that may expose sensitive information +# Based on H5BP server configs: https://github.com/h5bp/server-configs-nginx +location ~* (?:#.*#|\.(?:bak|conf|config|dist|inc|ini|log|sh|sql|sw[op])|~)$ { + deny all; } \ No newline at end of file diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index 56691112..e914049c 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -127,24 +127,38 @@ fd00::/8 \ } (security) { - # Reject dot files and certain file extensions - @rejected path *.bak *.conf *.dist *.fla *.ini *.inc *.inci *.log *.orig *.psd *.sh *.sql *.swo *.swp *.swop */.* - - # Return 403 Forbidden for rejected files + # This configuration follows security best practices from: + # + # H5BP Server Configs (nginx) - Adapted for Caddy + # https://github.com/h5bp/server-configs-nginx + # + # OWASP Secure Headers Project + # https://owasp.org/www-project-secure-headers/ + # + # RFC 8615 - Well-Known URIs + # https://www.rfc-editor.org/rfc/rfc8615 + + # Block access to files that may expose sensitive information + @rejected { + path *.bak *.conf *.config *.dist *.inc *.ini *.log *.sh *.sql *.swp *.swo *~ */.* + # EXCEPTION: /.well-known/* is allowed per RFC 8615 for ACME challenges + # https://www.rfc-editor.org/rfc/rfc8615 + not path /.well-known/* + } respond @rejected 403 - # Security headers + # Security Headers + # https://owasp.org/www-project-secure-headers/ header { defer - # Prevent IFRAME spoofing attacks + # Prevent clickjacking attacks by disabling iframe embedding X-Frame-Options "SAMEORIGIN" - # Prevent MIME type sniffing + # Prevent MIME type sniffing attacks X-Content-Type-Options "nosniff" - # Prevent referrer leakage + # Control referrer information sent with requests Referrer-Policy "strict-origin-when-cross-origin" - # Prevent server header leakage + # Remove server identification headers -Server - # Prevent powered by header leakage -X-Powered-By } } From 2829cf20c3d2b116ef7102b5ff65c327b71413ee Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 15 Jan 2026 14:31:37 -0600 Subject: [PATCH 08/15] Update GitHub Actions to use actions/checkout@v6 in multiple workflows --- .github/workflows/action_update-dockerhub-readme.yml | 2 +- .github/workflows/scheduled-task_update-sponsors.yml | 2 +- .github/workflows/service_docker-build-and-publish.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/action_update-dockerhub-readme.yml b/.github/workflows/action_update-dockerhub-readme.yml index b7ebb6c9..9f848712 100644 --- a/.github/workflows/action_update-dockerhub-readme.yml +++ b/.github/workflows/action_update-dockerhub-readme.yml @@ -14,7 +14,7 @@ jobs: name: Push README to Docker Hub steps: - name: git checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: main diff --git a/.github/workflows/scheduled-task_update-sponsors.yml b/.github/workflows/scheduled-task_update-sponsors.yml index 6461482d..3d1d233b 100644 --- a/.github/workflows/scheduled-task_update-sponsors.yml +++ b/.github/workflows/scheduled-task_update-sponsors.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout đŸ›Žī¸ - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Generate Sponsors 💖 uses: JamesIves/github-sponsors-readme-action@v1 diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index dbd4d7fa..fb09ff5c 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -39,7 +39,7 @@ jobs: php-version-map-json: ${{ steps.get-php-versions.outputs.php-version-map-json }} steps: - name: Check out code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} @@ -80,7 +80,7 @@ jobs: steps: - name: Check out code. - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ inputs.ref }} From f0e5dc93e7b1bbc43209a7e6eb75c96bf5bf24b1 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 15 Jan 2026 14:31:58 -0600 Subject: [PATCH 09/15] Update GitHub Actions to use actions/upload-artifact@v6 for improved artifact handling --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index fb09ff5c..1f14ef4b 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -67,7 +67,7 @@ jobs: echo "${MATRIX_JSON}" | jq '.' - name: Upload the php-versions.yml file - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: php-versions.yml path: ${{ inputs.php-versions-file }} From b8259a10b050555dc7485f1ad39f78cbdac7ec8a Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 15 Jan 2026 14:32:13 -0600 Subject: [PATCH 10/15] Update GitHub Actions to use actions/download-artifact@v7 for improved artifact handling --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index 1f14ef4b..b53d63b6 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -85,7 +85,7 @@ jobs: ref: ${{ inputs.ref }} - name: Download PHP Versions file - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v7 with: name: php-versions.yml path: ./artifacts From 245e23faa00d728f2f8248b28abfe46b8535f43f Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 15 Jan 2026 14:45:36 -0600 Subject: [PATCH 11/15] Upgrade FrankenPHP to v1.11.1 --- src/variations/frankenphp/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variations/frankenphp/Dockerfile b/src/variations/frankenphp/Dockerfile index 50813be4..f9c1ed67 100644 --- a/src/variations/frankenphp/Dockerfile +++ b/src/variations/frankenphp/Dockerfile @@ -2,7 +2,7 @@ ARG BASE_OS_VERSION='trixie' ARG PHP_VERSION='8.5' ARG BASE_IMAGE="php:${PHP_VERSION}-zts-${BASE_OS_VERSION}" -ARG FRANKENPHP_VERSION='1.10.1' +ARG FRANKENPHP_VERSION='1.11.1' ARG GOLANG_VERSION='1.25' ######################## From ca9136d971d096633f90927f43ae400feeb818fa Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Thu, 15 Jan 2026 14:47:30 -0600 Subject: [PATCH 12/15] =?UTF-8?q?Increase=20size=20of=20GitHub=20Actions?= =?UTF-8?q?=20Runners=20because=20of=20memory=20segmentation=20fault=20iss?= =?UTF-8?q?ues=20(Thanks=20Depot!=20=F0=9F=98=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/service_docker-build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/service_docker-build-and-publish.yml b/.github/workflows/service_docker-build-and-publish.yml index b53d63b6..7c3a36dd 100644 --- a/.github/workflows/service_docker-build-and-publish.yml +++ b/.github/workflows/service_docker-build-and-publish.yml @@ -74,7 +74,7 @@ jobs: docker-publish: needs: setup-matrix - runs-on: depot-ubuntu-24.04-4 + runs-on: depot-ubuntu-24.04-8 strategy: matrix: ${{fromJson(needs.setup-matrix.outputs.php-version-map-json)}} From 919fd470827d06950fa70c6ab3e2601d75970db7 Mon Sep 17 00:00:00 2001 From: Jay Rogers <3174134+jaydrogers@users.noreply.github.com> Date: Fri, 16 Jan 2026 13:50:12 -0600 Subject: [PATCH 13/15] Add security measures to block PHP execution in storage directory (#641) Implemented restrictions across Apache, NGINX, and FrankenPHP configurations to prevent the execution of PHP files in the /storage directory, addressing potential vulnerabilities related to arbitrary file uploads (GHSA-29cq-5w36-x7w3). --- .../fpm-apache/etc/apache2/conf-available/security.conf | 6 ++++++ .../fpm-nginx/etc/nginx/site-opts.d/http.conf.template | 6 ++++++ .../fpm-nginx/etc/nginx/site-opts.d/https.conf.template | 6 ++++++ src/variations/frankenphp/etc/frankenphp/Caddyfile | 5 +++++ 4 files changed, 23 insertions(+) diff --git a/src/variations/fpm-apache/etc/apache2/conf-available/security.conf b/src/variations/fpm-apache/etc/apache2/conf-available/security.conf index 990648e7..f572f250 100644 --- a/src/variations/fpm-apache/etc/apache2/conf-available/security.conf +++ b/src/variations/fpm-apache/etc/apache2/conf-available/security.conf @@ -55,6 +55,12 @@ Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains # | File Access Restrictions | # ------------------------------------------------------------------------------ +# Block PHP execution in storage directory to prevent uploaded malicious PHP files from running +# Reference: Livewire arbitrary file upload (GHSA-29cq-5w36-x7w3) + + Require all denied + + # Block access to all hidden files and directories (dotfiles) # EXCEPT for the "/.well-known/" directory which is required by RFC 8615 # for ACME challenges, security.txt, and other standardized endpoints. diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template index 1d6ee8e6..08a90ff9 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/http.conf.template @@ -30,6 +30,12 @@ location / { try_files $uri $uri/ /index.php?$query_string; } +# Block PHP execution in storage directory to prevent uploaded malicious PHP files from running +# Reference: Livewire arbitrary file upload (GHSA-29cq-5w36-x7w3) +location ~* ^/storage/.*\.php$ { + deny all; +} + # Pass "*.php" files to PHP-FPM location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; diff --git a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template index 0685ac17..810ff074 100644 --- a/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template +++ b/src/variations/fpm-nginx/etc/nginx/site-opts.d/https.conf.template @@ -36,6 +36,12 @@ location / { try_files $uri $uri/ /index.php?$query_string; } +# Block PHP execution in storage directory to prevent uploaded malicious PHP files from running +# Reference: Livewire arbitrary file upload (GHSA-29cq-5w36-x7w3) +location ~* ^/storage/.*\.php$ { + deny all; +} + # Pass "*.php" files to PHP-FPM location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; diff --git a/src/variations/frankenphp/etc/frankenphp/Caddyfile b/src/variations/frankenphp/etc/frankenphp/Caddyfile index e914049c..50b2158b 100644 --- a/src/variations/frankenphp/etc/frankenphp/Caddyfile +++ b/src/variations/frankenphp/etc/frankenphp/Caddyfile @@ -138,6 +138,11 @@ fd00::/8 \ # RFC 8615 - Well-Known URIs # https://www.rfc-editor.org/rfc/rfc8615 + # Block PHP execution in storage directory to prevent uploaded malicious PHP files from running + # Reference: Livewire arbitrary file upload (GHSA-29cq-5w36-x7w3) + @storage-php path_regexp ^/storage/.*\.php$ + respond @storage-php 403 + # Block access to files that may expose sensitive information @rejected { path *.bak *.conf *.config *.dist *.inc *.ini *.log *.sh *.sql *.swp *.swo *~ */.* From e6681ce8a3161377fa6966e981057c6e9a157a27 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 16 Jan 2026 15:25:43 -0600 Subject: [PATCH 14/15] Refactor Laravel version check and isolation mode handling in automation script - Updated the logic for enabling isolation mode based on Laravel version, ensuring it only activates for versions 9.38.0 and above. - Improved error handling and logging for Laravel version detection, providing clearer output when version determination fails. - Simplified version comparison logic to enhance readability and maintainability. --- .../entrypoint.d/50-laravel-automations.sh | 69 +++++++++++-------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/src/common/etc/entrypoint.d/50-laravel-automations.sh b/src/common/etc/entrypoint.d/50-laravel-automations.sh index 4f437e79..3634c0a1 100644 --- a/src/common/etc/entrypoint.d/50-laravel-automations.sh +++ b/src/common/etc/entrypoint.d/50-laravel-automations.sh @@ -79,11 +79,13 @@ artisan_migrate() { fi # Isolation requires Laravel 9.38.0+ - if ! laravel_version_is_at_least "9.38.0"; then + if laravel_version_is_at_least "9.38.0"; then + isolation_enabled="true" + debug_log "Isolation mode enabled (Laravel version check passed)" + else echo "âš ī¸ $script_name: Isolated migrations require Laravel v9.38.0 or above. Detected version: $(get_laravel_version)" + echo " Continuing without isolation mode..." fi - - isolation_enabled="true" fi # Start assembling migration flags @@ -265,17 +267,16 @@ get_laravel_version() { fi debug_log "Detecting Laravel version..." - # Use 2>/dev/null to handle potential PHP warnings - artisan_version_output=$(php "$APP_BASE_DIR/artisan" --version 2>/dev/null) - # Check if command was successful - if [ $? -ne 0 ]; then + # Capture artisan output + if ! artisan_version_output=$(php "$APP_BASE_DIR/artisan" --version 2>/dev/null); then echo "❌ $script_name: Failed to execute artisan command" >&2 return 1 fi + debug_log "Raw artisan output: $artisan_version_output" + # Extract version number using sed (POSIX compliant) - # Using a more strict pattern that matches "Laravel Framework X.Y.Z" laravel_version=$(echo "$artisan_version_output" | sed -e 's/^Laravel Framework \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/') # Validate that we got a version number (POSIX compliant regex) @@ -285,7 +286,7 @@ get_laravel_version() { echo "$laravel_version" return 0 else - echo "❌ $script_name: Failed to determine Laravel version" >&2 + echo "❌ $script_name: Failed to determine Laravel version from: $artisan_version_output" >&2 return 1 fi } @@ -310,34 +311,46 @@ laravel_version_is_at_least() { return 1 fi - # Validate required version format - if ! echo "$required_version" | grep -Eq '^[0-9]+\.[0-9]+(\.[0-9]+)?$'; then - echo "❌ $script_name - Invalid version requirement format: $required_version" >&2 - return 1 - fi - current_version=$(get_laravel_version) if [ $? -ne 0 ]; then echo "❌ $script_name: Failed to get Laravel version" >&2 return 1 fi - # normalize_version() takes a version string and ensures it has 3 parts - normalize_version() { - echo "$1" | awk -F. '{ print $1"."$2"."(NF>2?$3:0) }' - } + # Extract version components using cut (POSIX compliant) + cur_major=$(echo "$current_version" | cut -d. -f1) + cur_minor=$(echo "$current_version" | cut -d. -f2) + cur_patch=$(echo "$current_version" | cut -d. -f3) - normalized_current=$(normalize_version "$current_version") - normalized_required=$(normalize_version "$required_version") + req_major=$(echo "$required_version" | cut -d. -f1) + req_minor=$(echo "$required_version" | cut -d. -f2) + req_patch=$(echo "$required_version" | cut -d. -f3) - # Use sort -V to get the lower version, then compare it with required version - # This works in BusyBox because we only need to check the first line of output - lowest_version=$(printf '%s\n%s\n' "$normalized_required" "$normalized_current" | sort -V | head -n1) - if [ "$lowest_version" = "$normalized_required" ]; then - return 0 # Success: current version is >= required version - else - return 1 # Failure: current version is < required version + # Default patch to 0 if not specified + : "${cur_patch:=0}" + : "${req_patch:=0}" + + # Numeric comparison (POSIX arithmetic expansion handles this correctly) + # Compare major version + if [ "$cur_major" -gt "$req_major" ]; then + return 0 + elif [ "$cur_major" -lt "$req_major" ]; then + return 1 + fi + + # Major versions equal, compare minor + if [ "$cur_minor" -gt "$req_minor" ]; then + return 0 + elif [ "$cur_minor" -lt "$req_minor" ]; then + return 1 fi + + # Minor versions equal, compare patch + if [ "$cur_patch" -ge "$req_patch" ]; then + return 0 + fi + + return 1 } db_has_migrations_table() { From 375839a48b89c01f513dc2f08b3916f75b45af71 Mon Sep 17 00:00:00 2001 From: Jay Rogers Date: Fri, 16 Jan 2026 20:44:14 -0600 Subject: [PATCH 15/15] Update container info script to include automation status --- src/common/etc/entrypoint.d/0-container-info.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common/etc/entrypoint.d/0-container-info.sh b/src/common/etc/entrypoint.d/0-container-info.sh index a71de57c..b92ee2bd 100644 --- a/src/common/etc/entrypoint.d/0-container-info.sh +++ b/src/common/etc/entrypoint.d/0-container-info.sh @@ -55,7 +55,8 @@ Brought to you by serversideup.net â€ĸ Upload Limit: '"$UPLOAD_LIMIT"' 🔄 Runtime -â€ĸ Docker CMD: '"$DOCKER_CMD"' +â€ĸ Automations: '"$AUTORUN_ENABLED"' +â€ĸ Docker CMD: '"$DOCKER_CMD"' ' if [ "$PHP_OPCACHE_STATUS" = "0" ]; then