From 433e74975cc908aac0aeec28dd1b664e584f4c99 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 15:23:05 -0700 Subject: [PATCH] fix(cac-lsphp): enable .user.ini support (LSPHP_ENABLE_USER_INI) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit php-lsapi compiles .user.ini support in but leaves it DISABLED by default. sapi/litespeed/lsapi_main.c has `static int parse_user_ini = 0;` and only sets it when the process environment contains LSPHP_ENABLE_USER_INI=on. WHP never set it, so lsphp never entered the user-ini chain at all. The failure was silent: phpinfo() still reports user_ini.filename=.user.ini and user_ini.cache_ttl=300, because those are core INI defaults that are simply inert under this SAPI. Every other WHP PHP tier (cac, cac-fpm, cac-litespeed) honors .user.ini, so shared_ols was quietly inconsistent. Impact found in production (whp01/whp02/sdbees/TrueSelfCA, 29 sites): - Per-site memory_limit / max_input_vars overrides were ignored. A Divi site's max_input_vars stayed at the 2000 default while its .user.ini asked for 20000. - Wordfence's auto_prepend_file WAF never loaded on ANY shared_ols site. 11 sites had the plugin installed and reporting "Extended Protection" enabled while the prepend was never executed. Verified on a live sidecar (shadowdao.com, whp01) before this commit by injecting the env var via whp.container_types.startup_env and recreating: before: auto_prepend_file=/scripts/cac-lsphp-normalize.php (WAF absent) after: auto_prepend_file=/home/shadowdao/public_html/wordfence-waf.php wordfence-waf.php present in get_included_files() class_exists('wfWAF') === true The platform normalize prepend still chains in behind Wordfence's bootstrap, so DOCUMENT_ROOT canonicalisation is not lost. Set in two places on purpose: the Dockerfile ENV makes the value visible in `docker inspect` and survives an entrypoint override, and the entrypoint re-exports it with the same default because the runuser fallback exec path resets the environment. Still overridable per-container (LSPHP_ENABLE_USER_INI=off) as an escape hatch for a site whose legacy cPanel-generated .user.ini has not been remediated yet. NOTE: enabling this activates every previously-inert .user.ini at once. Audit the fleet for stale cPanel directives before rolling this image — session.save_path values under /var/cpanel/ that do not exist in the container, memory_limit above the cgroup cap, and upload_max_filesize values below the platform default were all found and remediated first. Co-Authored-By: Claude Opus 5 (1M context) --- Dockerfile.lsphp | 6 ++++++ scripts/entrypoint-lsphp.sh | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Dockerfile.lsphp b/Dockerfile.lsphp index c70de7b..588bcf2 100644 --- a/Dockerfile.lsphp +++ b/Dockerfile.lsphp @@ -54,6 +54,12 @@ RUN bash -c 'set -e; \ cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \ echo "wrote overrides to $SCAN_DIR"' +## php-lsapi gates .user.ini parsing behind this env var (see entrypoint-lsphp.sh +## for the full explanation). Set here so the value is visible in `docker inspect` +## and survives an entrypoint override; the entrypoint re-exports it with the same +## default so the runuser exec path can't drop it. +ENV LSPHP_ENABLE_USER_INI=on + EXPOSE 9000 ## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping). diff --git a/scripts/entrypoint-lsphp.sh b/scripts/entrypoint-lsphp.sh index c2f9edb..c75cd17 100644 --- a/scripts/entrypoint-lsphp.sh +++ b/scripts/entrypoint-lsphp.sh @@ -78,7 +78,26 @@ export LSAPI_EXTRA_CHILDREN="${LSAPI_EXTRA_CHILDREN:-5}" export LSAPI_AVOID_FORK="${LSAPI_AVOID_FORK:-0}" LSPHP_BIND="${LSPHP_BIND:-0.0.0.0:9000}" -echo "Container memory: ${CONTAINER_MEMORY_MB}MB | PHP_LSAPI_CHILDREN=${PHP_LSAPI_CHILDREN} | LSAPI_MAX_IDLE=${LSAPI_MAX_IDLE} | PHPVER=${PHPVER} | bind=${LSPHP_BIND}" +## ---- .user.ini support ---- +## php-lsapi compiles .user.ini support in but leaves it DISABLED by default: +## sapi/litespeed/lsapi_main.c has `static int parse_user_ini = 0;` and only +## sets it in PHP_MINIT_FUNCTION(litespeed) when the PROCESS ENV contains +## LSPHP_ENABLE_USER_INI=on. Without it, lsphp never enters the user-ini chain +## at all — and does so SILENTLY, because `user_ini.filename` / `user_ini.cache_ttl` +## still report their core defaults in phpinfo(). Every other WHP PHP tier +## (cac, cac-fpm, cac-litespeed) honors .user.ini, so leaving it off here made +## the shared-ols tier quietly inconsistent: customer memory_limit / +## max_input_vars overrides were ignored, and — the reason this was found — +## Wordfence's `auto_prepend_file` WAF never loaded on ANY shared-ols site. +## +## Exported here rather than relying solely on the Dockerfile ENV because the +## runuser fallback below resets the environment; an export survives all three +## exec paths. Still overridable per-container (set LSPHP_ENABLE_USER_INI=off in +## the site's env) as an escape hatch for a site whose legacy cPanel-generated +## .user.ini has not been remediated yet. +export LSPHP_ENABLE_USER_INI="${LSPHP_ENABLE_USER_INI:-on}" + +echo "Container memory: ${CONTAINER_MEMORY_MB}MB | PHP_LSAPI_CHILDREN=${PHP_LSAPI_CHILDREN} | LSAPI_MAX_IDLE=${LSAPI_MAX_IDLE} | PHPVER=${PHPVER} | bind=${LSPHP_BIND} | user_ini=${LSPHP_ENABLE_USER_INI}" ## ---- per-site ini drop-ins (identical mechanism to entrypoint-litespeed.sh) ---- ## error_log → the same customer-visible path cac:phpNN / cac-litespeed use, so