Skip to content

Instantly share code, notes, and snippets.

@sderosiaux
Last active March 8, 2026 03:49
Show Gist options
  • Select an option

  • Save sderosiaux/f1fedb2b6f6c1d75d398ca88b212d124 to your computer and use it in GitHub Desktop.

Select an option

Save sderosiaux/f1fedb2b6f6c1d75d398ca88b212d124 to your computer and use it in GitHub Desktop.
Claude Code command: thorough Linux machine health assessment (security, processes, disk, network, users, services, compromise indicators)

Machine Health Assessment: $ARGUMENTS

Thorough local machine audit: security, processes, disk, network, users, services, compromise indicators.

Setup: REPORT_DIR=$(mktemp -d /tmp/machine-health-XXXXX) && chmod 700 "$REPORT_DIR" && echo "Report: $REPORT_DIR"

Target

$ARGUMENTS Behavior
(empty) Full assessment, all phases
network disk security users processes services docker Deep-dive that area, baseline others
quick Phases 1, 3, 4, 5 only — fast triage

Phase 0: Prerequisites

Detect environment before running phases:

# Init system
pidof systemd >/dev/null && echo "systemd" || echo "other"
# Firewall backend
nft list ruleset &>/dev/null && echo "nftables" || (iptables -L &>/dev/null && echo "iptables" || echo "none")
# Container runtime
command -v docker &>/dev/null && echo "docker" || (command -v podman &>/dev/null && echo "podman" || echo "none")
# Sudo
sudo -n true 2>/dev/null && echo "sudo:yes" || echo "sudo:limited"
# Package manager
command -v apt-get &>/dev/null && echo "apt" || (command -v dnf &>/dev/null && echo "dnf" || echo "other")

Record capabilities. Note any limitations in the report.


Phase 1: System Overview

Collect in parallel:

Check Command
Uptime & load uptime
Kernel uname -r
OS head -4 /etc/os-release
RAM free -h
Swap usage swapon --show
CPU count nproc
Hostname hostname -f
Time sync timedatectl status 2>/dev/null | rg -i 'synchro|service|time zone'
Clock source chronyc tracking 2>/dev/null || ntpq -pn 2>/dev/null
Kernel taint cat /proc/sys/kernel/tainted (0 = clean)
Recent kernel errors journalctl -p err --no-pager --since '24h ago' 2>/dev/null | tail -20
OOM kills journalctl -k --no-pager -g 'oom|Out of memory' --since '7 days ago' 2>/dev/null | tail -10
DNS resolution timeout 5 dig +short example.com && echo "DNS OK" || echo "DNS FAIL"

Flag: load average > nproc, swap > 50% used, uptime < 1 day (unexpected reboot?), NTP not synchronized, kernel tainted (non-zero), OOM kills present, DNS resolution failure


Phase 2: Users & Access

Collect in parallel:

Check Command
Currently logged in who
UID 0 accounts awk -F: '$3 == 0' /etc/passwd
Interactive users (UID >= 1000) awk -F: '$3 >= 1000 && $7 !~ /nologin|false/' /etc/passwd
Recent logins last -15
Failed logins sudo lastb -15 2>/dev/null
SSH authorized_keys For each interactive user, check ~user/.ssh/authorized_keys — count keys, show fingerprints with ssh-keygen -lf
Sudoers sudo grep -v '^#|^$' /etc/sudoers 2>/dev/null; sudo cat /etc/sudoers.d/* 2>/dev/null | grep -v '^#|^$'
Password status For each interactive user: sudo passwd -S <user>

Flag:

  • Multiple UID 0 accounts
  • Unknown interactive users
  • SSH keys you don't recognize (show fingerprints)
  • Unexpected sudoers entries
  • Logins from unknown IPs (whois + reverse DNS on any unfamiliar IP)
  • Failed login bursts from same IP

Phase 3: Processes

Collect in parallel:

Check Command
Top CPU consumers ps aux --sort=-%cpu | head -20
Top MEM consumers ps aux --sort=-%mem | head -20
Zombie processes ps aux | awk '$8 ~ /Z/'
Long-running processes ps -eo pid,etime,comm --sort=-etime | head -20
Kernel threads (D state) ps aux | awk '$8 ~ /D/'
Deleted binaries ls -l /proc/*/exe 2>/dev/null | grep deleted
FD counts (top 10) for p in $(ls /proc/ | rg '^\d+$' | head -200); do echo "$(ls /proc/$p/fd 2>/dev/null | wc -l) $p $(cat /proc/$p/comm 2>/dev/null)"; done | sort -rn | head -10

