secubox-openwrt/package/secubox/secubox-cve-triage/files/usr/bin/cve-triage
CyberMind-FR 44493ebfe3 feat: Add CVE Triage Agent and Vortex DNS, fix webmail login
New Packages:
- secubox-cve-triage: AI-powered CVE analysis and vulnerability management
  - NVD API integration for CVE data
  - CrowdSec CVE alert correlation
  - LocalAI-powered impact analysis
  - Approval workflow for patch recommendations
  - Multi-source monitoring (opkg, LXC, Docker)

- luci-app-cve-triage: Dashboard with alerts, pending queue, risk score

- secubox-vortex-dns: Meshed multi-dynamic subdomain delegation
  - Master/slave hierarchical DNS delegation
  - Wildcard domain management
  - First Peek auto-registration
  - Gossip-based exposure config sync
  - Submastering for nested hierarchies

Fixes:
- Webmail 401 login: config.docker.inc.php was overriding IMAP host
  to ssl://mail.secubox.in:993 which Docker couldn't reach
- Fixed mailctl webmail configure to use socat proxy (172.17.0.1:10143)

Documentation:
- Added LXC cgroup:mixed fix to FAQ-TROUBLESHOOTING.md
- Updated CLAUDE.md to include FAQ consultation at startup

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

470 lines
12 KiB
Bash

#!/bin/sh
# SecuBox CVE Triage Agent
# Copyright (C) 2026 CyberMind.fr
#
# AI-powered CVE analysis and vulnerability management
CONFIG="cve-triage"
LIB_DIR="/usr/lib/cve-triage"
STATE_DIR="/var/lib/cve-triage"
CACHE_DIR="/var/cache/cve-triage"
LOG_TAG="cve-triage"
# Source libraries
. "$LIB_DIR/collector.sh"
. "$LIB_DIR/analyzer.sh"
. "$LIB_DIR/recommender.sh"
. "$LIB_DIR/applier.sh"
usage() {
cat <<'EOF'
Usage: cve-triage <command> [options]
Commands:
run Run single triage cycle
daemon Run as background daemon
status Show agent status
scan Scan installed packages only
fetch Fetch latest CVE data
analyze <cve> Analyze specific CVE with AI
Recommendations:
list-pending List pending recommendations
approve <id> Approve recommendation
reject <id> Reject recommendation
approve-all Approve all pending
clear-pending Clear all pending
Alerts:
alerts Show active alerts
ack <id> Acknowledge alert
Reports:
summary Generate security summary
export Export CVE report (JSON)
Configuration: /etc/config/cve-triage
EOF
}
log_info() { logger -t "$LOG_TAG" "$*"; echo "[INFO] $*"; }
log_warn() { logger -t "$LOG_TAG" -p warning "$*"; echo "[WARN] $*" >&2; }
log_error() { logger -t "$LOG_TAG" -p err "$*"; echo "[ERROR] $*" >&2; }
uci_get() { uci -q get "${CONFIG}.$1"; }
load_config() {
enabled=$(uci_get main.enabled)
interval=$(uci_get main.interval)
localai_url=$(uci_get main.localai_url)
localai_model=$(uci_get main.localai_model)
min_severity=$(uci_get main.min_severity)
affected_only=$(uci_get main.affected_only)
auto_apply_patches=$(uci_get main.auto_apply_patches)
min_confidence=$(uci_get main.min_confidence)
max_recommendations=$(uci_get main.max_recommendations)
# Defaults
[ -z "$interval" ] && interval=3600
[ -z "$min_severity" ] && min_severity="high"
[ -z "$affected_only" ] && affected_only=1
[ -z "$min_confidence" ] && min_confidence=80
[ -z "$max_recommendations" ] && max_recommendations=10
mkdir -p "$STATE_DIR" "$CACHE_DIR"
}
# =============================================================================
# COMMANDS
# =============================================================================
cmd_status() {
load_config
echo "=== CVE Triage Agent Status ==="
echo ""
echo "Enabled: $([ "$enabled" = "1" ] && echo "Yes" || echo "No")"
echo "Interval: ${interval}s ($(($interval / 60)) minutes)"
echo "Min Severity: $min_severity"
echo "Affected Only: $([ "$affected_only" = "1" ] && echo "Yes" || echo "No")"
echo ""
# Check LocalAI availability
if check_localai; then
echo "LocalAI: ONLINE ($localai_url)"
else
echo "LocalAI: OFFLINE (basic analysis mode)"
fi
echo ""
# Package counts
local opkg_count=$(opkg list-installed 2>/dev/null | wc -l)
local lxc_count=$(ls -d /srv/lxc/*/ 2>/dev/null | wc -l)
local docker_count=$(docker ps -q 2>/dev/null | wc -l)
echo "Monitored Packages:"
echo " opkg: $opkg_count packages"
echo " LXC containers: $lxc_count"
echo " Docker containers: $docker_count"
echo ""
# Pending recommendations
local pending=$(get_pending_count)
echo "Pending Recommendations: $pending"
# Active alerts
local alerts=$(get_active_alerts | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
echo "Active Alerts: $alerts"
# Last run
if [ -f "$STATE_DIR/last_run" ]; then
echo ""
echo "Last Run: $(cat "$STATE_DIR/last_run")"
fi
}
cmd_scan() {
load_config
log_info "Scanning installed packages..."
local packages=$(collect_all_packages)
local pkg_count=$(echo "$packages" | jsonfilter -e '@.packages[*]' 2>/dev/null | wc -l)
echo "Found $pkg_count packages"
echo "$packages" > "$STATE_DIR/packages.json"
# Show summary
echo ""
echo "Package sources:"
echo " opkg: $(echo "$packages" | jsonfilter -e '@.packages[*].source' 2>/dev/null | grep -c '^opkg$')"
echo " LXC: $(echo "$packages" | jsonfilter -e '@.packages[*].source' 2>/dev/null | grep -c '^lxc:')"
echo " Docker: $(echo "$packages" | jsonfilter -e '@.packages[*].source' 2>/dev/null | grep -c '^docker$')"
}
cmd_fetch() {
load_config
log_info "Fetching CVE data..."
# Fetch NVD CVEs
if [ "$(uci_get source.nvd.enabled)" = "1" ]; then
local nvd_data=$(fetch_nvd_cves)
local cve_count=$(echo "$nvd_data" | jsonfilter -e '@.totalResults' 2>/dev/null)
echo "NVD: Found ${cve_count:-0} recent CVEs"
fi
# Fetch CrowdSec CVE alerts
if [ "$(uci_get source.crowdsec_cve.enabled)" = "1" ]; then
local cs_cves=$(collect_crowdsec_cves)
local cs_count=$(echo "$cs_cves" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
echo "CrowdSec: Found $cs_count CVE-related alerts"
fi
}
cmd_run() {
load_config
[ "$enabled" = "1" ] || {
log_warn "CVE Triage agent is disabled"
return 1
}
log_info "Starting CVE triage cycle..."
# 1. Collect installed packages
log_info "Collecting installed packages..."
local packages=$(collect_all_packages)
# 2. Fetch CVE data
log_info "Fetching CVE data..."
local nvd_data=$(fetch_nvd_cves)
local nvd_cves=$(parse_nvd_cves "$nvd_data")
# 3. Also get CrowdSec CVE alerts
local cs_cves=$(collect_crowdsec_cves)
# 4. Match CVEs to installed packages (if affected_only=1)
local matched_cves="$nvd_cves"
if [ "$affected_only" = "1" ]; then
log_info "Matching CVEs to installed packages..."
matched_cves=$(match_cves_to_packages "$nvd_cves" "$packages")
fi
# 5. Analyze CVEs
local cve_count=$(echo "$matched_cves" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
log_info "Analyzing $cve_count CVEs..."
local analyzed=$(analyze_cves_batch "$matched_cves")
# 6. Generate recommendations
log_info "Generating recommendations..."
local recommendations=$(create_recommendations "$analyzed")
save_recommendations "$recommendations"
# 7. Process recommendations (queue/auto-apply)
process_recommendations "$recommendations"
auto_apply_recommendations "$recommendations"
# 8. Generate summary
local summary=$(generate_summary "$analyzed")
echo "$summary" > "$STATE_DIR/last_summary.json"
# Save last run timestamp
date -Iseconds > "$STATE_DIR/last_run"
# Report results
local rec_count=$(echo "$recommendations" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
local pending=$(get_pending_count)
echo ""
echo "=== Triage Complete ==="
echo "CVEs analyzed: $cve_count"
echo "Recommendations: $rec_count"
echo "Pending approval: $pending"
echo ""
# Show summary
local risk_score=$(echo "$summary" | jsonfilter -e '@.risk_score' 2>/dev/null)
local summary_text=$(echo "$summary" | jsonfilter -e '@.summary' 2>/dev/null)
echo "Risk Score: ${risk_score:-N/A}/100"
echo "Summary: ${summary_text:-Analysis complete}"
}
cmd_daemon() {
load_config
[ "$enabled" = "1" ] || {
log_error "CVE Triage agent is disabled"
exit 1
}
log_info "Starting CVE Triage daemon (interval: ${interval}s)..."
while true; do
cmd_run 2>&1 | while read -r line; do
logger -t "$LOG_TAG" "$line"
done
sleep "$interval"
done
}
cmd_analyze_cve() {
local cve_id="$1"
[ -z "$cve_id" ] && {
echo "Usage: cve-triage analyze <CVE-ID>"
exit 1
}
load_config
log_info "Analyzing $cve_id..."
# Try to fetch CVE details from NVD
local nvd_url="https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=$cve_id"
local nvd_data=$(wget -q -O - --timeout=30 "$nvd_url" 2>/dev/null)
if [ -z "$nvd_data" ]; then
log_warn "Could not fetch CVE details from NVD"
return 1
fi
local cve_info=$(parse_nvd_cves "$nvd_data" | jsonfilter -e '@[0]' 2>/dev/null)
if [ -z "$cve_info" ]; then
log_error "CVE $cve_id not found"
return 1
fi
local description=$(echo "$cve_info" | jsonfilter -e '@.description' 2>/dev/null)
local cvss=$(echo "$cve_info" | jsonfilter -e '@.cvss' 2>/dev/null)
local severity=$(echo "$cve_info" | jsonfilter -e '@.severity' 2>/dev/null)
echo "=== $cve_id ==="
echo "CVSS: $cvss ($severity)"
echo "Description: $description"
echo ""
# Analyze with AI
if check_localai; then
log_info "Running AI analysis..."
local analysis=$(analyze_cve "$cve_id" "$description" "$cvss" "")
echo "=== AI Analysis ==="
echo "$analysis" | jsonfilter -e '@' 2>/dev/null || echo "$analysis"
else
echo "LocalAI not available for detailed analysis"
fi
}
cmd_list_pending() {
load_config
local pending=$(list_pending)
local count=$(echo "$pending" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
echo "=== Pending Recommendations ($count) ==="
echo ""
echo "$pending" | jsonfilter -e '@[*]' 2>/dev/null | while read -r rec; do
local id=$(echo "$rec" | jsonfilter -e '@.id' 2>/dev/null)
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
local severity=$(echo "$rec" | jsonfilter -e '@.severity' 2>/dev/null)
local action=$(echo "$rec" | jsonfilter -e '@.action' 2>/dev/null)
local pkg=$(echo "$rec" | jsonfilter -e '@.affected_package' 2>/dev/null)
echo "[$severity] $cve"
echo " ID: $id"
echo " Action: $action"
echo " Package: ${pkg:-unknown}"
echo ""
done
}
cmd_approve() {
local rec_id="$1"
[ -z "$rec_id" ] && {
echo "Usage: cve-triage approve <recommendation-id>"
exit 1
}
load_config
approve_recommendation "$rec_id"
}
cmd_reject() {
local rec_id="$1"
local reason="$2"
[ -z "$rec_id" ] && {
echo "Usage: cve-triage reject <recommendation-id> [reason]"
exit 1
}
load_config
reject_recommendation "$rec_id" "$reason"
}
cmd_alerts() {
load_config
local alerts=$(get_active_alerts)
local count=$(echo "$alerts" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
echo "=== Active Alerts ($count) ==="
echo ""
echo "$alerts" | jsonfilter -e '@[*]' 2>/dev/null | while read -r alert; do
local id=$(echo "$alert" | jsonfilter -e '@.id' 2>/dev/null)
local cve=$(echo "$alert" | jsonfilter -e '@.cve' 2>/dev/null)
local severity=$(echo "$alert" | jsonfilter -e '@.severity' 2>/dev/null)
local message=$(echo "$alert" | jsonfilter -e '@.message' 2>/dev/null)
local created=$(echo "$alert" | jsonfilter -e '@.created' 2>/dev/null)
echo "[$severity] $cve - $created"
echo " ID: $id"
echo " $message"
echo ""
done
}
cmd_summary() {
load_config
if [ -f "$STATE_DIR/last_summary.json" ]; then
local summary=$(cat "$STATE_DIR/last_summary.json")
local risk=$(echo "$summary" | jsonfilter -e '@.risk_score' 2>/dev/null)
local text=$(echo "$summary" | jsonfilter -e '@.summary' 2>/dev/null)
echo "=== Security Summary ==="
echo ""
echo "Risk Score: ${risk:-N/A}/100"
echo ""
echo "$text"
else
echo "No summary available. Run 'cve-triage run' first."
fi
}
cmd_export() {
load_config
local export_file="/tmp/cve-report-$(date +%Y%m%d).json"
{
echo '{'
echo '"generated":"'"$(date -Iseconds)"'",'
echo '"packages":'
[ -f "$STATE_DIR/packages.json" ] && cat "$STATE_DIR/packages.json" || echo '[]'
echo ','
echo '"recommendations":'
[ -f "$STATE_DIR/recommendations.json" ] && cat "$STATE_DIR/recommendations.json" || echo '[]'
echo ','
echo '"alerts":'
[ -f "$STATE_DIR/alerts.json" ] && cat "$STATE_DIR/alerts.json" || echo '[]'
echo ','
echo '"summary":'
[ -f "$STATE_DIR/last_summary.json" ] && cat "$STATE_DIR/last_summary.json" || echo '{}'
echo '}'
} > "$export_file"
echo "Report exported to: $export_file"
}
# =============================================================================
# MAIN
# =============================================================================
case "$1" in
run)
cmd_run
;;
daemon)
cmd_daemon
;;
status)
cmd_status
;;
scan)
cmd_scan
;;
fetch)
cmd_fetch
;;
analyze)
cmd_analyze_cve "$2"
;;
list-pending)
cmd_list_pending
;;
approve)
cmd_approve "$2"
;;
reject)
cmd_reject "$2" "$3"
;;
approve-all)
load_config
approve_all
;;
clear-pending)
load_config
clear_pending
;;
alerts)
cmd_alerts
;;
ack)
# TODO: Acknowledge alert
echo "Not implemented yet"
;;
summary)
cmd_summary
;;
export)
cmd_export
;;
-h|--help|help)
usage
;;
*)
usage
exit 1
;;
esac