Posted on
Artificial Intelligence

Building an Artificial Intelligence Security Assistant for Linux

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Building an Artificial Intelligence Security Assistant for Linux (with Bash)

You’re drowning in logs. Attackers automate their scans and brute-force attempts; you manually triage alerts at 2 a.m. What if a small Bash-driven assistant could collect the right signals, summarize what matters using an LLM (local or cloud), and automatically take safe first steps like blocking an IP or pinging you?

This article shows you how to build a Linux AI Security Assistant using nothing but Bash, familiar CLI tools, and an optional LLM. You’ll get practical install steps (apt, dnf, zypper), real scripts, and a runnable systemd timer to keep your host safer without babysitting it.

Why this matters

  • Signal-to-noise: System logs are verbose. A lightweight assistant can condense multi-source telemetry into an actionable, human-sized summary.

  • Time to response: Basic playbooks (block source IPs, notify admins, kick off a scan) are repetitive. Automate the boring parts safely.

  • Privacy and control: Use a local model (e.g., Ollama) when you can, or a cloud API when you must. Your data, your rules.

  • Linux-native: Everything here runs on Bash with standard tools—no heavyweight SIEM required to get value.

What you’ll build

  • Sensors: Collect recent SSH failures, active network connections, and optional malware scan outputs.

  • Normalizer: Convert raw logs into a compact JSON bundle.

  • AI brain (optional): Send the bundle to a local or cloud LLM to triage and recommend actions (JSON output only).

  • Actuator: Automatically block offending IPs using ufw/firewalld/iptables and log what happened.

  • Scheduler: A systemd timer to run every 5–15 minutes.


Prerequisites: packages and setup

Below are commands for Debian/Ubuntu (apt), Fedora/RHEL/CentOS (dnf), and openSUSE/SLE (zypper). Install what you plan to use.

  • Core CLI utilities

    • curl (HTTP client)
    • jq (JSON helper)
    • lsof, iproute2 (ss command)
    • inotify-tools (optional file change monitoring)
    • mailx (optional alerts via email)
    • python3 (optional scripting)
  • Security sensors

    • auditd (system auditing)
    • fail2ban (brute-force protection)
    • clamav (malware scanning)
    • aide (optional file integrity)
    • nmap or nmap-ncat (optional networking utilities)
    • Firewall tooling: ufw (Debian/Ubuntu/openSUSE), firewalld (Fedora/RHEL/openSUSE)

Install packages:

  • apt (Debian/Ubuntu)
sudo apt update
sudo apt install -y curl jq lsof iproute2 inotify-tools mailutils python3 \
  auditd fail2ban clamav clamav-daemon aide nmap ufw firewalld
  • dnf (Fedora/RHEL/CentOS Stream)
sudo dnf install -y curl jq lsof iproute inotify-tools mailx python3 \
  audit fail2ban clamav clamav-update aide nmap-ncat firewalld ufw
# Note: On RHEL you may need EPEL for fail2ban: sudo dnf install -y epel-release
  • zypper (openSUSE/SLE)
sudo zypper refresh
sudo zypper install -y curl jq lsof iproute2 inotify-tools mailx python3 \
  audit-auditd fail2ban clamav aide nmap firewalld ufw

Initialize/update AV signatures (run once):

sudo freshclam || sudo -u clamav freshclam || true

Enable core services:

# Auditd
sudo systemctl enable --now auditd

# Fail2ban
sudo systemctl enable --now fail2ban

# Optional firewall (pick one)
# UFW
sudo systemctl enable --now ufw
# Firewalld
sudo systemctl enable --now firewalld

Optional: Local LLM (Ollama)

curl -fsSL https://ollama.com/install.sh | sh
# Start service (if not auto-started)
sudo systemctl enable --now ollama
# Pull a small model
ollama pull llama3.1:8b

Optional: Cloud LLM

  • Export your API key as an environment variable (example shown later).

Step 1: Gather the right signals (lightweight, frequent)

This assistant focuses on three high-yield indicators:

  • SSH authentication failures (likely brute-force)

  • Active network connections and listeners (unexpected egress)

  • Optional quick malware checks (ClamAV)

