From 74648ba34bce4fde3d2f6c3f298e1ab179069721 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Tue, 20 Oct 2026 16:31:56 -0700 Subject: [PATCH] fix(security): deny config/VCS dotfiles over HTTP on standalone cac and cac-litespeed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on .148: standalone cac served /.env with its contents, and cac-litespeed served /.env even at mode 0640 (OLS runs as the customer uid there). shared-httpd and shared-ols already deny these via WHP's drop-ins; the standalone tiers never got them. - cac: new configs/whp-security.conf in /etc/httpd/conf.d — the same named FilesMatch + DirectoryMatch list as WHP's shared-httpd-security.conf, re-rooted to /home. - cac-litespeed: the same `context exp:` regex as WHP's shared-ols vhconf template, added to the vhost template. Named list (.user.ini .env* .git .svn .hg .bzr .ht* .whp .site-builder), not a blanket dotfile deny, so /.well-known/ stays reachable. HTTP only: PHP still reads these files from disk. Co-Authored-By: Claude Opus 5.5 --- Dockerfile | 1 + configs/litespeed/site-template.tpl | 12 ++++++++++++ configs/whp-security.conf | 29 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 configs/whp-security.conf diff --git a/Dockerfile b/Dockerfile index 676bddc..e999c5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,7 @@ COPY ./configs/phpinfo.php /var/www/html/ COPY ./configs/mariadb.repo /etc/yum.repos.d/ COPY ./configs/index.php /var/www/html/ COPY ./configs/remote_ip.conf /etc/httpd/conf.d/ +COPY ./configs/whp-security.conf /etc/httpd/conf.d/ # Set up cron job in a single layer RUN echo "15 */12 * * * root /scripts/log-rotate.sh" >> /etc/crontab diff --git a/configs/litespeed/site-template.tpl b/configs/litespeed/site-template.tpl index b508e54..5db442d 100644 --- a/configs/litespeed/site-template.tpl +++ b/configs/litespeed/site-template.tpl @@ -72,6 +72,18 @@ virtualHostConfig { addDefaultCharset off } + ## SECURITY: never serve config / VCS dotfiles over HTTP. Same named list as + ## WHP's shared-ols vhconf template (first `context exp:` there) and + ## shared-httpd's 000-whp-security.conf. Measured on .148: this tier served + ## /.env even at mode 0640, because OLS runs as the customer uid here. An + ## `exp:` regex context wins over the `context /` prefix context regardless + ## of order (measured on OLS 1.8.4 for shared-ols), so a customer .htaccess + ## cannot reopen it. Named, not blanket "^\.": /.well-known/ (ACME, + ## security.txt, app-site-association) must stay reachable. + context exp:^/(.*/)?\.(user\.ini|env|git|svn|hg|bzr|ht|whp|site-builder) { + allowBrowse 0 + } + rewrite { enable 1 autoLoadHtaccess 1 diff --git a/configs/whp-security.conf b/configs/whp-security.conf new file mode 100644 index 0000000..b492680 --- /dev/null +++ b/configs/whp-security.conf @@ -0,0 +1,29 @@ +## SECURITY: never serve config / VCS dotfiles over HTTP (standalone cac). +## +## Same named list as WHP's shared-httpd drop-in (web-files/configs/ +## shared-httpd-security.conf, 000-whp-security.conf on every shared-httpd host), +## re-rooted from /mnt/users to /home because here the docroot is +## /home//public_html. Standalone cac never got that drop-in, and measured +## on .148 it served /.env with contents. +## +## matches the FILENAME resolved from the filesystem path, not the +## request URL, so it cannot be side-stepped by URL-encoding. httpd applies +## sections (and .htaccess) first and / after +## them, so this deny wins over create-vhost.sh's `Require all granted`. +## +## A NAMED LIST, NOT A BLANKET "^\." DENY: /.well-known/ (ACME, security.txt, +## apple-app-site-association, assetlinks.json, payment-domain verification) +## must stay reachable. FilesMatch tests the final path component, and for +## /.well-known/acme-challenge/ that is , not a dotfile. +## +## HTTP only: PHP still reads .user.ini and .env off the filesystem as before. + + + Require all denied + + +## Directory form: reaches the files INSIDE .git/ etc. (e.g. /.git/config, +## whose own name is not a dotfile). + + Require all denied +