Author SHA1 Message Date
Claude 433e74975c fix(cac-lsphp): enable .user.ini support (LSPHP_ENABLE_USER_INI)
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) <noreply@anthropic.com>
2026-08-02 15:23:05 -07:00
jknapp cc72fda741 Merge pull request 'fix(shared-ols): never cache logged-in pages (disable tier private cache)' (#20) from fix/shared-ols-no-logged-in-cache into trunk
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m17s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m19s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m16s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m25s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m11s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m57s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m22s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 34s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 1m12s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 46s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 36s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m20s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m24s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 30s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 30s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 30s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 29s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 27s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 27s
Reviewed-on: #20
2026-06-27 04:04:49 +00:00
shadowdaoandClaude Opus 4.8 a865a13940 fix(shared-ols): never cache logged-in pages (disable tier private cache)
OLS tier had enablePrivateCache=1 + checkPrivateCache=1 at module scope on the
assumption that with no LiteSpeed Cache WP plugin nothing would be cached. In
practice OLS privately cached logged-in / cookie-bearing responses regardless of
the plugin, serving stale wp-admin pages for the full privateExpireInSeconds TTL
(observed: a WordPress 'automated update failed' nag persisting after the cause
was cleared).

Disable private caching at the tier (enablePrivateCache 0 + checkPrivateCache 0)
so logged-in pages are always served fresh. Public/anonymous caching is unchanged
(enableCache 1 + checkPublicCache 1), still honored from the plugin's
X-LiteSpeed-Cache-Control headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 20:58:23 -07:00
shadowdaoandClaude Opus 4.8 8dbfdf599a fix(shared-ols): useIpInProxyHeader 2->1 so real client IP reaches lsphp
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m26s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 1m24s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m20s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 1m21s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 1m20s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 1m19s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 1m20s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 1m18s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m16s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 1m22s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 1m19s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 56s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 34s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 29s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 30s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 27s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 25s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 26s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 26s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 28s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 26s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 25s
Mode 2 ("trusted IP only") extracts the real client IP from X-Forwarded-For
ONLY when the connecting peer is in a TRUSTED access-control list — which this
tier never configured (accessControl is `allow ALL`, no trusted designation).
So OLS kept HAProxy's container IP (172.18.0.34) as REMOTE_ADDR for EVERY
request across ALL tenants. WP security plugins (Wordfence etc.) then saw all
traffic as one IP; blocking it locked every site — and the admin — out.

HAProxy already sends X-Forwarded-For and is the ONLY peer that connects to
this tier (client-net, no host-published ports), and it OVERWRITES XFF with
%[src] (set-header), so spoofing is impossible. Mode 1 (always trust XFF) is
correct and safe here — it matches the working standalone configs/litespeed
config which has always used 1.

Verified on whp01: lsphp now receives the forwarded client IP end-to-end
(REMOTE_ADDR=<real-ip>, was 172.18.0.34). Live-hotpatched whp01+whp02 pending
this image rebuild.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 07:58:07 -07:00
jknapp 2e85f458d3 Merge pull request 'feat: OLS tier images — cac-lsphp (detached lsphp) + shared-ols' (#19) from feature/cac-lsphp-image into trunk
Cloud Apache Container / Build-and-Push (74) (push) Successful in 1m22s
Cloud Apache Container / Build-and-Push (80) (push) Successful in 2m15s
Cloud Apache Container / Build-and-Push (81) (push) Successful in 1m18s
Cloud Apache Container / Build-and-Push (82) (push) Successful in 2m24s
Cloud Apache Container / Build-and-Push (83) (push) Successful in 2m23s
Cloud Apache Container / Build-and-Push (84) (push) Successful in 2m16s
Cloud Apache Container / Build-and-Push (85) (push) Successful in 2m22s
Cloud Apache Container / Build-FPM-Images (74) (push) Successful in 2m18s
Cloud Apache Container / Build-FPM-Images (80) (push) Successful in 2m17s
Cloud Apache Container / Build-FPM-Images (81) (push) Successful in 2m18s
Cloud Apache Container / Build-FPM-Images (82) (push) Successful in 1m17s
Cloud Apache Container / Build-FPM-Images (83) (push) Successful in 2m22s
Cloud Apache Container / Build-FPM-Images (84) (push) Successful in 2m15s
Cloud Apache Container / Build-FPM-Images (85) (push) Successful in 2m13s
Cloud Apache Container / Build-LiteSpeed-Images (81) (push) Successful in 1m8s
Cloud Apache Container / Build-LiteSpeed-Images (82) (push) Successful in 47s
Cloud Apache Container / Build-LiteSpeed-Images (83) (push) Successful in 30s
Cloud Apache Container / Build-LiteSpeed-Images (84) (push) Successful in 31s
Cloud Apache Container / Build-LiteSpeed-Images (85) (push) Successful in 1m11s
Cloud Apache Container / Build-LSPHP-Images (81) (push) Successful in 1m30s
Cloud Apache Container / Build-LSPHP-Images (82) (push) Successful in 35s
Cloud Apache Container / Build-LSPHP-Images (83) (push) Successful in 29s
Cloud Apache Container / Build-LSPHP-Images (84) (push) Successful in 51s
Cloud Apache Container / Build-LSPHP-Images (85) (push) Successful in 59s
Cloud Apache Container / Build-Shared-httpd (push) Successful in 45s
Cloud Apache Container / Build-Shared-OLS (push) Successful in 1m3s
Reviewed-on: #19
2026-06-10 16:56:38 +00:00
3 changed files with 50 additions and 12 deletions
+6
View File
@@ -54,6 +54,12 @@ RUN bash -c 'set -e; \
cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \ cp /etc/lsws-templates/lsphp-overrides.ini "$SCAN_DIR/99-prod-overrides.ini"; \
echo "wrote overrides to $SCAN_DIR"' 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 EXPOSE 9000
## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping). ## TCP-connect + lsphp-alive check (LSAPI isn't FastCGI, so no cgi-fcgi ping).
+24 -11
View File
@@ -9,21 +9,34 @@
serverName shared-ols serverName shared-ols
## Real client IP behind HAProxy. HAProxy sets X-Forwarded-For (the real ## Real client IP behind HAProxy. HAProxy sets X-Forwarded-For (the real
## client) and X-Forwarded-Proto. Mode 2 = trust the proxy header. HAProxy is ## client) and X-Forwarded-Proto. Mode 1 = always use X-Forwarded-For as the
## the only thing that ever connects to this tier (it's not publicly exposed), ## client IP. HAProxy is the ONLY thing that ever connects to this tier (it's on
## so trusting the header from the docker-network peer is safe — same trust ## client-net with no host-published ports) and it OVERWRITES X-Forwarded-For
## model as the shared httpd's RemoteIPInternalProxy. ## with %[src] (set-header, not add-header), so a client can't spoof it — mode 1
useIpInProxyHeader 2 ## is safe here and matches the working standalone litespeed config.
## NOTE: mode 2 ("trusted IP only") does NOT mean "trust the proxy header" — it
## extracts the real IP ONLY when the connecting peer is in a TRUSTED access
## list, which this tier never configured. With mode 2 + no trusted IP, OLS kept
## HAProxy's container IP as REMOTE_ADDR for every request, so WP security
## plugins saw all tenants as one IP and blocking it locked everyone out.
useIpInProxyHeader 1
## LSCache enabled at MODULE scope for the whole tier (dedicated cache volume, ## LSCache enabled at MODULE scope for the whole tier (dedicated cache volume,
## ephemeral across rebuilds; OLS auto-keys a per-vhost subdir under storagePath). ## ephemeral across rebuilds; OLS auto-keys a per-vhost subdir under storagePath).
## enableCache/enablePrivateCache ON here means the cache module is ACTIVE, but a ## PUBLIC (anonymous) caching ONLY: enableCache 1 + checkPublicCache 1 let OLS
## response is only cached if it's marked cacheable — the LiteSpeed Cache WP ## serve cacheable, non-logged-in responses marked by the LiteSpeed Cache WP
## plugin sets X-LiteSpeed-Cache-Control headers, and checkPublic/PrivateCache + ## plugin's X-LiteSpeed-Cache-Control headers (ignoreRespCacheCtrl=0 honors them).
## ignoreRespCacheCtrl=0 make OLS honor them. No plugin → nothing cached (safe). ##
## PRIVATE caching is intentionally OFF (enablePrivateCache 0 + checkPrivateCache 0).
## Logged-in / cookie-bearing pages must NEVER be cached at the tier. We previously
## left enablePrivateCache=1 assuming "no plugin -> nothing cached," but that was
## WRONG: with private storage + reqCookieCache on, OLS privately cached logged-in
## responses regardless of plugin, serving stale wp-admin (e.g. a "failed update"
## nag that persisted for the full privateExpireInSeconds TTL). Keeping private
## cache off guarantees logged-in pages are always served fresh.
module cache { module cache {
storagePath ${LSCACHE_ROOT} storagePath ${LSCACHE_ROOT}
checkPrivateCache 1 checkPrivateCache 0
checkPublicCache 1 checkPublicCache 1
maxCacheObjSize 10000000 maxCacheObjSize 10000000
maxStaleAge 200 maxStaleAge 200
@@ -33,6 +46,6 @@ module cache {
ignoreReqCacheCtrl 0 ignoreReqCacheCtrl 0
ignoreRespCacheCtrl 0 ignoreRespCacheCtrl 0
enableCache 1 enableCache 1
enablePrivateCache 1 enablePrivateCache 0
} }
## ---- end shared-ols server append ---- ## ---- end shared-ols server append ----
+20 -1
View File
@@ -78,7 +78,26 @@ export LSAPI_EXTRA_CHILDREN="${LSAPI_EXTRA_CHILDREN:-5}"
export LSAPI_AVOID_FORK="${LSAPI_AVOID_FORK:-0}" export LSAPI_AVOID_FORK="${LSAPI_AVOID_FORK:-0}"
LSPHP_BIND="${LSPHP_BIND:-0.0.0.0:9000}" 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) ---- ## ---- per-site ini drop-ins (identical mechanism to entrypoint-litespeed.sh) ----
## error_log → the same customer-visible path cac:phpNN / cac-litespeed use, so ## error_log → the same customer-visible path cac:phpNN / cac-litespeed use, so