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>
259 lines
6.7 KiB
Bash
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"
|
|
}
|