Skip to content

Instantly share code, notes, and snippets.

@lukapaunovic
Last active November 1, 2025 04:32
Show Gist options
  • Select an option

  • Save lukapaunovic/8ec3716a5be907f7afcfcf120a6e1a98 to your computer and use it in GitHub Desktop.

Select an option

Save lukapaunovic/8ec3716a5be907f7afcfcf120a6e1a98 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# kill_foreign_sessions.sh
# Kills session leader PIDs from `who -u` whose remote IP is not the allowed IP.
# DEFAULT: dry-run mode. Set DRY_RUN=0 to actually kill.
#
# Usage:
# DRY_RUN=1 sudo /usr/local/bin/kill_foreign_sessions.sh # just show what WOULD be killed
# DRY_RUN=0 sudo /usr/local/bin/kill_foreign_sessions.sh # perform kills
ALLOWED_IP="91.150.72.133"
DRY_RUN="${DRY_RUN:-1}" # 1 = dry run (default), 0 = actually kill
LOG="/var/log/kill_foreign_sessions.log"
timestamp() { printf '%s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')"; }
echo "=== $(timestamp) run (DRY_RUN=${DRY_RUN}) ===" >> "$LOG"
# Parse `who -u` in a robust way:
# typical line: user tty YYYY-MM-DD HH:MM idle pid (host)
# We'll only process lines with at least 7 fields and extract pid ($6) and host ($7).
who -u | awk 'NF>=7 { pid=$6; host=$7; gsub(/^\(|\)$/,"",host); print pid " " host }' |
while read -r pid host; do
# Validate pid numeric
if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
echo "$(timestamp) skipping non-numeric pid=\"$pid\" host=\"$host\"" >> "$LOG"
continue
fi
# If no host (local console), skip
if [[ -z "$host" || "$host" == "-" ]]; then
echo "$(timestamp) skipping pid $pid (no remote host)" >> "$LOG"
continue
fi
# Preserve allowed IP
if [[ "$host" == "$ALLOWED_IP" ]]; then
echo "$(timestamp) preserving pid $pid from allowed IP $host" >> "$LOG"
continue
fi
# Safety: don't touch system-critical PIDs
if (( pid <= 1 )); then
echo "$(timestamp) skipping unsafe pid $pid" >> "$LOG"
continue
fi
# Check /proc exists
if [ ! -d "/proc/$pid" ]; then
echo "$(timestamp) pid $pid not present in /proc, skipping" >> "$LOG"
continue
fi
# Read cmdline (may be empty for kernel threads)
cmdline=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true)
exe=$(readlink -f "/proc/$pid/exe" 2>/dev/null || true)
summary="${cmdline:-$exe}"
# Extra safety: skip if likely critical system process
lower=$(printf '%s' "$summary" | tr '[:upper:]' '[:lower:]')
if printf '%s' "$lower" | grep -qE '(^|/)(sshd|systemd|init|cron|rsyslogd|mysqld|mariadbd|postgres|nginx|httpd|apache2|php-fpm)'; then
echo "$(timestamp) skipping pid $pid because it looks critical: $summary" >> "$LOG"
continue
fi
# Log what would be done / will be done
echo "$(timestamp) target pid=$pid host=$host cmd=\"$summary\"" | tee -a "$LOG"
if [[ "$DRY_RUN" -eq 0 ]]; then
# polite terminate, then force after timeout
kill -TERM "$pid" 2>/dev/null || true
sleep 4
if kill -0 "$pid" 2>/dev/null; then
echo "$(timestamp) pid $pid still alive, sending KILL" >> "$LOG"
kill -KILL "$pid" 2>/dev/null || true
sleep 1
fi
if kill -0 "$pid" 2>/dev/null; then
echo "$(timestamp) FAILED to kill pid $pid" >> "$LOG"
else
echo "$(timestamp) pid $pid terminated" >> "$LOG"
fi
fi
done
echo "=== $(timestamp) finished ===" >> "$LOG"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment