Harden the four monitoring scripts

set -euo pipefail across all four, but added deliberately rather than
pasted in -- each script needed the places where a non-zero exit is
normal handled first, or strict mode would have made them worse:

- sys_monitor / process_monitor: `ps | head -n 6` is a latent SIGPIPE.
  head closes the pipe after six lines, and on a host with enough
  processes ps fills the buffer and exits 141, which pipefail turns into
  a script abort -- on exactly the busy machine you wanted to inspect.
  Confirmed the mechanism (a large producer into head returns 141) and
  those pipelines now tolerate it.
- security_audit: find exits non-zero when it cannot read a directory,
  which is routine when walking the whole filesystem. Without handling,
  set -e aborted the audit part way while still looking complete. Also
  notes that a clean report as non-root means little, since find cannot
  descend where it may not read.
- network_info: iptables needs root, so the last section aborted the
  script for ordinary users. Now reports the failure, and falls back to
  nft where iptables is absent.

process_monitor also no longer kills on sight. `pkill -x` by name can
match several processes at once, and as root that is an easy way to take
down more than intended. It now prints what it matched and asks, with
FORCE=1 for unattended use and a refusal rather than a hang when there
is no tty.

All four run clean; the kill path was tested against a live process and
left it alive.
This commit is contained in:
2026-08-22 22:19:05 -07:00
parent 82528a1402
commit e8c0a73dba
4 changed files with 68 additions and 17 deletions
+9 -1
View File
@@ -17,6 +17,11 @@
# #
# Usage: network_info.sh (no arguments) # Usage: network_info.sh (no arguments)
# Description: Displays network interface addresses, routing table, open ports, and iptables rules. # Description: Displays network interface addresses, routing table, open ports, and iptables rules.
#
# Read-only. The iptables section needs root; without it that section
# reports the failure rather than silently showing nothing.
set -euo pipefail
echo "==== Network Interfaces (IP addresses) ====" echo "==== Network Interfaces (IP addresses) ===="
if command -v ip &> /dev/null; then if command -v ip &> /dev/null; then
@@ -47,7 +52,10 @@ fi
echo -e "\n==== Firewall Rules (iptables) ====" echo -e "\n==== Firewall Rules (iptables) ===="
if command -v iptables &> /dev/null; then if command -v iptables &> /dev/null; then
iptables -L -n -v # list firewall rules with numeric addresses and packet counts # Needs root; report the failure instead of letting set -e abort here.
iptables -L -n -v || echo "Could not read iptables rules (run as root?)."
elif command -v nft &> /dev/null; then
nft list ruleset || echo "Could not read nftables ruleset (run as root?)."
else else
echo "iptables command not found (no firewall rules to show or using nftables)." echo "iptables command not found (no firewall rules to show or using nftables)."
fi fi
+31 -7
View File
@@ -18,23 +18,47 @@
# Usage: process_monitor.sh [kill <process_name|PID>] # Usage: process_monitor.sh [kill <process_name|PID>]
# Description: Without args, shows top CPU & memory processes. With "kill", terminates process by name or PID. # Description: Without args, shows top CPU & memory processes. With "kill", terminates process by name or PID.
if [ "$1" = "kill" ]; then set -euo pipefail
if [ "${1:-}" = "kill" ]; then
target="$2" target="$2"
if [ -z "$target" ]; then if [ -z "$target" ]; then
echo "Usage: $0 kill <process_name|PID>"; exit 1 echo "Usage: $0 kill <process_name|PID>"; exit 1
fi fi
# If target is numeric (PID), kill that PID, else kill by name # Show what will be signalled and confirm first. pkill -x by name can
# match several processes at once, and as root that is an easy way to
# take down more than intended with no warning.
if [[ "$target" =~ ^[0-9]+$ ]]; then if [[ "$target" =~ ^[0-9]+$ ]]; then
kill "$target" && echo "Process $target killed." || echo "Failed to kill process $target." if ! ps -p "$target" -o pid,user,comm >/dev/null 2>&1; then
echo "No process with PID $target."; exit 1
fi
ps -p "$target" -o pid,user,comm
else else
# Use pkill to kill by name (match full process name) if ! pgrep -x "$target" >/dev/null 2>&1; then
pkill -x "$target" && echo "Processes named '$target' killed." || echo "No process '$target' found or kill failed." echo "No process named '$target'."; exit 1
fi
pgrep -x -a "$target"
fi
if [ "${FORCE:-}" != "1" ]; then
if [ ! -t 0 ]; then
echo "Refusing to kill without confirmation; set FORCE=1 for unattended use." >&2
exit 1
fi
read -r -p "Send SIGTERM to the above? (yes/NO): " reply
[ "$reply" = "yes" ] || { echo "Cancelled."; exit 0; }
fi
if [[ "$target" =~ ^[0-9]+$ ]]; then
kill "$target" && echo "Process $target signalled." || echo "Failed to signal process $target."
else
pkill -x "$target" && echo "Processes named '$target' signalled." || echo "Failed to signal '$target'."
fi fi
exit 0 exit 0
fi fi
echo "==== Top 5 CPU-consuming processes ====" echo "==== Top 5 CPU-consuming processes ===="
ps -eo pid,user,comm,%cpu --sort=-%cpu | head -n 6 ps -eo pid,user,comm,%cpu --sort=-%cpu | head -n 6 || true
echo -e "\n==== Top 5 Memory-consuming processes ====" echo -e "\n==== Top 5 Memory-consuming processes ===="
ps -eo pid,user,comm,%mem --sort=-%mem | head -n 6 ps -eo pid,user,comm,%mem --sort=-%mem | head -n 6 || true
+14 -3
View File
@@ -17,18 +17,29 @@
# #
# Usage: security_audit.sh (no arguments) # Usage: security_audit.sh (no arguments)
# Description: Lists world-writable files/dirs, SUID/SGID files, and listening ports. # Description: Lists world-writable files/dirs, SUID/SGID files, and listening ports.
#
# Run as root for a complete picture: as an unprivileged user, find
# cannot descend into directories it may not read, so a clean report
# below is not the same as a clean system.
set -euo pipefail
# Each find below ends in `|| true`. find exits non-zero when it could
# not read some directory -- routine here, since we deliberately walk the
# whole filesystem -- and without this, set -e would abort the audit part
# way through and still look like it had finished.
# World-writable files (perm bits: others have write) # World-writable files (perm bits: others have write)
echo "==== World-Writable Files (potentially unsafe) ====" echo "==== World-Writable Files (potentially unsafe) ===="
find / -xdev -type f -perm -0002 -printf '%M %u %g %p\n' 2>/dev/null find / -xdev -type f -perm -0002 -printf '%M %u %g %p\n' 2>/dev/null || true
# World-writable directories without sticky bit # World-writable directories without sticky bit
echo -e "\n==== World-Writable Directories (no sticky bit) ====" echo -e "\n==== World-Writable Directories (no sticky bit) ===="
find / -xdev -type d -perm -0002 ! -perm -1000 -printf '%M %u %g %p\n' 2>/dev/null find / -xdev -type d -perm -0002 ! -perm -1000 -printf '%M %u %g %p\n' 2>/dev/null || true
# SUID/SGID files (files with setuid or setgid bits) # SUID/SGID files (files with setuid or setgid bits)
echo -e "\n==== SUID/SGID Files ====" echo -e "\n==== SUID/SGID Files ===="
find / -xdev \( -perm -4000 -o -perm -2000 \) -printf '%M %u %g %p\n' 2>/dev/null find / -xdev \( -perm -4000 -o -perm -2000 \) -printf '%M %u %g %p\n' 2>/dev/null || true
# Open listening ports # Open listening ports
echo -e "\n==== Listening Network Ports ====" echo -e "\n==== Listening Network Ports ===="
+10 -2
View File
@@ -18,6 +18,14 @@
# Usage: sys_monitor.sh (no arguments) # Usage: sys_monitor.sh (no arguments)
# Description: Prints system uptime, memory, disk usage, and top CPU/mem processes. # Description: Prints system uptime, memory, disk usage, and top CPU/mem processes.
set -euo pipefail
# `ps | head` is a SIGPIPE waiting to happen: head closes the pipe after
# six lines, and on a host with enough processes ps fills the buffer and
# dies with 141, which pipefail turns into a script exit. That is exactly
# the busy machine you most want this to work on, so those two pipelines
# tolerate it explicitly.
echo "==== System Uptime and Load ====" echo "==== System Uptime and Load ===="
uptime uptime
@@ -30,7 +38,7 @@ df -h -x tmpfs -x devtmpfs
echo -e "\n==== Top 5 Processes by CPU Usage ====" echo -e "\n==== Top 5 Processes by CPU Usage ===="
# Display header and top 5 CPU-consuming processes # Display header and top 5 CPU-consuming processes
ps -eo pid,user,comm,%cpu --sort=-%cpu | head -n 6 ps -eo pid,user,comm,%cpu --sort=-%cpu | head -n 6 || true
echo -e "\n==== Top 5 Processes by Memory Usage ====" echo -e "\n==== Top 5 Processes by Memory Usage ===="
ps -eo pid,user,comm,%mem --sort=-%mem | head -n 6 ps -eo pid,user,comm,%mem --sort=-%mem | head -n 6 || true