GNU rm: Recursive Deletion, Mount Boundaries, and Guardrails
Use GNU rm with a fixed target allowlist while understanding symlink handling, mount-boundary options, root protection, and unlink semantics.
rm removes directory entries; it is not a secure-erasure or rollback tool. Recursive deletion is useful for disposable build trees, but flags do not prove that the path you computed is the path you intended. A robust cleanup workflow begins with a fixed parent directory and a validated target identifier, runs with only the permissions it needs, and treats mount and symlink behavior as explicit boundaries.
This article focuses on GNU Coreutils rm. Options such as --one-file-system and --preserve-root=all are GNU extensions, so confirm the installed implementation before using them in a script.
Constrain the operand before calling rm
For automation, derive the target from a trusted root and a single path component, not from an arbitrary pathname supplied by a job or user. The following Bash fragment rejects empty IDs, . and .., and strings containing a slash. Run cleanup as a restricted service account, and ensure untrusted users cannot replace the parent directory or its entries while cleanup runs.
job_id=${1-}
case "$job_id" in
''|.|..|*/*)
printf '%s\n' 'invalid job identifier' >&2
exit 2
;;
esac
root=/var/tmp/build-jobs
target=$root/$job_id
if [[ ! -d "$root" || ! -d "$target" || -L "$target" ]]; then
printf 'refusing unexpected cleanup target: %s\n' "$target" >&2
exit 1
fi
printf 'removing reviewed target: %s\n' "$target"
rm -r --one-file-system --preserve-root=all -- "$target"
The -- separates operands from options, so an identifier beginning with - is not parsed as an rm flag. The explicit component check blocks path traversal through slashes, while the symlink check refuses a target that is a link at check time. These checks do not make a writable or attacker-controlled parent safe: another process could replace entries between the test and removal. Keep the root directory private to the cleanup account and avoid elevated privileges where they are unnecessary.
Understand what the GNU safety flags do
GNU rm refuses recursive removal of / by default. --preserve-root=all also rejects a command-line argument that is not on the same filesystem as its parent. This is a guardrail, not an allowlist: it does not ensure a path is inside your intended scratch directory.
--one-file-system skips directories on a different filesystem while recursively removing a hierarchy. It can prevent cleanup from crossing a normal filesystem mount, but it does not protect a bind mount that shares the same filesystem as the target. If a build tree relies on mount namespaces or bind mounts, inspect and unmount those deliberately before cleanup rather than assuming this option detects every boundary.
-I prompts once for sufficiently large or recursive operations, but interactive confirmation is not a substitute for identity checks in unattended jobs. Conversely, -f suppresses prompts and ignores missing operands; it should not be added reflexively to make a script “reliable.” GNU rm does not provide a general dry-run mode. Review an explicit manifest or target inventory before the destructive call.
Deletion is not erasure
POSIX specifies that recursive rm removes directory entries without following symbolic links encountered during traversal; it removes the link itself. The underlying unlink() operation removes a name from a directory. If a process still has the file open, its file descriptor can continue to refer to the object until it is closed. Neither command promises that storage blocks were overwritten or made unrecoverable. Backups, snapshots, copy-on-write filesystems, and device behavior make that stronger claim especially unsafe.
For incident review, log the resolved target, the job ID, command exit status, and the cleanup account. A nonzero exit status means the request encountered a failure; zero does not prove that every external mount or independently managed resource was cleaned up. Use filesystem-specific inventory and post-cleanup checks for those obligations.
Related:
- find in Production: Safe Traversal, Filenames, and Destructive Actions
- GNU shred: In-Place Overwrites and the Limits of File Erasure
Sources: