Root rm -f in drop_symlink could be redirected by a parent-directory swap to
delete arbitrary files as root. drop_symlink and the logs/ symlink sweep now
unlink AS THE CUSTOMER (can never delete anything the customer couldn't), and
drop_symlink still refuses to start if the path is a symlink afterwards; the
pre-daemon logs/ gate re-checks and fails closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
drop_symlink unlinks as root (never follows the target) and refuses to start if
the path is still a symlink. Immediately before nginx (which opens its logs
under /home/$user/logs as root) starts, logs/, logs/nginx and logs/nodejs must be
real directories and nothing under logs/ may be other than a regular file or
directory, else the entrypoint exits 1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same race-safety as cac: drop_symlink and the logs/ symlink sweep run as the
customer, so a parent dir swapped for a symlink between check and rm cannot
redirect a root unlink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/home/$user is the customer's bind mount; they can plant symlinks in it. The
entrypoint ran as root and created/copied/chowned through customer paths (the
example-app copy, ecosystem.config.js generation + non-recursive chown, which
dereferences). Now: refuse to start if /home/$user is not a real dir; logs/ must
be a real dir and symlinks under it are removed and logged (nginx opens those
files as root); app/logs dirs, the example copy and ecosystem.config.js are
created AS THE CUSTOMER (setpriv, runuser fallback); the o+x directory pass runs
as the customer. chown -R is unchanged (GNU default -P never follows symlinks).
app/ itself may stay a symlink (release-dir deploys).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
entrypoint.sh ran `chmod -R 755 /home/$user` on every container start.
/home/$user is the customer's bind-mounted directory, so every file (.env and
other app secrets included) became world-readable and executable, and any
tighter permissions the customer set were reverted on each restart. It dates
from the initial implementation (2989cd5) with no stated reason; the same
pattern was removed from cloud-apache-container.
The legitimate need: nginx workers run as `nginx` (configs/nginx.conf) and
serve /static/ via `alias /home/$user/app/public/`, so nginx must traverse
directories. Keep the recursive chown; replace the recursive chmod with
chmod 755 on /home/$user itself and o+x on directories lacking it. Regular
files are never touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>