fix(safe-paths): unlink planted symlinks as the customer, then fail closed

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>
This commit is contained in:
shadowdaoandClaude Opus 5.5 committed 2026-10-20 16:06:41 -07:00
1 parent 02d93f4d1a
commit 86a88c87db
1 file changed
+10 -7
+10 -7
View File
@@ -34,13 +34,14 @@ die() {
exit 1
}
# drop_symlink PATH : if PATH is a symlink, unlink it (as root: rm on the final
# component never touches the link target, and the customer cannot deny it) and
# log it. Fails closed: if it is still a symlink afterwards, refuse to start.
# drop_symlink PATH : if PATH is a symlink, unlink it AS THE CUSTOMER (rm on
# the final component never touches the link target, and if a parent dir is
# swapped for a symlink mid-way the customer can only delete what they already
# could) and log it. Fails closed: still a symlink afterwards -> refuse to start.
drop_symlink() {
if [ -L "$1" ]; then
echo "entrypoint: SECURITY: $1 was a symlink to '$(readlink "$1")' - removing" >&2
rm -f -- "$1"
as_user rm -f -- "$1"
[ -L "$1" ] && die "could not remove symlink $1"
fi
return 0
@@ -59,9 +60,11 @@ secure_home() {
# public_html may legitimately be a symlink (release-dir deploys); it is left
# alone and every root-side operation on it runs as the customer instead.
if [ -d "$h/logs" ]; then
# find without -L never follows symlinks; -delete unlinks the link itself.
# Errors are not fatal HERE: require_safe_logs re-checks and fails closed.
find "$h/logs" -xdev -type l \
# As the customer: find without -L never follows symlinks, -delete unlinks
# the link itself, and a raced parent swap can only reach what the customer
# could already delete. Errors are not fatal HERE: require_safe_logs
# re-checks immediately before the daemons start and fails closed.
as_user find "$h/logs" -xdev -type l \
-printf 'entrypoint: SECURITY: %p was a symlink to %l - removed\n' -delete >&2 || true
fi
}