fix(security): deny config/VCS dotfiles over HTTP on standalone cac and cac-litespeed

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 <noreply@anthropic.com>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-10-20 16:31:56 -07:00
1 parent 86a88c87db
commit 74648ba34b
3 files changed
+42

No files matched your search

+1
View File
@@ -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
+12
View File
@@ -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
+29
View File
@@ -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/<user>/public_html. Standalone cac never got that drop-in, and measured
## on .148 it served /.env with contents.
##
## <FilesMatch> matches the FILENAME resolved from the filesystem path, not the
## request URL, so it cannot be side-stepped by URL-encoding. httpd applies
## <Directory> sections (and .htaccess) first and <Files>/<FilesMatch> 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/<token> that is <token>, not a dotfile.
##
## HTTP only: PHP still reads .user.ini and .env off the filesystem as before.
<Directory "/home">
<FilesMatch "^\.(user\.ini|env|git|svn|hg|bzr|ht|whp|site-builder)">
Require all denied
</FilesMatch>
</Directory>
## Directory form: reaches the files INSIDE .git/ etc. (e.g. /.git/config,
## whose own name is not a dotfile).
<DirectoryMatch "^/home/.*/\.(git|svn|hg|bzr|whp|site-builder)(/|$)">
Require all denied
</DirectoryMatch>