#!/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 < "$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" }