Flag:

  • Any process using > 80% CPU sustained
  • Zombies (parent not reaping)
  • Suspicious process names (crypto miners: xmrig, kdevtmpfsi, kinsing, solr, etc.)
  • Unknown processes running as root
  • Processes with deleted binaries = CRITICAL
  • Any process with > 10,000 open FDs (leak)
  • For every suspicious process: check ls -l /proc/<pid>/exe, cat /proc/<pid>/cmdline, fd count

Gate 1: Early Threat Assessment

Review findings from Phases 1-3. If ANY of these are true:

  • Unknown UID 0 accounts or unauthorized SSH keys
  • Processes with deleted binaries or known miner signatures
  • Outbound connections to C2 ports or unknown IPs with high traffic

Escalate: Log evidence to $REPORT_DIR/evidence/, note timestamps, do NOT terminate suspicious processes (preserves forensic state). Tag report verdict as COMPROMISED early. Continue remaining phases with forensic lens.

Otherwise: Continue normally.


Phase 4: Network

Collect in parallel:

Check Command
Listening ports ss -tlnp
Established connections ss -tnp state established
UDP listeners ss -ulnp
Firewall (nftables) sudo nft list ruleset 2>/dev/null
Firewall (iptables fallback) sudo iptables -L -n --line-numbers 2>/dev/null; sudo ip6tables -L -n --line-numbers 2>/dev/null
DNS config cat /etc/resolv.conf
/etc/hosts cat /etc/hosts
Routing ip route show
Interfaces ip -br addr
ARP table ip neigh show