We’ll sample the last 30 minutes of SSH logs via journalctl and look at current TCP/UDP connections with ss.


Step 2: The assistant script (Bash)

Save this as sec-assistant.sh and run as root (or via sudo). It:

  • Collects telemetry

  • Queries an LLM (Ollama by default; OpenAI optional)

  • Parses the model’s JSON response

  • Blocks IPs if directed and logs outcomes

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Config
ASSISTANT_NAME="sec-assistant"
LOG_FILE="/var/log/${ASSISTANT_NAME}.log"
WORK_DIR="/var/lib/${ASSISTANT_NAME}"
mkdir -p "$WORK_DIR"
chmod 700 "$WORK_DIR"

# LLM configuration
# Set one of these:
#   Provider: export LLM_PROVIDER=ollama  (default) OR export LLM_PROVIDER=openai
#   Model:    export LLM_MODEL="llama3.1:8b" or "gpt-4o-mini"
#   OpenAI:   export OPENAI_API_KEY="sk-..."
LLM_PROVIDER="${LLM_PROVIDER:-ollama}"
LLM_MODEL="${LLM_MODEL:-llama3.1:8b}"

have() { command -v "$1" &>/dev/null; }

log() {
  echo "$(date -Is) [$ASSISTANT_NAME] $*" | tee -a "$LOG_FILE"
}

collect_ssh_failures() {
  # journal unit name can be ssh or sshd; cover both
  # last 30 minutes of failed auth attempts
  if have journalctl; then
    { journalctl -u ssh -S -30m 2>/dev/null || true; journalctl -u sshd -S -30m 2>/dev/null || true; } \
      | grep -E "Failed password|authentication failure|invalid user" || true
  else
    # Fallback to /var/log/auth.log style (Debian) if journal unavailable
    grep -E "Failed password|authentication failure|invalid user" /var/log/auth.log 2>/dev/null | tail -n 500 || true
  fi
}

collect_net() {
  # Listening + established TCP/UDP with processes
  if have ss; then
    {
      echo "LISTENING:"
      ss -tulpen || true
      echo
      echo "ESTABLISHED:"
      ss -tpn state established || true
    } || true
  else
    # Fallback
    netstat -tulpen 2>/dev/null || true
  fi
}

quick_malware_findings() {
  # Optional lightweight sampling: list last 50 infected lines if any
  # Full clamscan can be heavy; run occasionally or on-demand
  if have clamscan; then
    clamscan --infected --recursive --max-filesize=10M --max-scansize=50M \
      --exclude-dir='^/sys' --exclude-dir='^/proc' --exclude-dir='^/dev' / 2>/dev/null | tail -n 50 || true
  else
    echo "ClamAV not installed or clamscan not found."
  fi
}

block_ip() {
  local ip="$1"
  # Try ufw, then firewalld, then iptables/nftables
  if have ufw && sudo ufw status &>/dev/null; then
    sudo ufw deny from "$ip" to any || true
    sudo ufw reload || true
    log "Blocked $ip via ufw"
    return
  fi
  if have firewall-cmd && sudo firewall-cmd --state &>/dev/null; then
    sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='${ip}' reject" || true
    sudo firewall-cmd --reload || true
    log "Blocked $ip via firewalld"
    return
  fi
  if have iptables; then
    sudo iptables -I INPUT -s "$ip" -j DROP || true
    log "Blocked $ip via iptables"
    return
  fi
  log "No firewall tool found to block $ip"
}

# Collect telemetry
HOSTNAME_FQDN="$(hostname -f 2>/dev/null || hostname)"
TIME_ISO="$(date -Is)"
SSH_FAILS="$(collect_ssh_failures)"
NET_INFO="$(collect_net)"
MALWARE="$(quick_malware_findings)"

# Build JSON payload safely with jq
PAYLOAD_JSON="$(jq -n \
  --arg host "$HOSTNAME_FQDN" \
  --arg time "$TIME_ISO" \
  --arg ssh "$SSH_FAILS" \
  --arg net "$NET_INFO" \
  --arg malware "$MALWARE" \
  '{host:$host,time:$time,signals:{ssh_failures:$ssh,network:$net,malware:$malware}}')"

