Thorough local machine audit: security, processes, disk, network, users, services, compromise indicators. Cross-platform: Linux (systemd) and macOS (Darwin).
Setup: REPORT_DIR=$(mktemp -d /tmp/machine-health-XXXXX) && chmod 700 "$REPORT_DIR" && echo "Report: $REPORT_DIR"
| $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 |
Detect environment before running phases. All subsequent phases MUST branch on $OS.
OS=$(uname -s) # "Darwin" or "Linux"
echo "OS: $OS"
if [ "$OS" = "Darwin" ]; then
echo "macOS $(sw_vers -productVersion) ($(uname -m))"
# Container runtime
command -v docker &>/dev/null && echo "docker" || echo "no-docker"
# Sudo
sudo -n true 2>/dev/null && echo "sudo:yes" || echo "sudo:limited"
# Homebrew
command -v brew &>/dev/null && echo "brew:yes" || echo "brew:no"
else
# 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")
fiRecord capabilities. Note any limitations in the report.
| 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" |
| Check | Command |
|---|---|
| Uptime & load | uptime |
| Kernel | uname -r |
| OS version | sw_vers |
| Hardware | sysctl -n machdep.cpu.brand_string; uname -m |
| RAM | sysctl -n hw.memsize | awk '{printf "%.1f GB\n", $1/1073741824}' |
| Swap usage | sysctl vm.swapusage |
| CPU count | sysctl -n hw.ncpu |
| Hostname | scutil --get ComputerName; scutil --get LocalHostName |
| Time sync | sntp -d time.apple.com 2>&1 | head -5 |
| Chip info | system_profiler SPHardwareDataType 2>/dev/null | grep -E 'Chip|Memory|Serial|Model' |
| Recent kernel errors | log show --predicate 'messageType == error' --last 24h --style compact 2>/dev/null | tail -20 |
| Recent panics | ls -lt /Library/Logs/DiagnosticReports/kernel* /Library/Logs/DiagnosticReports/*.panic 2>/dev/null | head -5 |
| DNS resolution | timeout 5 dig +short example.com && echo "DNS OK" || echo "DNS FAIL" |
Flag (both):
- Load average > CPU count, swap heavily used, uptime < 1 day (unexpected reboot?), NTP not synchronized, DNS failure
Flag (macOS extra):
- Hostname contains real name (e.g., "John's MacBook") — leaks identity on local networks
- Intel CPU detected — hardware-level vulnerabilities (checkm8) that cannot be patched
- Kernel panics in the last 7 days
| 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> |
| Check | Command |
|---|---|
| Currently logged in | who |
| Admin group members | dscl . -read /Groups/admin GroupMembership |
| All local users | dscl . -list /Users UniqueID | awk '$2 >= 500' |
| Recent logins | last -15 |
| Failed logins | log show --predicate 'eventMessage contains "authentication failure"' --last 7d --style compact 2>/dev/null | tail -15 |
| SSH authorized_keys | For each 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 '^#|^$' |
| Sudoers HOME env | grep -n 'env_keep.*HOME' /etc/sudoers /etc/sudoers.d/* 2>/dev/null |
| TouchID sudo | grep 'pam_tid' /etc/pam.d/sudo 2>/dev/null |
| Remote Login (SSH) | sudo systemsetup -getremotelogin 2>/dev/null |
| Remote Apple Events | sudo systemsetup -getremoteappleevents 2>/dev/null |
Flag (both):
- Unknown interactive users, SSH keys you don't recognize, unexpected sudoers entries
- Logins from unknown IPs (whois + reverse DNS on unfamiliar IPs)
Flag (macOS extra):
- Unexpected users in admin group
Defaults env_keep += "HOME"not commented out — sudo preserves user HOME, allowing malware in ~/.zshrc to run as root- Remote Login ON (unless deliberate)
- Remote Apple Events ON
| 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 |
| Check | Command |
|---|---|
| Top CPU consumers | ps aux -r | head -20 |
| Top MEM consumers | ps aux -m | head -20 |
| Zombie processes | ps aux | awk '$8 ~ /Z/' |
| Long-running processes | ps -eo pid,etime,comm | sort -k2 -r | head -20 |
| Open FD counts (top 10) | sudo lsof -n 2>/dev/null | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 |
| All open network connections | sudo lsof -Pni | head -40 |
Flag (both):
- Any process using > 80% CPU sustained
- Zombies (parent not reaping)
- Suspicious process names (crypto miners: xmrig, kdevtmpfsi, kinsing, solr)
- Unknown processes running as root
Flag (Linux extra):
- Processes with deleted binaries = CRITICAL
- Any process with > 10,000 open FDs (leak)
Review findings from Phases 1-3. If ANY of these are true:
- Unknown admin/root 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.
| 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 |
| Check | Command |
|---|---|
| Listening ports | sudo lsof -Pni | grep LISTEN |
| Established connections | sudo lsof -Pni | grep ESTABLISHED |
| UDP listeners | sudo lsof -Pni UDP |
| Application Firewall state | /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate |
| Stealth mode | /usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode |
| Allow signed apps | /usr/libexec/ApplicationFirewall/socketfilterfw --getallowsigned |
| pf firewall status | sudo pfctl -s info 2>/dev/null | head -5 |
| pf rules | sudo pfctl -s rules 2>/dev/null |
| DNS config | scutil --dns | head -30 |
| Wi-Fi DNS servers | networksetup -getdnsservers "Wi-Fi" 2>/dev/null |
| Proxy settings | scutil --proxy |
| /etc/hosts | cat /etc/hosts |
| Routing | netstat -rn | head -20 |
| Interfaces | ifconfig -l; ifconfig | grep -E 'flags|inet ' |
| ARP table | arp -an |
| Wi-Fi info | /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I 2>/dev/null | grep -E 'SSID|security|agrCtlRSSI' |
| Known Wi-Fi networks | networksetup -listpreferredwirelessnetworks en0 2>/dev/null |
| mDNS/Bonjour advertising | defaults read /Library/Preferences/com.apple.mDNSResponder.plist NoMulticastAdvertisements 2>/dev/null |
| DNS leak check (UDP 53) | sudo lsof +c 15 -Pni UDP:53 2>/dev/null |
Flag (both):
- Unexpected listening ports (cross-reference with known services)
- Outbound connections to unknown IPs (whois suspicious ones)
- No firewall rules at all
- 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)
Flag (macOS extra):
- Application Firewall disabled = WARN
- Stealth mode disabled = WARN
- pf firewall disabled with no rules = WARN
- Auto-allow signed apps enabled (socketfilterfw --getallowsigned = on)
- mDNS advertising enabled (NoMulticastAdvertisements != 1) — leaks service presence on LAN
- Wi-Fi connected to open/WPA2 network instead of WPA3
- Hidden networks in preferred list (cause directed probe broadcasts)
- Proxy configured unexpectedly (traffic interception risk)
| 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 |
| Check | Command |
|---|---|
| Disk usage | df -h | grep -v devfs |
| 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 stale (>7d) | find /tmp -maxdepth 1 -mindepth 1 -mtime +7 -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 |
| FileVault status | fdesetup status |
| FileVault users | sudo fdesetup list 2>/dev/null |
| Disk layout | diskutil list |
| APFS status | diskutil apfs list 2>/dev/null | head -30 |
| Spotlight indexing | sudo mdutil -s / 2>/dev/null |
Flag (both):
- Any partition > 85% used
- /tmp total > 500MB = investigate; enumerate every entry, cross-reference with lsof
- For each /tmp entry: if NOT in lsof live list AND older than 7 days -> mark STALE, include in cleanup recommendation
- Deleted files still held open (space not reclaimed)
- Unexpected large files in user directories
Flag (Linux extra):
- Inode exhaustion (> 90%)
Flag (macOS extra):
- FileVault OFF = CRITICAL (disk not encrypted)
- Unexpected users in FileVault authorized list
- Spotlight indexing on sensitive volumes (privacy concern)
| 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 |
| Check | Command |
|---|---|
| User Launch Agents | ls -la ~/Library/LaunchAgents/ 2>/dev/null |
| System Launch Agents | ls -la /Library/LaunchAgents/ 2>/dev/null |
| System Launch Daemons | ls -la /Library/LaunchDaemons/ 2>/dev/null |
| Third-party persistence detail | find ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons -name '*.plist' 2>/dev/null | while read f; do echo "=== $f ==="; defaults read "$f" 2>/dev/null | grep -E 'Label|Program|RunAtLoad'; done |
| Login Items | osascript -e 'tell application "System Events" to get the name of every login item' 2>/dev/null |
| Running launch services | launchctl list | head -30 |
| System daemons | sudo launchctl list 2>/dev/null | head -30 |
| User crontabs | crontab -l 2>/dev/null |
| Root crontab | sudo crontab -l 2>/dev/null |
| At jobs | atq 2>/dev/null |
| Profile backdoors | rg -l 'curl|wget|nc |python.*-c|bash.*-i|/dev/tcp' ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile ~/.profile /etc/profile /etc/profile.d/ 2>/dev/null |
| Kernel extensions | kextstat 2>/dev/null | grep -v com.apple |
| System extensions | systemextensionsctl list 2>/dev/null |
Flag (Linux):
- Failed units (especially security: fail2ban, ufw, apparmor, auditd)
- Unknown cron entries, cron jobs downloading/executing from URLs
- 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
Flag (macOS):
- Any plist in ~/Library/LaunchAgents or /Library/LaunchDaemons with
Programpointing outside /System, /usr, /Applications = suspicious - RunAtLoad = true on unknown agents = WARN
- Unknown Login Items
- Unknown kernel extensions (non-Apple kexts) = HIGH (kernel-level privilege)
- Unknown system extensions
- Shell profiles containing curl/wget/nc/python -c patterns = CRITICAL
| 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 |
| Check | Command |
|---|---|
| SIP status | csrutil status |
| Gatekeeper status | spctl --status |
| FileVault (reiterate) | fdesetup status |
| Secure Boot / LocalPolicy | bputil -d 2>/dev/null | head -10 |
| SSH config | rg -v '^\s*#|^\s*$' /etc/ssh/sshd_config 2>/dev/null |
| Remote Login | sudo systemsetup -getremotelogin 2>/dev/null |
| Screen Sharing | sudo launchctl list com.apple.screensharing 2>/dev/null |
| Pending updates | softwareupdate -l 2>/dev/null |
| Auto-update: check enabled | defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null |
| Auto-update: auto download | defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticDownload 2>/dev/null |
| Auto-update: auto install | defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticallyInstallMacOSUpdates 2>/dev/null |
| Auto-update: critical patches | defaults read /Library/Preferences/com.apple.SoftwareUpdate CriticalUpdateInstall 2>/dev/null |
| Screensaver password | defaults read com.apple.screensaver askForPassword 2>/dev/null |
| Screensaver delay | defaults read com.apple.screensaver askForPasswordDelay 2>/dev/null |
| Show file extensions | defaults read NSGlobalDomain AppleShowAllExtensions 2>/dev/null |
| Save to iCloud default | defaults read NSGlobalDomain NSDocumentSaveNewDocumentsToCloud 2>/dev/null |
| Crash reporter | defaults read com.apple.CrashReporter DialogType 2>/dev/null |
| Sudoers file perms | ls -la /etc/sudoers |
| Umask | launchctl config user umask 2>/dev/null |
| SUID binaries (non-Apple) | find / -xdev -perm -4000 -type f 2>/dev/null | grep -v '^/System|^/usr/bin|^/usr/sbin|^/usr/libexec' | head -20 |
Flag (Linux):
- SSH: PermitRootLogin yes, PasswordAuthentication yes (without fail2ban), MaxAuthTries > 4
- 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
- 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
Flag (macOS):
- SIP disabled = CRITICAL
- Gatekeeper disabled = CRITICAL
- FileVault off = CRITICAL
- Reduced Security boot policy = HIGH
- Remote Login on, Screen Sharing on (unless deliberate)
- Pending software updates = WARN; pending security updates = HIGH
- Any auto-update setting = 0 (disabled) = WARN
- Screensaver askForPassword != 1 = WARN (no lock on screensaver)
- Screensaver askForPasswordDelay > 0 = WARN (grace period before lock)
- AppleShowAllExtensions != 1 = WARN (hidden extensions enable masquerading attacks like Evil.jpg.app)
- NSDocumentSaveNewDocumentsToCloud = 1 = INFO (files auto-uploaded to iCloud)
- CrashReporter DialogType != "none" = INFO (crash data sent to Apple)
- Umask != 077 = WARN (default 022 means newly created files readable by group/others)
- Non-standard SUID binaries outside /System, /usr = suspicious
| 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 |
| Check | Command |
|---|---|
| DYLD_INSERT_LIBRARIES hijack | rg DYLD_INSERT_LIBRARIES ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile ~/.profile /etc/profile /etc/profile.d/ /etc/environment 2>/dev/null |
| DYLD env in running procs | ps -eww -o command | grep DYLD 2>/dev/null |
| Hidden files in / | ls -la / | grep '^\.' |
| Recently modified sys bins | find /usr/local/bin /usr/local/sbin -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 |
| Non-Apple kernel extensions | kextstat 2>/dev/null | grep -v com.apple |
| Quarantine flags on downloads | ls -l@ ~/Downloads/*.app 2>/dev/null | head -20 |
| Bluetooth device history | defaults read /Library/Preferences/com.apple.Bluetooth.plist DeviceCache 2>/dev/null | head -20 |
| OpenBSM audit (recent) | sudo praudit -l /dev/auditpipe 2>/dev/null &; sleep 3; kill %1 2>/dev/null |
| PAM config integrity | ls -lt /etc/pam.d/ | head -15 |
| Cert expiry (system) | security find-certificate -a -p /System/Library/Keychains/SystemRootCertificates.keychain 2>/dev/null | awk '/BEGIN CERT/,/END CERT/' | openssl x509 -enddate -noout 2>/dev/null | head -5 |
| Third-party certs in keychain | security find-certificate -a /Library/Keychains/System.keychain 2>/dev/null | grep '"alis"' | head -20 |
Flag (Linux):
- 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 -Sfor each) - rkhunter warnings
- Certificates expiring within 30 days
- Audit rules missing or auditd not running
Flag (macOS):
- DYLD_INSERT_LIBRARIES in any profile or environment = CRITICAL (library injection)
- DYLD_INSERT_LIBRARIES in running process environment = CRITICAL
- Non-Apple kernel extensions loaded = HIGH
- Recently modified binaries in /usr/local (not from brew) = suspicious
- Missing quarantine flags on downloaded .app bundles = WARN (may have been tampered)
- Unknown third-party certificates in system keychain = HIGH (MITM risk)
- Recently modified PAM config = investigate
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 '.' || cat ~/.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
Write to $REPORT_DIR/report.md:
## Machine Health Report — [hostname] — [date] — [OS]
### 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
- OS detected: [Darwin/Linux]
- 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.
- Run all checks — never skip a phase (unless
quickmode) - Branch every phase on
$OS— never run Linux commands on macOS or vice versa - Maximize parallel execution within each phase
- Use
sudoonly when needed, note if sudo is unavailable and which checks were skipped - For every unknown IP found: run
whois+ reverse DNS - For every suspicious process: check binary path, open files, and cmdline
- Compare SUID binaries against known-good OS defaults — flag anything unusual
- Don't just collect data — interpret it. Every finding needs a severity and actionable recommendation
- If $ARGUMENTS is provided, focus on that area but still run baseline checks on other areas
- Use compact CLI flags per CLAUDE.md conventions
- 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
- Wrap any filesystem-wide scan in
timeout 30to prevent hangs on NFS/large storage. Always use-xdevwith find from / - 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
- Respect Gate 1 — if early phases indicate compromise, switch to forensic lens for remaining phases
- On macOS, prefer
defaults readfor checking settings,launchctlfor services,scutil/networksetupfor network config - On macOS,
softwareupdate -lcan be slow — run it in background if doing full assessment