secubox-openwrt/package/secubox/secubox-cve-triage/files/usr/lib/cve-triage/applier.sh
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

259 lines
6.7 KiB
Bash

#!/bin/sh
# CVE Triage - Applier Module
# Applies recommendations and manages approval workflow
PENDING_FILE="/var/lib/cve-triage/pending_actions.json"
HISTORY_FILE="/var/lib/cve-triage/action_history.json"
# Initialize pending actions file
init_pending() {
mkdir -p "$(dirname "$PENDING_FILE")"
[ -f "$PENDING_FILE" ] || echo '[]' > "$PENDING_FILE"
}
# Queue a recommendation for approval
queue_recommendation() {
local rec="$1"
init_pending
local rec_id=$(echo "$rec" | jsonfilter -e '@.id' 2>/dev/null)
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
# Check if already queued
if grep -q "\"id\":\"$rec_id\"" "$PENDING_FILE" 2>/dev/null; then
log_info "Recommendation $rec_id already queued"
return
fi
# Add to pending queue
local existing=$(cat "$PENDING_FILE")
if [ "$existing" = "[]" ]; then
echo "[$rec]" > "$PENDING_FILE"
else
echo "$existing" | sed "s/\]$/,$rec]/" > "${PENDING_FILE}.tmp"
mv "${PENDING_FILE}.tmp" "$PENDING_FILE"
fi
log_info "Queued recommendation for $cve (ID: $rec_id)"
}
# Approve a pending recommendation
approve_recommendation() {
local rec_id="$1"
init_pending
# Find the recommendation
local rec=$(jsonfilter -i "$PENDING_FILE" -e "@[?(@.id==\"$rec_id\")]" 2>/dev/null)
if [ -z "$rec" ] || [ "$rec" = "null" ]; then
log_error "Recommendation $rec_id not found"
return 1
fi
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
local action=$(echo "$rec" | jsonfilter -e '@.action' 2>/dev/null)
local command=$(echo "$rec" | jsonfilter -e '@.command' 2>/dev/null)
local affected=$(echo "$rec" | jsonfilter -e '@.affected_package' 2>/dev/null)
log_info "Approving recommendation $rec_id for $cve..."
# Execute the action
case "$action" in
patch)
log_info "Applying patch for $cve..."
if [ -n "$affected" ]; then
opkg update 2>/dev/null
opkg upgrade "$affected" 2>&1
local result=$?
if [ $result -eq 0 ]; then
log_info "Successfully patched $affected"
else
log_warn "Patch for $affected may have failed (exit code: $result)"
fi
else
log_warn "No specific package to patch, running general upgrade check"
opkg update && opkg list-upgradable
fi
;;
mitigate)
log_info "Mitigation for $cve requires manual action"
log_info "Recommendation: $(echo "$rec" | jsonfilter -e '@.mitigation' 2>/dev/null)"
;;
monitor)
log_info "No action required for $cve - monitoring only"
;;
esac
# Update recommendation status
update_recommendation_status "$rec_id" "approved"
# Remove from pending queue
remove_pending "$rec_id"
# Add to history
add_to_history "$rec" "approved"
return 0
}
# Reject a pending recommendation
reject_recommendation() {
local rec_id="$1"
local reason="${2:-No reason provided}"
init_pending
local rec=$(jsonfilter -i "$PENDING_FILE" -e "@[?(@.id==\"$rec_id\")]" 2>/dev/null)
if [ -z "$rec" ] || [ "$rec" = "null" ]; then
log_error "Recommendation $rec_id not found"
return 1
fi
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
log_info "Rejecting recommendation $rec_id for $cve: $reason"
# Update recommendation status
update_recommendation_status "$rec_id" "rejected"
# Remove from pending queue
remove_pending "$rec_id"
# Add to history with reason
add_to_history "$rec" "rejected" "$reason"
return 0
}
# Remove item from pending queue
remove_pending() {
local rec_id="$1"
# Rebuild pending file without the specified item
{
echo '['
local first=1
jsonfilter -i "$PENDING_FILE" -e '@[*]' 2>/dev/null | while read -r item; do
local item_id=$(echo "$item" | jsonfilter -e '@.id' 2>/dev/null)
[ "$item_id" = "$rec_id" ] && continue
[ $first -eq 0 ] && printf ','
first=0
printf '%s' "$item"
done
echo ']'
} > "${PENDING_FILE}.tmp"
mv "${PENDING_FILE}.tmp" "$PENDING_FILE"
}
# Update recommendation status in recommendations file
update_recommendation_status() {
local rec_id="$1"
local new_status="$2"
local rec_file="/var/lib/cve-triage/recommendations.json"
[ -f "$rec_file" ] || return
# Update status using sed (avoid jq)
sed -i "s/\"id\":\"$rec_id\",\\(.*\\)\"status\":\"[^\"]*\"/\"id\":\"$rec_id\",\\1\"status\":\"$new_status\"/" "$rec_file"
}
# Add action to history
add_to_history() {
local rec="$1"
local action="$2"
local reason="${3:-}"
mkdir -p "$(dirname "$HISTORY_FILE")"
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
local rec_id=$(echo "$rec" | jsonfilter -e '@.id' 2>/dev/null)
local history_entry=$(cat <<EOF
{
"id": "hist_$(date +%s)_$cve",
"recommendation_id": "$rec_id",
"cve": "$cve",
"action_taken": "$action",
"reason": "$reason",
"timestamp": "$(date -Iseconds)",
"user": "$(whoami)"
}
EOF
)
if [ -f "$HISTORY_FILE" ]; then
local existing=$(cat "$HISTORY_FILE")
if [ "$existing" = "[]" ]; then
echo "[$history_entry]" > "$HISTORY_FILE"
else
echo "$existing" | sed "s/\]$/,$history_entry]/" > "${HISTORY_FILE}.tmp"
mv "${HISTORY_FILE}.tmp" "$HISTORY_FILE"
fi
else
echo "[$history_entry]" > "$HISTORY_FILE"
fi
}
# Auto-apply recommendations based on confidence and settings
auto_apply_recommendations() {
local recs="$1"
local auto_apply="${auto_apply_patches:-0}"
local min_conf="${min_confidence:-80}"
[ "$auto_apply" = "1" ] || return
log_info "Auto-apply enabled, processing recommendations..."
echo "$recs" | jsonfilter -e '@[*]' 2>/dev/null | while read -r rec; do
local rec_id=$(echo "$rec" | jsonfilter -e '@.id' 2>/dev/null)
local cve=$(echo "$rec" | jsonfilter -e '@.cve' 2>/dev/null)
local action=$(echo "$rec" | jsonfilter -e '@.action' 2>/dev/null)
local urgency=$(echo "$rec" | jsonfilter -e '@.urgency' 2>/dev/null)
# Only auto-apply non-critical actions with high confidence
# Critical items always go to queue for human review
if [ "$urgency" = "immediate" ] || [ "$action" = "patch" ]; then
log_info "Queueing $cve for manual review (action: $action, urgency: $urgency)"
queue_recommendation "$rec"
elif [ "$action" = "monitor" ]; then
log_info "Auto-marking $cve as acknowledged (monitor only)"
update_recommendation_status "$rec_id" "acknowledged"
else
log_info "Queueing $cve for approval"
queue_recommendation "$rec"
fi
done
}
# List pending actions
list_pending() {
init_pending
cat "$PENDING_FILE"
}
# Get pending count
get_pending_count() {
init_pending
jsonfilter -i "$PENDING_FILE" -e '@[*]' 2>/dev/null | wc -l
}
# Approve all pending recommendations
approve_all() {
init_pending
jsonfilter -i "$PENDING_FILE" -e '@[*].id' 2>/dev/null | while read -r rec_id; do
approve_recommendation "$rec_id"
done
}
# Clear all pending (without approving)
clear_pending() {
init_pending
echo '[]' > "$PENDING_FILE"
log_info "Cleared all pending recommendations"
}