log_inspect.sh discarded grep's stderr and ignored its exit status, so
an unprivileged search over root-owned logs was indistinguishable from a
search that genuinely found nothing. grep's three outcomes now mean
three different things: matched, matched nothing, or could not read
everything -- the last of which says so and exits non-zero. Confirmed
grep returns 2 rather than 1 in that case, which is why the naive
"status -eq 1" check would never have fired.
service_manager.sh validates the action before dispatch and requires
root for the five that change system state, leaving status and list open
to anyone. $action is quoted at both call sites.
rsync_magic.sh had --inplace on unconditionally. It writes straight into
destination files instead of to a temporary and renaming, so an
interrupted run leaves them partially overwritten -- the opposite of
what a backup tool should guarantee. Now opt-in, with a warning when
used. Its log lives under /var/log and every line pipes through tee, so
under pipefail an unprivileged run died on the first line with a bare
permission error; it now falls back to stdout rather than failing the
sync over its own logging. --delete also confirms before running, since
reversing the two arguments erases the backup.
disk_cleanup.sh moves from `set -o pipefail` to full strict mode, with
the two pipelines that legitimately return non-zero handled at their
call sites rather than by leaving the script lax. Its "largest files"
walk also gained -xdev, which it was missing while security_audit.sh
next door already had it -- without it the walk descends /proc, /sys and
every network mount.
All fifteen scripts now run under set -euo pipefail.
The workflow went in at severity: error on the assumption that a
never-linted repository would have a backlog worth grandfathering. It
did not -- error found nothing, and warning found exactly two things, so
the cautious setting was protecting against a problem that was not
there.
zimbra_backup.sh: SC2024, sudo does not affect redirects. The
`> "$BACKUP_FILE"` runs as root rather than as the sudo'd zimbra user,
so backups landed root-owned inside a directory the script deliberately
chowns to zimbra:zimbra. Kept the redirect -- root can always write
there, and piping into `tee` would put tee's status in $? and hide a
zmmailbox failure -- and handed ownership over explicitly afterwards.
The suppression is narrow and states why.
disk_cleanup.sh: SC2034, total_freed was assigned and never read.
CI now holds at warning with a clean tree, so anything that trips it is
new rather than inherited.
--dirs accepted any path and fed it to `find -delete` running as root,
with no confirmation: `--clean --dirs /home` removed every file in /home
past the age threshold, and `--dirs /` did it system-wide.
Now refuses protected directories, comparing the readlink -f resolved
path so a symlink or /tmp/../home cannot smuggle one through, and
requires absolute paths. Anything outside the /tmp,/var/tmp defaults
also needs an interactive confirmation -- or --yes, so unattended use
stays possible; without a tty and without --yes it refuses rather than
hanging in cron.
usage() sliced fixed line numbers (head -22 | tail -n +18) and had
already outgrown them, truncating --help mid-list at --age. So --dirs,
the one option that could destroy a system, was the one option --help
never mentioned. Replaced with a sed range that tracks the comment block
wherever it moves.
The first version of the protected-path check let "/" through: it
compared against "${p%/}", which turns the "/" entry into an empty
string that matches nothing. Caught by testing the guard against the
paths it exists to stop, rather than assuming it worked.