SYSTEM_PROMPT=$(cat <<'EOF'
You are an AI Linux security assistant. Summarize the situation and recommend safe, minimal actions.
Output strict JSON only, with keys:
{
  "summary": "1-3 sentence overview",
  "severity": "low|medium|high|critical",
  "attacker_ips": ["1.2.3.4", "..."],  // external sources to optionally block
  "actions": ["block ip 1.2.3.4", "run full clamav scan", "notify admin"],
  "rationale": "short reason for each action"
}
Only include routable IPs, avoid private/local addresses. Be conservative.
EOF
)

query_ollama() {
  curl -fsS http://localhost:11434/api/chat \
    -H "Content-Type: application/json" \
    -d "$(jq -n \
      --arg model "$LLM_MODEL" \
      --arg sys "$SYSTEM_PROMPT" \
      --arg user "$PAYLOAD_JSON" \
      '{model:$model, stream:false, messages:[{"role":"system","content":$sys},{"role":"user","content":$user}] }')" \
    | jq -r '.message.content'
}

query_openai() {
  if [[ -z "${OPENAI_API_KEY:-}" ]]; then
    echo '{"summary":"No OPENAI_API_KEY set","severity":"low","attacker_ips":[],"actions":[],"rationale":"Missing API key"}'
    return
  fi
  curl -fsS https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer '"$OPENAI_API_KEY"'" \
    -H "Content-Type: application/json" \
    -d "$(jq -n \
      --arg model "${LLM_MODEL}" \
      --arg sys "$SYSTEM_PROMPT" \
      --arg user "$PAYLOAD_JSON" \
      '{model:$model,temperature:0,messages:[{"role":"system","content":$sys},{"role":"user","content":$user}] }')" \
    | jq -r '.choices[0].message.content'
}

MODEL_OUT_RAW=""
if [[ "$LLM_PROVIDER" == "openai" ]]; then
  MODEL_OUT_RAW="$(query_openai || true)"
else
  MODEL_OUT_RAW="$(query_ollama || true)"
fi

# Fallback if model unreachable
if [[ -z "${MODEL_OUT_RAW// }" ]]; then
  MODEL_OUT_RAW='{"summary":"LLM unavailable","severity":"low","attacker_ips":[],"actions":[],"rationale":"No response"}'
fi

# Ensure JSON parse; if the model returned text with code fences, strip them
MODEL_JSON="$(echo "$MODEL_OUT_RAW" | sed -E 's/^```(json)?//; s/```$//' | jq -c . 2>/dev/null || echo '{"summary":"Parse error","severity":"low","attacker_ips":[],"actions":[],"rationale":"Invalid JSON"}')"

SUMMARY="$(echo "$MODEL_JSON" | jq -r '.summary // "no summary"')"
SEVERITY="$(echo "$MODEL_JSON" | jq -r '.severity // "low"')"
IPS=($(echo "$MODEL_JSON" | jq -r '.attacker_ips[]?'))

log "Summary: $SUMMARY (severity: $SEVERITY)"
if (( ${#IPS[@]} > 0 )); then
  log "Candidate IPs to block: ${IPS[*]}"
fi

# Conservative auto-response: only block when severity is high/critical
if [[ "$SEVERITY" == "high" || "$SEVERITY" == "critical" ]]; then
  for ip in "${IPS[@]}"; do
    # Basic sanity check: avoid private/local ranges
    if [[ "$ip" =~ ^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|127\.|169\.254\.|::1|fc00:|fe80:) ]]; then
      log "Skipping non-routable/local IP $ip"
      continue
    fi
    block_ip "$ip"
  done
else
  log "No auto-block due to non-critical severity."
fi

# Store last result
echo "$MODEL_JSON" > "$WORK_DIR/last.json"

Make it executable:

sudo install -m 0750 sec-assistant.sh /usr/local/bin/sec-assistant.sh

Environment variables to select your LLM:

  • Local (Ollama):
export LLM_PROVIDER=ollama
export LLM_MODEL="llama3.1:8b"
  • Cloud (OpenAI example):
export LLM_PROVIDER=openai
export LLM_MODEL="gpt-4o-mini"
export OPENAI_API_KEY="sk-..."

Tip: Put these in /etc/default/sec-assistant (and source them from the systemd service below).


Step 3: Automate safe responses

The example script already contains a conservative auto-block routine that:

  • Parses the model’s JSON for attacker IPs

  • Blocks with ufw, firewalld, or iptables

  • Skips private/reserved addresses

  • Only auto-blocks when severity is high or critical

You can extend actions:

  • Trigger a full ClamAV scan on demand:
sudo clamscan -r --bell -i / | tee -a /var/log/clamav/manual-scan.log
  • Notify via email:
echo "Incident: $(date -Is) - $SUMMARY" | mail -s "Security Assistant Alert on $(hostname -f)" you@example.com

Step 4: Schedule with systemd (recommended)

Create a system user and directories:

sudo useradd -r -s /usr/sbin/nologin secassist || true
sudo install -d -o secassist -g secassist -m 0750 /var/lib/sec-assistant
sudo touch /var/log/sec-assistant.log && sudo chown secassist:secassist /var/log/sec-assistant.log

Optional environment file /etc/default/sec-assistant:

LLM_PROVIDER=ollama
LLM_MODEL="llama3.1:8b"

Systemd unit /etc/systemd/system/sec-assistant.service:

[Unit]
Description=AI Security Assistant (Bash)
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=secassist
Group=secassist
EnvironmentFile=-/etc/default/sec-assistant
ExecStart=/usr/local/bin/sec-assistant.sh
Nice=5
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/sec-assistant.log /var/lib/sec-assistant

Timer /etc/systemd/system/sec-assistant.timer:

[Unit]
Description=Run AI Security Assistant periodically

[Timer]
OnBootSec=2min
OnUnitActiveSec=10min
Persistent=true
RandomizedDelaySec=60s

[Install]
WantedBy=timers.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now sec-assistant.timer
sudo systemctl list-timers | grep sec-assistant

View logs:

sudo tail -f /var/log/sec-assistant.log
sudo journalctl -u sec-assistant --since -1h

Step 5: Test it safely

  • Generate harmless SSH failures from a test client (wrong password against your lab VM) and watch the assistant summarize attempts and optionally suggest blocking the source IP.

  • Force a benign finding by connecting to an unusual outbound port and see if the assistant flags it in the summary.

  • Temporarily set severity threshold lower to test auto-block logic, then restore.


Real-world example

Scenario: You notice rhythmic SSH failures every 2–3 seconds in auth logs. The assistant:

  • Summarizes: “80 SSH failures from 203.0.113.45 in 20 min; no successful logins”

  • Severity: high

  • Actions: “block ip 203.0.113.45; notify admin”

  • Auto-blocks via ufw/firewalld (logged), stores the decision JSON, and tells you why it acted.


Hardening tips

  • Least privilege: run under a dedicated user; limit Write paths in the systemd unit.

  • Key hygiene: if using a cloud LLM, keep your API key in a root-owned file not world-readable.

  • Rate-limit actions: avoid thrashing firewall rules; log all changes.

  • Privacy: prefer local LLMs for sensitive log data.

  • Defense-in-depth: this assistant complements—not replaces—fail2ban, auditd, and your firewall.


Conclusion and next steps (CTA)

You now have a working, Linux-native AI Security Assistant:

  • It collects high-signal telemetry

  • Summarizes and prioritizes with an LLM (local or cloud)

  • Executes conservative, auditable responses

Next:

  • Add more sensors (AIDE diffs, unusual SUID files, new systemd services)

  • Integrate notifications (email, Slack/Webhooks)

  • Tune the system prompt and auto-response policies for your environment

  • Share improvements with your team

Security is a journey—start small, automate the repetitive, and make space for the incidents that need your attention most.