secubox-openwrt/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights
CyberMind-FR f2dfb5c144 feat(ai): Add v0.19 AI agent packages
Network Anomaly Agent (secubox-network-anomaly):
- 5 detection modules: bandwidth, connection flood, port scan, DNS, protocol
- EMA-based baseline comparison
- LocalAI integration for threat assessment
- network-anomalyctl CLI

LocalRecall Memory System (secubox-localrecall):
- Persistent memory for AI agents
- Categories: threats, decisions, patterns, configs, conversations
- EMA-based importance scoring
- LocalAI integration for summarization
- localrecallctl CLI with 13 commands

AI Insights Dashboard (luci-app-ai-insights):
- Unified view across all AI agents
- Security posture scoring (0-100)
- Agent status grid with alert counts
- Aggregated alerts from all agents
- Run All Agents and AI Analysis actions

LuCI Dashboards:
- luci-app-network-anomaly with real-time stats
- luci-app-localrecall with memory management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:58:08 +01:00

308 lines
9.6 KiB
Bash

#!/bin/sh
# AI Insights Aggregation RPCD Handler
# Unified view across all SecuBox AI agents
. /usr/share/libubox/jshn.sh
log_info() { logger -t ai-insights-rpcd "$*"; }
# Check if a command exists
cmd_exists() { command -v "$1" >/dev/null 2>&1; }
# Get agent status
get_agent_status() {
local agent="$1"
local status="offline"
local alerts=0
case "$agent" in
threat_analyst)
pgrep -f "threat-analyst daemon" >/dev/null 2>&1 && status="online"
[ -f /var/lib/threat-analyst/pending_rules.json ] && \
alerts=$(jsonfilter -i /var/lib/threat-analyst/pending_rules.json -e '@[*]' 2>/dev/null | wc -l)
;;
dns_guard)
pgrep -f "dnsguardctl daemon" >/dev/null 2>&1 && status="online"
[ -f /var/lib/dns-guard/alerts.json ] && \
alerts=$(jsonfilter -i /var/lib/dns-guard/alerts.json -e '@[*]' 2>/dev/null | wc -l)
;;
network_anomaly)
pgrep -f "network-anomalyctl daemon" >/dev/null 2>&1 && status="online"
[ -f /var/lib/network-anomaly/alerts.json ] && \
alerts=$(jsonfilter -i /var/lib/network-anomaly/alerts.json -e '@[*]' 2>/dev/null | wc -l)
;;
cve_triage)
pgrep -f "cve-triagectl daemon" >/dev/null 2>&1 && status="online"
[ -f /var/lib/cve-triage/alerts.json ] && \
alerts=$(jsonfilter -i /var/lib/cve-triage/alerts.json -e '@[*]' 2>/dev/null | wc -l)
;;
esac
printf '{"status":"%s","alerts":%d}' "$status" "$alerts"
}
# Calculate security posture score (0-100)
calculate_posture() {
local score=100
local factors=""
# Check LocalAI
local localai_url=$(uci -q get localrecall.main.localai_url || echo "http://127.0.0.1:8091")
if ! wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null; then
score=$((score - 10))
factors="${factors}LocalAI offline (-10), "
fi
# Check agent statuses
for agent in threat_analyst dns_guard network_anomaly cve_triage; do
local status=$(get_agent_status "$agent" | jsonfilter -e '@.status' 2>/dev/null)
if [ "$status" != "online" ]; then
score=$((score - 5))
factors="${factors}${agent} offline (-5), "
fi
done
# Check CrowdSec alerts (high = bad)
if cmd_exists cscli; then
local cs_alerts=$(cscli alerts list -o json --since 1h 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
if [ "$cs_alerts" -gt 50 ]; then
score=$((score - 20))
factors="${factors}High CrowdSec alerts (-20), "
elif [ "$cs_alerts" -gt 20 ]; then
score=$((score - 10))
factors="${factors}Elevated CrowdSec alerts (-10), "
elif [ "$cs_alerts" -gt 5 ]; then
score=$((score - 5))
factors="${factors}Some CrowdSec alerts (-5), "
fi
fi
# Check CVE alerts
if [ -f /var/lib/cve-triage/vulnerabilities.json ]; then
local critical=$(jsonfilter -i /var/lib/cve-triage/vulnerabilities.json -e "@[@.severity='critical']" 2>/dev/null | wc -l)
local high=$(jsonfilter -i /var/lib/cve-triage/vulnerabilities.json -e "@[@.severity='high']" 2>/dev/null | wc -l)
[ "$critical" -gt 0 ] && score=$((score - critical * 10)) && factors="${factors}Critical CVEs (-$((critical * 10))), "
[ "$high" -gt 0 ] && score=$((score - high * 5)) && factors="${factors}High CVEs (-$((high * 5))), "
fi
# Ensure score is 0-100
[ "$score" -lt 0 ] && score=0
[ "$score" -gt 100 ] && score=100
# Remove trailing comma
factors=$(echo "$factors" | sed 's/, $//')
[ -z "$factors" ] && factors="All systems nominal"
printf '{"score":%d,"factors":"%s"}' "$score" "$factors"
}
case "$1" in
list)
cat <<'EOF'
{
"status": {},
"get_alerts": {"limit": 50},
"get_posture": {},
"get_timeline": {"hours": 24},
"run_all": {},
"analyze": {}
}
EOF
;;
call)
case "$2" in
status)
# Get all agent statuses
ta=$(get_agent_status threat_analyst)
dg=$(get_agent_status dns_guard)
na=$(get_agent_status network_anomaly)
ct=$(get_agent_status cve_triage)
# Check LocalAI
localai_status="offline"
localai_url=$(uci -q get localrecall.main.localai_url || echo "http://127.0.0.1:8091")
wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null && localai_status="online"
# Check LocalRecall
memories=0
[ -f /var/lib/localrecall/memories.json ] && \
memories=$(jsonfilter -i /var/lib/localrecall/memories.json -e '@[*]' 2>/dev/null | wc -l)
# Get posture
posture=$(calculate_posture)
posture_score=$(echo "$posture" | jsonfilter -e '@.score' 2>/dev/null)
cat <<EOF
{
"localai": "$localai_status",
"memories": $memories,
"posture_score": ${posture_score:-0},
"agents": {
"threat_analyst": $ta,
"dns_guard": $dg,
"network_anomaly": $na,
"cve_triage": $ct
}
}
EOF
;;
get_alerts)
read -r input
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
[ -z "$limit" ] && limit=50
# Aggregate alerts from all sources
alerts='['
first=1
# Threat Analyst pending rules
if [ -f /var/lib/threat-analyst/pending_rules.json ]; then
jsonfilter -i /var/lib/threat-analyst/pending_rules.json -e '@[*]' 2>/dev/null | head -n 10 | while read -r a; do
[ $first -eq 0 ] && printf ','
first=0
printf '{"source":"threat_analyst","type":"rule","data":%s}' "$a"
done
fi
# DNS Guard alerts
if [ -f /var/lib/dns-guard/alerts.json ]; then
jsonfilter -i /var/lib/dns-guard/alerts.json -e '@[*]' 2>/dev/null | head -n 10 | while read -r a; do
[ $first -eq 0 ] && printf ','
first=0
printf '{"source":"dns_guard","type":"alert","data":%s}' "$a"
done
fi
# Network Anomaly alerts
if [ -f /var/lib/network-anomaly/alerts.json ]; then
jsonfilter -i /var/lib/network-anomaly/alerts.json -e '@[*]' 2>/dev/null | head -n 10 | while read -r a; do
[ $first -eq 0 ] && printf ','
first=0
printf '{"source":"network_anomaly","type":"alert","data":%s}' "$a"
done
fi
# CVE Triage alerts
if [ -f /var/lib/cve-triage/alerts.json ]; then
jsonfilter -i /var/lib/cve-triage/alerts.json -e '@[*]' 2>/dev/null | head -n 10 | while read -r a; do
[ $first -eq 0 ] && printf ','
first=0
printf '{"source":"cve_triage","type":"cve","data":%s}' "$a"
done
fi
alerts="${alerts}]"
printf '{"alerts":%s}' "$alerts"
;;
get_posture)
posture=$(calculate_posture)
echo "$posture"
;;
get_timeline)
read -r input
hours=$(echo "$input" | jsonfilter -e '@.hours' 2>/dev/null)
[ -z "$hours" ] && hours=24
# Build timeline from system log
timeline='['
first=1
# Get security-related log entries
logread 2>/dev/null | grep -E "(crowdsec|threat-analyst|dns-guard|network-anomaly|cve-triage)" | tail -n 50 | while read -r line; do
ts=$(echo "$line" | awk '{print $1" "$2" "$3}')
msg=$(echo "$line" | cut -d: -f4-)
source=$(echo "$line" | grep -oE "(crowdsec|threat-analyst|dns-guard|network-anomaly|cve-triage)" | head -1)
[ $first -eq 0 ] && printf ','
first=0
printf '{"time":"%s","source":"%s","message":"%s"}' "$ts" "$source" "$(echo "$msg" | sed 's/"/\\"/g')"
done
timeline="${timeline}]"
printf '{"timeline":%s}' "$timeline"
;;
run_all)
# Trigger all agents
results='{'
# Run Threat Analyst
if cmd_exists threat-analystctl; then
/usr/bin/threat-analystctl run -q >/dev/null 2>&1 &
results="${results}\"threat_analyst\":\"started\","
else
results="${results}\"threat_analyst\":\"not_installed\","
fi
# Run DNS Guard
if cmd_exists dnsguardctl; then
/usr/bin/dnsguardctl run -q >/dev/null 2>&1 &
results="${results}\"dns_guard\":\"started\","
else
results="${results}\"dns_guard\":\"not_installed\","
fi
# Run Network Anomaly
if cmd_exists network-anomalyctl; then
/usr/bin/network-anomalyctl run -q >/dev/null 2>&1 &
results="${results}\"network_anomaly\":\"started\","
else
results="${results}\"network_anomaly\":\"not_installed\","
fi
# Run CVE Triage
if cmd_exists cve-triagectl; then
/usr/bin/cve-triagectl run -q >/dev/null 2>&1 &
results="${results}\"cve_triage\":\"started\","
else
results="${results}\"cve_triage\":\"not_installed\","
fi
# Remove trailing comma and close
results=$(echo "$results" | sed 's/,$//')
results="${results}}"
echo "$results"
;;
analyze)
# Get AI security analysis
localai_url=$(uci -q get localrecall.main.localai_url || echo "http://127.0.0.1:8091")
localai_model=$(uci -q get localrecall.main.localai_model || echo "tinyllama-1.1b-chat-v1.0.Q4_K_M")
if ! curl -s --max-time 2 "${localai_url}/v1/models" >/dev/null 2>&1; then
echo '{"error":"LocalAI not available"}'
exit 0
fi
# Collect current state
posture=$(calculate_posture)
score=$(echo "$posture" | jsonfilter -e '@.score' 2>/dev/null)
factors=$(echo "$posture" | jsonfilter -e '@.factors' 2>/dev/null)
prompt="You are a security analyst for SecuBox. Current security posture score: $score/100. Factors: $factors. Provide a brief security assessment and top 3 recommendations."
prompt=$(printf '%s' "$prompt" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
response=$(curl -s --max-time 60 -X POST "${localai_url}/v1/chat/completions" \
-H "Content-Type: application/json" \
-d "{\"model\":\"$localai_model\",\"messages\":[{\"role\":\"user\",\"content\":\"$prompt\"}],\"max_tokens\":256,\"temperature\":0.3}" 2>/dev/null)
if [ -n "$response" ]; then
content=$(echo "$response" | jsonfilter -e '@.choices[0].message.content' 2>/dev/null)
content=$(printf '%s' "$content" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
printf '{"analysis":"%s"}' "$content"
else
echo '{"error":"AI analysis failed"}'
fi
;;
*)
echo '{"error":"Unknown method"}'
;;
esac
;;
esac