#!/bin/bash # Check VT-Sentinel activity — incremental (byte-offset tracking) # Only reads NEW log lines since last check set -e LOG_FILE="/tmp/openclaw/vt-sentinel.log" STATE_DIR="/home/node/.openclaw/workspace-security/memory" OFFSET_FILE="$STATE_DIR/vt-log-offset" mkdir -p "$STATE_DIR" if [ ! -f "$LOG_FILE" ]; then echo '{"error":"Gateway log not found"}' exit 1 fi # Current file size FILE_SIZE=$(stat -c%s "$LOG_FILE") # Last read offset (0 = first run, reads last 1MB as bootstrap) LAST_OFFSET=0 if [ -f "$OFFSET_FILE" ]; then LAST_OFFSET=$(cat "$OFFSET_FILE") fi # If file shrank (log rotation), reset if [ "$LAST_OFFSET" -gt "$FILE_SIZE" ]; then LAST_OFFSET=0 fi # Dedicated log is small — no need for bootstrap limit SKIP=$LAST_OFFSET BYTES_NEW=$((FILE_SIZE - SKIP)) # Nothing new if [ "$BYTES_NEW" -le 0 ]; then echo "$FILE_SIZE" > "$OFFSET_FILE" echo '{"totalEvents":0,"alerts":false,"summary":{"uploads":0,"upload_complete":0,"upload_failed":0,"cache_hits":0,"verdicts":{"clean":0,"malicious":0,"suspicious":0},"quarantined":0,"blocked":0},"events":[]}' exit 0 fi # Read only new bytes, grep VT-Sentinel TMPFILE=$(mktemp) trap "rm -f $TMPFILE" EXIT tail -c +"$((SKIP + 1))" "$LOG_FILE" | grep '"subsystem.*gateway"' | grep '\[VT-Sentinel\]' | while IFS= read -r line; do TIMESTAMP=$(echo "$line" | grep -oP '"date":"\K[^"]*' | head -1) MESSAGE=$(echo "$line" | grep -oP '\[VT-Sentinel\] \K[^"\\]*' | head -1) [ -z "$TIMESTAMP" ] || [ -z "$MESSAGE" ] && continue CATEGORY="info" case "$MESSAGE" in *"Unknown"*"uploading"*) CATEGORY="upload" ;; *"Uploaded for analysis"*) CATEGORY="upload_complete" ;; *"Upload failed"*) CATEGORY="upload_failed" ;; *"Cache hit"*) CATEGORY="cache_hit" ;; *"MALICIOUS"*) CATEGORY="verdict_malicious" ;; *"SUSPICIOUS"*) CATEGORY="verdict_suspicious" ;; *"clean"*|*"BENIGN"*) CATEGORY="verdict_clean" ;; *"quarantin"*) CATEGORY="quarantine" ;; *"BLOCKED"*|*"blocked"*) CATEGORY="blocked" ;; *"Watching:"*) CATEGORY="config" ;; *"Plugin loaded"*|*"Service stopped"*|*"Auto-"*|*"Registered"*|*"Using"*) CATEGORY="lifecycle" ;; esac jq -cn --arg ts "$TIMESTAMP" --arg msg "$MESSAGE" --arg cat "$CATEGORY" \ '{"timestamp":$ts,"message":$msg,"category":$cat}' done > "$TMPFILE" # Save new offset echo "$FILE_SIZE" > "$OFFSET_FILE" TOTAL=$(wc -l < "$TMPFILE" | tr -d ' ') [ -z "$TOTAL" ] && TOTAL=0 if [ "$TOTAL" -eq 0 ]; then echo '{"totalEvents":0,"alerts":false,"summary":{"uploads":0,"upload_complete":0,"upload_failed":0,"cache_hits":0,"verdicts":{"clean":0,"malicious":0,"suspicious":0},"quarantined":0,"blocked":0},"events":[]}' exit 0 fi EVENTS_JSON=$(jq -s '.' "$TMPFILE") count_cat() { local c c=$(grep -c "\"category\":\"$1\"" "$TMPFILE" 2>/dev/null || true) echo "${c:-0}" } jq -n \ --argjson events "$EVENTS_JSON" \ --argjson total "$TOTAL" \ --argjson uploads "$(count_cat upload)" \ --argjson upload_complete "$(count_cat upload_complete)" \ --argjson upload_failed "$(count_cat upload_failed)" \ --argjson cache_hits "$(count_cat cache_hit)" \ --argjson clean "$(count_cat verdict_clean)" \ --argjson malicious "$(count_cat verdict_malicious)" \ --argjson suspicious "$(count_cat verdict_suspicious)" \ --argjson quarantined "$(count_cat quarantine)" \ --argjson blocked "$(count_cat blocked)" \ '{ totalEvents: $total, summary: { uploads: $uploads, upload_complete: $upload_complete, upload_failed: $upload_failed, cache_hits: $cache_hits, verdicts: {clean: $clean, malicious: $malicious, suspicious: $suspicious}, quarantined: $quarantined, blocked: $blocked }, alerts: ($malicious > 0 or $suspicious > 0 or $quarantined > 0 or $blocked > 0), events: $events }'