Flag:

  • Unexpected listening ports (cross-reference with known services, don't just flag >8000)
  • Outbound connections to unknown IPs (whois suspicious ones)
  • No firewall rules at all (nft + iptables both empty)
  • Connections to known-bad ports: 1337, 4444, 5555, 6666, 9090 (common C2)
  • DNS pointing to unexpected resolvers
  • /etc/hosts entries pointing known domains to unexpected IPs = CRITICAL
  • ARP anomalies (duplicate MACs, unexpected gateways)

Phase 5: Disk & Filesystem

Collect in parallel:

Check Command
Disk usage df -h --total | grep -v tmpfs
Inode usage df -i | grep -v tmpfs
Top space consumers timeout 30 dust -n 15 -d 3 / 2>/dev/null || du -sh /* 2>/dev/null | sort -rh | head -15
/tmp total + contents du -sh /tmp/ 2>/dev/null; du -sh /tmp/* 2>/dev/null | sort -rh | head -15
/tmp live files sudo lsof +D /tmp 2>/dev/null | awk '{print $NF}' | sort -u | rg '^/tmp/'
/tmp stale (>7d) find /tmp -maxdepth 1 -mindepth 1 -mtime +7 ! -name 'systemd-private-*' -exec du -sh {} + 2>/dev/null | sort -rh
/var/log size du -sh /var/log/ && du -sh /var/log/* 2>/dev/null | sort -rh | head -10
Large files (7d) find / -xdev -type f -mtime -7 -size +100M 2>/dev/null | head -20
Open deleted files sudo lsof +L1 2>/dev/null | head -20
World-writable dirs find / -xdev -type d -perm -0002 ! -path '/tmp/*' ! -path '/var/tmp/*' ! -path '/dev/*' ! -path '/proc/*' ! -path '/sys/*' 2>/dev/null | head -10
Filesystem layout lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,RO

Flag:

  • Any partition > 85% used
  • Inode exhaustion (> 90%)
  • /tmp total > 500MB = investigate. Enumerate every entry, cross-reference with lsof live list
  • For each /tmp entry: if NOT in lsof live list AND older than 7 days → mark as STALE, include in cleanup recommendation with exact size
  • Present stale /tmp items as an explicit itemized cleanup list in the report, not a vague "some files in /tmp"
  • Deleted files still held open (space not reclaimed)
  • Unexpected large files in user directories

Phase 6: Services & Persistence

Collect in parallel:

Check Command
Failed systemd units systemctl list-units --state=failed --no-pager
Enabled services systemctl list-unit-files --state=enabled --no-pager
User crontabs for u in $(awk -F: '$3>=1000{print $1}' /etc/passwd); do echo "=== $u ==="; sudo crontab -u $u -l 2>/dev/null; done
Root crontab sudo crontab -l 2>/dev/null
System cron ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/ 2>/dev/null
Systemd timers systemctl list-timers --no-pager
At jobs atq 2>/dev/null
Systemd overrides find /etc/systemd/system -name 'override.conf' -o -name '*.d' -type d 2>/dev/null
Non-standard services find /etc/systemd/system /run/systemd/system -name '*.service' -newer /etc/os-release 2>/dev/null
Init scripts ls /etc/init.d/ 2>/dev/null
rc.local cat /etc/rc.local 2>/dev/null
Profile backdoors rg -l 'curl|wget|nc |python.*-c|bash.*-i|/dev/tcp' /etc/profile.d/ /etc/environment ~/.bashrc ~/.bash_profile ~/.profile 2>/dev/null
Motd scripts ls -la /etc/update-motd.d/ 2>/dev/null
Udev rules ls /etc/udev/rules.d/ 2>/dev/null

Flag:

  • Failed units (especially security: fail2ban, ufw, apparmor, auditd)
  • Unknown cron entries
  • Cron jobs downloading or executing from URLs
  • Timers running unexpected scripts
  • Services created after OS install (newer than /etc/os-release)
  • ExecStart pointing outside /usr/ paths
  • Shell profiles containing curl/wget/nc/python -c patterns = CRITICAL
  • rc.local with non-trivial content

Phase 7: Security Posture

Collect in parallel:

Check Command
SSH config (full) rg -v '^\s*#|^\s*$' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null
fail2ban status sudo fail2ban-client status 2>/dev/null && for j in $(sudo fail2ban-client status 2>/dev/null | grep 'Jail list' | sed 's/.*:\s*//;s/,/ /g'); do sudo fail2ban-client status $j; done
SUID binaries find / -xdev -perm -4000 -type f 2>/dev/null | head -30
SGID binaries find / -xdev -perm -2000 -type f 2>/dev/null | head -20
ASLR cat /proc/sys/kernel/randomize_va_space (should be 2)
Kernel hardening sysctl kernel.kptr_restrict kernel.dmesg_restrict kernel.yama.ptrace_scope net.ipv4.tcp_syncookies net.ipv4.conf.all.rp_filter fs.protected_hardlinks fs.protected_symlinks 2>/dev/null
Unattended upgrades dpkg -l unattended-upgrades 2>/dev/null | tail -1; cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null
Pending updates apt-get -s upgrade 2>/dev/null | grep ^Inst | head -20
AppArmor/SELinux aa-status 2>/dev/null || getenforce 2>/dev/null || echo "No MAC"
Package integrity debsums -c 2>/dev/null | head -30
Log integrity wc -l /var/log/auth.log /var/log/syslog 2>/dev/null; find /var/log -name '*.log' -empty 2>/dev/null; journalctl --verify 2>&1 | tail -5
Log permissions stat -c '%a %U %G %n' /var/log/auth.log /var/log/syslog 2>/dev/null

Flag:

  • SSH: PermitRootLogin yes, PasswordAuthentication yes (without fail2ban), MaxAuthTries > 4, AllowTcpForwarding yes, X11Forwarding yes on servers, AuthorizedKeysCommand pointing to unexpected binary
  • fail2ban not running
  • Unusual SUID binaries outside /usr/bin, /usr/sbin, /usr/lib
  • ASLR disabled (randomize_va_space != 2)
  • Kernel hardening params at insecure defaults
  • Pending security patches
  • No unattended-upgrades configured
  • No MAC (AppArmor/SELinux) active
  • Package integrity failures on security binaries (sshd, sudo, login, su, passwd) = CRITICAL
  • auth.log < 100 lines on server with uptime > 7 days = logs likely cleared
  • Empty .log files that should have content
  • Journal verification failures

Phase 8: Compromise Indicators

Collect in parallel:

Check Command
LD_PRELOAD hijack cat /etc/ld.so.preload 2>/dev/null; rg LD_PRELOAD /etc/environment /etc/profile.d/ ~/.bashrc 2>/dev/null
Shared library config rg -v '^#|^$' /etc/ld.so.conf.d/*.conf 2>/dev/null
PAM integrity dpkg -V libpam-modules 2>/dev/null; ls -lt /etc/pam.d/ | head -15
Kernel modules lsmod | head -30
Rootkit scan sudo rkhunter --check --skip-keypress --report-warnings-only 2>/dev/null | tail -30
/dev/shm contents ls -la /dev/shm/
Hidden files in / ls -la / | grep '^\.'
Process vs /proc count echo "ps: $(ps -e --no-headers | wc -l) /proc: $(ls -d /proc/[0-9]* | wc -l)"
Recently modified sys bins find /usr/bin /usr/sbin -xdev -mtime -7 -type f 2>/dev/null | head -20
Unusual /usr/local ls -lt /usr/local/bin/ /usr/local/sbin/ 2>/dev/null | head -20
Immutable files lsattr -R /etc/ 2>/dev/null | rg '\-i\-' | head -10
Cert expiry (30d) find /etc/ssl /etc/letsencrypt -name '*.pem' -o -name '*.crt' 2>/dev/null | head -20 | xargs -I{} sh -c 'openssl x509 -enddate -noout -in "{}" 2>/dev/null && echo " {}"'
Audit log (recent) sudo ausearch -ts recent --raw 2>/dev/null | aureport -au --summary 2>/dev/null | tail -20; sudo auditctl -l 2>/dev/null

Flag:

  • ANY content in /etc/ld.so.preload = CRITICAL
  • LD_PRELOAD in environment or profiles = CRITICAL
  • /dev/shm containing executables or scripts = CRITICAL
  • PAM module integrity failures = CRITICAL
  • Process count discrepancy > 5 = investigate hidden processes
  • Recently modified binaries in /usr/bin, /usr/sbin (not from apt) = CRITICAL
  • Unknown kernel modules (compare against dpkg -S for each)
  • rkhunter warnings (rootkit signatures, modified binaries, hidden ports)
  • Certificates expiring within 30 days
  • Audit rules missing or auditd not running (no syscall visibility)

Phase 9: Docker / Containers

Only run if command -v docker &>/dev/null || command -v podman &>/dev/null:

Check Command
Running containers docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
All containers docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
Images docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}'
Disk usage docker system df
Privileged docker ps -q | xargs -I{} docker inspect {} --format '{{.Name}} privileged={{.HostConfig.Privileged}}'
Socket mounts docker ps -q | xargs -I{} docker inspect {} --format '{{.Name}} {{range .Mounts}}{{if eq .Source "/var/run/docker.sock"}}DOCKER_SOCKET{{end}}{{end}}'
Host namespaces docker ps -q | xargs -I{} docker inspect {} --format '{{.Name}} pid={{.HostConfig.PidMode}} ipc={{.HostConfig.IpcMode}} net={{.HostConfig.NetworkMode}}'
Container user docker ps -q | xargs -I{} docker inspect {} --format '{{.Name}} user={{.Config.User}}'
Daemon config cat /etc/docker/daemon.json 2>/dev/null | jq -c '.'
Restart loops docker ps -a --format '{{.Names}} {{.Status}}' | rg -i 'restarting'

Flag:

  • Containers running as privileged = CRITICAL
  • Docker socket mounted into container = CRITICAL
  • Host PID/IPC namespace = HIGH
  • Old stopped containers wasting space
  • Dangling images/volumes
  • Unknown images
  • Insecure registries in daemon.json
  • Containers in restart loop

Phase 10: Report

Write to $REPORT_DIR/report.md:

## Machine Health Report — [hostname] — [date]

### Verdict: [CLEAN / NEEDS ATTENTION / COMPROMISED]

### Summary
| Area | Status | Issues |
|------|--------|--------|
| System | OK/WARN/CRIT | ... |
| Users & Access | OK/WARN/CRIT | ... |
| Processes | OK/WARN/CRIT | ... |
| Network | OK/WARN/CRIT | ... |
| Disk | OK/WARN/CRIT | ... |
| Services | OK/WARN/CRIT | ... |
| Security | OK/WARN/CRIT | ... |
| Compromise | OK/WARN/CRIT | ... |
| Containers | OK/WARN/CRIT/N/A | ... |

### Findings (by severity)

#### CRITICAL
[findings requiring immediate action]

#### WARNING
[findings requiring attention]

#### INFO
[observations, no action needed]

### Recommended Actions
[ordered list of fixes, most urgent first]

### Methodology
- Phases executed: [list]
- Focus area: [from $ARGUMENTS or "full"]
- Sudo available: [yes/no — which checks were skipped]
- Report: $REPORT_DIR

Also display the report inline.


Rules

  1. Run all checks — never skip a phase (unless quick mode)
  2. Maximize parallel execution within each phase
  3. Use sudo only when needed, note if sudo is unavailable and which checks were skipped
  4. For every unknown IP found: run whois + reverse DNS
  5. For every suspicious process: check binary path (ls -l /proc/<pid>/exe), open files, and cmdline
  6. Compare SUID binaries against known-good Debian/Ubuntu defaults — flag anything unusual
  7. Don't just collect data — interpret it. Every finding needs a severity and actionable recommendation
  8. If $ARGUMENTS is provided, focus on that area but still run baseline checks on other areas
  9. Use compact CLI flags per CLAUDE.md conventions
  10. Use local tools (fd, rg, dust, procs) when available, fall back to standard tools. Do NOT use fd for permission-based filtering — use find with -perm instead
  11. Wrap any filesystem-wide scan in timeout 30 to prevent hangs on NFS/large storage. Always use -xdev with find from /
  12. OPSEC: If compromise is suspected mid-assessment, avoid commands that generate outbound network traffic (whois, apt update). Flag IPs for offline analysis instead. Do not terminate suspicious processes — preserve forensic state
  13. Respect Gate 1 — if early phases indicate compromise, switch to forensic lens for remaining phases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment