- 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.