secubox-openwrt/package/secubox/luci-app-ai-insights/root/usr/libexec/rpcd/luci.ai-insights
CyberMind-FR e364595b16 feat(ai-insights,tor-shield): KISS cyberpunk theme and domain exclusions
AI Insights Dashboard:
- Rewrite CSS with KISS cyberpunk theme (dark bg, neon accents, glowing effects)
- Fix CVE feed RPCD for OpenWrt/BusyBox compatibility (date format, JSON building)
- Add wget fallback for CVE fetch

Tor Shield:
- Add excluded_domains support for bypassing Tor routing
- Resolve domains via nslookup and add to iptables RETURN rules
- Default exclusions: openwrt.org, downloads.openwrt.org, services.nvd.nist.gov

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-12 10:00:38 +01:00

391 lines
13 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},
"get_cve_feed": {"limit": 10},
"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"
;;
get_cve_feed)
read -r input
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
[ -z "$limit" ] && limit=10
# Cache file for CVE feed (refresh every 30 min)
cache_file="/tmp/cve_feed_cache.json"
cache_age=1800
# Check cache (use ls -l for file age on OpenWrt)
if [ -f "$cache_file" ]; then
file_time=$(date -r "$cache_file" +%s 2>/dev/null || echo 0)
now=$(date +%s)
age=$((now - file_time))
if [ "$age" -lt "$cache_age" ] && [ -s "$cache_file" ]; then
cat "$cache_file"
exit 0
fi
fi
# Calculate dates (OpenWrt compatible)
# 3 days = 259200 seconds
now=$(date +%s)
start_ts=$((now - 259200))
# Format: YYYY-MM-DDTHH:MM:SS.000
# BusyBox date can format from timestamp with -D
start_date=$(date -u -d "@$start_ts" +%Y-%m-%dT00:00:00.000 2>/dev/null)
if [ -z "$start_date" ]; then
# Fallback: just use current date (NVD will return recent CVEs)
start_date=$(date -u +%Y-%m-%dT00:00:00.000)
fi
end_date=$(date -u +%Y-%m-%dT23:59:59.999)
# Fetch from NVD API - use wget if curl not available
nvd_url="https://services.nvd.nist.gov/rest/json/cves/2.0?pubStartDate=${start_date}&pubEndDate=${end_date}&resultsPerPage=${limit}"
if command -v curl >/dev/null 2>&1; then
response=$(curl -s --max-time 20 "$nvd_url" 2>/dev/null)
elif command -v wget >/dev/null 2>&1; then
response=$(wget -q -O - --timeout=20 "$nvd_url" 2>/dev/null)
else
echo '{"cves":[],"error":"No HTTP client available"}'
exit 0
fi
if [ -n "$response" ] && echo "$response" | grep -q "vulnerabilities"; then
# Build JSON manually (jshn in while loop doesn't work due to subshell)
tmpfile="/tmp/cve_build_$$.json"
echo '{"cves":[' > "$tmpfile"
first=1
# Parse each vulnerability
total=$(echo "$response" | jsonfilter -e '@.totalResults' 2>/dev/null)
[ -z "$total" ] && total=0
i=0
while [ "$i" -lt "$limit" ] && [ "$i" -lt "$total" ]; do
cve_id=$(echo "$response" | jsonfilter -e "@.vulnerabilities[$i].cve.id" 2>/dev/null)
[ -z "$cve_id" ] && { i=$((i + 1)); continue; }
desc=$(echo "$response" | jsonfilter -e "@.vulnerabilities[$i].cve.descriptions[0].value" 2>/dev/null | head -c 200 | sed 's/"/\\"/g; s/\\/\\\\/g' | tr '\n' ' ')
score=$(echo "$response" | jsonfilter -e "@.vulnerabilities[$i].cve.metrics.cvssMetricV31[0].cvssData.baseScore" 2>/dev/null)
[ -z "$score" ] && score=$(echo "$response" | jsonfilter -e "@.vulnerabilities[$i].cve.metrics.cvssMetricV2[0].cvssData.baseScore" 2>/dev/null)
[ -z "$score" ] && score="0"
published=$(echo "$response" | jsonfilter -e "@.vulnerabilities[$i].cve.published" 2>/dev/null | cut -c1-10)
[ "$first" -eq 0 ] && echo ',' >> "$tmpfile"
first=0
printf '{"id":"%s","description":"%s","score":%s,"published":"%s"}' \
"$cve_id" "$desc" "$score" "$published" >> "$tmpfile"
i=$((i + 1))
done
echo ']}' >> "$tmpfile"
mv "$tmpfile" "$cache_file"
cat "$cache_file"
else
echo '{"cves":[],"error":"Failed to fetch CVE feed"}'
fi
;;
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