Skip to content

Instantly share code, notes, and snippets.

@adyp
Last active November 27, 2025 13:46
Show Gist options
  • Select an option

  • Save adyp/8cf272ae7217fe24eecacb0fe3ad147e to your computer and use it in GitHub Desktop.

Select an option

Save adyp/8cf272ae7217fe24eecacb0fe3ad147e to your computer and use it in GitHub Desktop.
find-world-writable
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat << EOF
Usage: $0 [OPTIONS] [TARGET_PATHS...]
Scan for world-writable files/directories (ls -l style output).
OPTIONS:
--user, -u Run in non-root user mode: skip inaccessible paths
--out FILE, -o FILE Write output to FILE (also prints to stdout)
--quiet, -q Batch mode: suppress progress/summary
--help Show this help
EXAMPLES:
$0 --out report.txt # Root scan to file
$0 -u -q . -o ~/ww-report.txt # Quiet user scan of current dir
sudo $0 /var /tmp --quiet # Quiet root scan
EOF
exit 0
}
# Parse options
targets=("/")
user_mode=false
output_file=""
quiet=false
while [[ $# -gt 0 ]]; do
case $1 in
--user|-u) user_mode=true; shift ;;
--out=*) output_file="${1#*=}"; shift ;;
-o) output_file="$2"; shift 2 ;;
--quiet|-q) quiet=true; shift ;;
--help) usage ;;
--*) echo "Unknown option: $1" >&2; usage ;;
*) break ;;
esac
done
if [[ $# -gt 0 ]]; then
targets=("$@")
fi
# Setup tee command
if [[ -n "$output_file" ]]; then
[[ -f "$output_file" ]] && echo "Warning: Overwriting $output_file" >&2
tee_cmd="tee \"$output_file\""
else
tee_cmd="cat"
fi
# Progress (unless quiet)
[[ $quiet == false ]] && echo "Scanning world-writable files/directories on: ${targets[*]}"
[[ $quiet == false && $user_mode == true ]] && echo " (User mode: skipping inaccessible paths)"
# Exclusions
exclusions=(
'/run/user/*' '/proc/*' '*/containerd/*' '*/kubelet/pods/*'
'/sys/kernel/security/apparmor/*' '/snap/*'
'/sys/fs/cgroup/memory/*' '/sys/fs/selinux/*'
)
path_excl=()
for excl in "${exclusions[@]}"; do
path_excl+=(-not -path "$excl")
done
nfs_excl=()
while IFS= read -r target; do
nfs_excl+=(-not -path "${target}/*")
done < <(findmnt -Dkerno fstype,target 2>/dev/null | awk '$1 ~ /^(nfs|proc|smb)$/ {print $2}' | grep -E '^/' || true)
# Counters
files_found=0 dirs_found=0 skipped=0 invalid_targets=0
# STREAMING: Process each target immediately
for target in "${targets[@]}"; do
[[ ! -e "$target" ]] && {
[[ $quiet == false ]] && echo "Skipping non-existent target: $target" >&2
((invalid_targets++))
continue
}
# Use find -ls for standard ls -l output + null termination for paths with spaces
find "$target" \
"${path_excl[@]}" \
"${nfs_excl[@]}" \
\( -fstype nfs -o -fstype proc -o -fstype smb \) -prune \
-o \( -type f -o -type d \) -perm /002 \
${user_mode:+-readable} \
-print0 2>/dev/null | \
while IFS= read -r -d '' path || [[ -n $path ]]; do
# Skip inaccessible in user mode
[[ $user_mode == true && ! -r "$path" && ! -x "$path" ]] && {
((skipped++)); continue
}
# Get ls -l style output for this path
ls_output=$(find "$path" -maxdepth 0 -ls 2>/dev/null)
# Stream to tee immediately
printf '%s\n' "$ls_output" | eval $tee_cmd
# Update counters
if [[ -f "$path" ]]; then
((files_found++))
elif [[ -d "$path" ]]; then
((dirs_found++))
fi
done
done
# Final summary (unless quiet)
[[ $quiet == false ]] && {
echo ""
echo "Scan complete:"
echo "- Files: $files_found"
echo "- Dirs: $dirs_found"
[[ $user_mode == true ]] && echo "- Skipped: $skipped"
((invalid_targets > 0)) && echo "- Invalid targets: $invalid_targets"
[[ -n "$output_file" ]] && echo "- Output also saved to: $output_file"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment