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>
222 lines
6.3 KiB
Bash
222 lines
6.3 KiB
Bash
#!/bin/sh
|
|
# CVE Triage - Recommender Module
|
|
# Generates actionable recommendations from CVE analysis
|
|
|
|
RECOMMENDATIONS_FILE="/var/lib/cve-triage/recommendations.json"
|
|
ALERTS_FILE="/var/lib/cve-triage/alerts.json"
|
|
|
|
# Generate recommendation from analyzed CVE
|
|
generate_recommendation() {
|
|
local cve_id="$1"
|
|
local cvss="$2"
|
|
local severity="$3"
|
|
local affected_pkg="$4"
|
|
local analysis="$5"
|
|
|
|
local action=$(echo "$analysis" | jsonfilter -e '@.action' 2>/dev/null)
|
|
local urgency=$(echo "$analysis" | jsonfilter -e '@.urgency' 2>/dev/null)
|
|
local mitigation=$(echo "$analysis" | jsonfilter -e '@.mitigation' 2>/dev/null)
|
|
local impact=$(echo "$analysis" | jsonfilter -e '@.impact_level' 2>/dev/null)
|
|
|
|
action="${action:-monitor}"
|
|
urgency="${urgency:-scheduled}"
|
|
mitigation="${mitigation:-Update to latest version}"
|
|
impact="${impact:-$severity}"
|
|
|
|
local rec_id="rec_$(date +%s)_${cve_id}"
|
|
|
|
# Generate specific command based on action
|
|
local command=""
|
|
case "$action" in
|
|
patch)
|
|
if [ -n "$affected_pkg" ]; then
|
|
command="opkg update && opkg upgrade $affected_pkg"
|
|
else
|
|
command="opkg update && opkg list-upgradable"
|
|
fi
|
|
;;
|
|
mitigate)
|
|
command="# Manual mitigation required: $mitigation"
|
|
;;
|
|
monitor)
|
|
command="# No immediate action - continue monitoring"
|
|
;;
|
|
esac
|
|
|
|
# Escape strings for JSON
|
|
mitigation=$(echo "$mitigation" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
command=$(echo "$command" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
|
|
|
cat <<EOF
|
|
{
|
|
"id": "$rec_id",
|
|
"cve": "$cve_id",
|
|
"cvss": $cvss,
|
|
"severity": "$severity",
|
|
"impact": "$impact",
|
|
"affected_package": "$affected_pkg",
|
|
"action": "$action",
|
|
"urgency": "$urgency",
|
|
"mitigation": "$mitigation",
|
|
"command": "$command",
|
|
"status": "pending",
|
|
"created": "$(date -Iseconds)"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Process analyzed CVEs and create recommendations
|
|
create_recommendations() {
|
|
local analyzed_cves="$1"
|
|
local max_recs="${max_recommendations:-10}"
|
|
|
|
mkdir -p "$(dirname "$RECOMMENDATIONS_FILE")"
|
|
|
|
local count=0
|
|
local recs='[]'
|
|
|
|
echo "$analyzed_cves" | jsonfilter -e '@[*]' 2>/dev/null | while read -r cve_data; do
|
|
[ $count -ge $max_recs ] && break
|
|
|
|
local cve_id=$(echo "$cve_data" | jsonfilter -e '@.cve' 2>/dev/null)
|
|
local cvss=$(echo "$cve_data" | jsonfilter -e '@.cvss' 2>/dev/null)
|
|
local severity=$(echo "$cve_data" | jsonfilter -e '@.severity' 2>/dev/null)
|
|
local affected_pkg=$(echo "$cve_data" | jsonfilter -e '@.affected_package' 2>/dev/null)
|
|
local analysis=$(echo "$cve_data" | jsonfilter -e '@.analysis' 2>/dev/null)
|
|
|
|
# Skip CVEs that don't affect OpenWrt (if analysis says so)
|
|
local affects_openwrt=$(echo "$analysis" | jsonfilter -e '@.affects_openwrt' 2>/dev/null)
|
|
[ "$affects_openwrt" = "false" ] && continue
|
|
|
|
# Skip CVEs with action=ignore
|
|
local action=$(echo "$analysis" | jsonfilter -e '@.action' 2>/dev/null)
|
|
[ "$action" = "ignore" ] && continue
|
|
|
|
generate_recommendation "$cve_id" "${cvss:-0}" "$severity" "$affected_pkg" "$analysis"
|
|
count=$((count + 1))
|
|
done | json_slurp
|
|
}
|
|
|
|
# Save recommendations to file
|
|
save_recommendations() {
|
|
local new_recs="$1"
|
|
|
|
# Load existing recommendations
|
|
local existing='[]'
|
|
[ -f "$RECOMMENDATIONS_FILE" ] && existing=$(cat "$RECOMMENDATIONS_FILE" 2>/dev/null)
|
|
|
|
# Merge (keep existing, add new if CVE not already present)
|
|
{
|
|
echo '['
|
|
local first=1
|
|
|
|
# Keep existing non-completed recommendations
|
|
echo "$existing" | jsonfilter -e '@[*]' 2>/dev/null | while read -r rec; do
|
|
local status=$(echo "$rec" | jsonfilter -e '@.status' 2>/dev/null)
|
|
[ "$status" = "completed" ] || [ "$status" = "rejected" ] && continue
|
|
[ $first -eq 0 ] && printf ','
|
|
first=0
|
|
printf '%s' "$rec"
|
|
done
|
|
|
|
# Add new recommendations (skip duplicates)
|
|
echo "$new_recs" | jsonfilter -e '@[*]' 2>/dev/null | while read -r rec; do
|
|
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
|
|
# Check if CVE already in existing
|
|
if ! echo "$existing" | grep -q "\"cve\":\"$cve\""; then
|
|
[ $first -eq 0 ] && printf ','
|
|
first=0
|
|
printf '%s' "$rec"
|
|
fi
|
|
done
|
|
|
|
echo ']'
|
|
} > "${RECOMMENDATIONS_FILE}.tmp"
|
|
|
|
mv "${RECOMMENDATIONS_FILE}.tmp" "$RECOMMENDATIONS_FILE"
|
|
}
|
|
|
|
# Create alert for immediate attention CVEs
|
|
create_alert() {
|
|
local cve_id="$1"
|
|
local severity="$2"
|
|
local message="$3"
|
|
|
|
mkdir -p "$(dirname "$ALERTS_FILE")"
|
|
|
|
local alert=$(cat <<EOF
|
|
{
|
|
"id": "alert_$(date +%s)_$cve_id",
|
|
"cve": "$cve_id",
|
|
"severity": "$severity",
|
|
"message": "$message",
|
|
"created": "$(date -Iseconds)",
|
|
"acknowledged": false
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Append to alerts file
|
|
if [ -f "$ALERTS_FILE" ]; then
|
|
local existing=$(cat "$ALERTS_FILE")
|
|
# Check if alert for this CVE already exists
|
|
if ! echo "$existing" | grep -q "\"cve\":\"$cve_id\""; then
|
|
echo "$existing" | sed "s/\]$/,$alert]/" > "${ALERTS_FILE}.tmp"
|
|
mv "${ALERTS_FILE}.tmp" "$ALERTS_FILE"
|
|
fi
|
|
else
|
|
echo "[$alert]" > "$ALERTS_FILE"
|
|
fi
|
|
|
|
log_warn "ALERT: $cve_id ($severity) - $message"
|
|
}
|
|
|
|
# Process recommendations and create alerts for critical/immediate items
|
|
process_recommendations() {
|
|
local recs="$1"
|
|
|
|
echo "$recs" | jsonfilter -e '@[*]' 2>/dev/null | while read -r rec; do
|
|
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
|
|
local severity=$(echo "$rec" | jsonfilter -e '@.severity' 2>/dev/null)
|
|
local urgency=$(echo "$rec" | jsonfilter -e '@.urgency' 2>/dev/null)
|
|
local affected=$(echo "$rec" | jsonfilter -e '@.affected_package' 2>/dev/null)
|
|
|
|
# Create alert for critical CVEs or immediate urgency
|
|
if [ "$severity" = "critical" ] || [ "$urgency" = "immediate" ]; then
|
|
create_alert "$cve" "$severity" "Critical vulnerability affecting ${affected:-system}. Immediate action required."
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Get pending recommendations
|
|
get_pending_recommendations() {
|
|
[ -f "$RECOMMENDATIONS_FILE" ] || { echo '[]'; return; }
|
|
|
|
echo '['
|
|
local first=1
|
|
jsonfilter -i "$RECOMMENDATIONS_FILE" -e '@[*]' 2>/dev/null | while read -r rec; do
|
|
local status=$(echo "$rec" | jsonfilter -e '@.status' 2>/dev/null)
|
|
[ "$status" = "pending" ] || continue
|
|
[ $first -eq 0 ] && printf ','
|
|
first=0
|
|
printf '%s' "$rec"
|
|
done
|
|
echo ']'
|
|
}
|
|
|
|
# Get active alerts
|
|
get_active_alerts() {
|
|
[ -f "$ALERTS_FILE" ] || { echo '[]'; return; }
|
|
|
|
echo '['
|
|
local first=1
|
|
jsonfilter -i "$ALERTS_FILE" -e '@[*]' 2>/dev/null | while read -r alert; do
|
|
local ack=$(echo "$alert" | jsonfilter -e '@.acknowledged' 2>/dev/null)
|
|
[ "$ack" = "true" ] && continue
|
|
[ $first -eq 0 ] && printf ','
|
|
first=0
|
|
printf '%s' "$alert"
|
|
done
|
|
echo ']'
|
|
}
|