- workspace: capmetro-monitor, github-notifications, model-selector - workspace-security: vt-monitor, monitor-unauthorized - workspace-home: cron-manager, monitor-unauthorized - extensions: vt-sentinel (VT-Sentinel plugin) Includes sync.sh for pull/push, README, AGENTS.md, .gitignore.
112 lines
3.9 KiB
Bash
Executable File
112 lines
3.9 KiB
Bash
Executable File
#!/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
|
|
}'
|