- 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.
137 lines
6.1 KiB
Bash
Executable File
137 lines
6.1 KiB
Bash
Executable File
#!/bin/bash
|
||
# VT-Sentinel monitoring cron wrapper (incremental)
|
||
# Checks both VT-Sentinel file activity AND plugin updates
|
||
set -e
|
||
|
||
SCRIPT_DIR="$(dirname "$0")"
|
||
LOG_FILE="/tmp/openclaw/openclaw.log"
|
||
STATE_DIR="/home/node/.openclaw/workspace-security/memory"
|
||
SEEN_FILE="$STATE_DIR/seen-plugin-updates.txt"
|
||
mkdir -p "$STATE_DIR"
|
||
touch "$SEEN_FILE"
|
||
|
||
OUTPUT=""
|
||
|
||
# --- VT-Sentinel activity ---
|
||
REPORT=$("$SCRIPT_DIR/check.sh" 2>/dev/null)
|
||
TOTAL=$(echo "$REPORT" | jq '.totalEvents')
|
||
ALERTS=$(echo "$REPORT" | jq '.alerts')
|
||
ACTION_EVENTS=$(echo "$REPORT" | jq '[.summary.uploads, .summary.upload_complete, .summary.upload_failed, .summary.cache_hits, .summary.verdicts.clean, .summary.verdicts.malicious, .summary.verdicts.suspicious, .summary.quarantined, .summary.blocked] | add')
|
||
|
||
if [ "$ACTION_EVENTS" -gt 0 ]; then
|
||
OUTPUT+="🛡️ VT-Sentinel Activity Report\n===============================\n\n"
|
||
OUTPUT+="📊 Summary: ${ACTION_EVENTS} file events\n"
|
||
OUTPUT+=$(echo "$REPORT" | jq -r '
|
||
.summary |
|
||
(if .uploads > 0 then " ⬆️ Uploads initiated: \(.uploads)" else empty end),
|
||
(if .upload_complete > 0 then " ✅ Uploads completed: \(.upload_complete)" else empty end),
|
||
(if .upload_failed > 0 then " ❌ Upload failures: \(.upload_failed)" else empty end),
|
||
(if .cache_hits > 0 then " 💾 Cache hits: \(.cache_hits)" else empty end),
|
||
(if .verdicts.clean > 0 then " ✅ Clean verdicts: \(.verdicts.clean)" else empty end),
|
||
(if .verdicts.malicious > 0 then " 🚨 MALICIOUS: \(.verdicts.malicious)" else empty end),
|
||
(if .verdicts.suspicious > 0 then " ⚠️ SUSPICIOUS: \(.verdicts.suspicious)" else empty end),
|
||
(if .quarantined > 0 then " 📦 Quarantined: \(.quarantined)" else empty end),
|
||
(if .blocked > 0 then " 🚫 Blocked: \(.blocked)" else empty end)
|
||
')
|
||
OUTPUT+="\n\n📋 Event Details:\n"
|
||
OUTPUT+=$(echo "$REPORT" | jq -r '.events[] | select(.category != "lifecycle" and .category != "config") | " [\(.timestamp)] \(.category): \(.message)"')
|
||
|
||
if [ "$ALERTS" = "true" ]; then
|
||
OUTPUT+="\n\n⚠️ SECURITY ALERT: Review malicious/suspicious/blocked events above!"
|
||
fi
|
||
fi
|
||
|
||
# --- VT scan thread creation for new uploads ---
|
||
PENDING_FILE="$STATE_DIR/pending-scans.json"
|
||
[ ! -f "$PENDING_FILE" ] || [ ! -s "$PENDING_FILE" ] && echo '[]' > "$PENDING_FILE"
|
||
SEEN_SCANS="$STATE_DIR/seen-scan-hashes.txt"
|
||
touch "$SEEN_SCANS"
|
||
|
||
UPLOADS=$(echo "$REPORT" | jq -r '[.events[] | select(.category == "upload")] | .[]' 2>/dev/null)
|
||
COMPLETES=$(echo "$REPORT" | jq -r '[.events[] | select(.category == "upload_complete")] | .[].message' 2>/dev/null)
|
||
|
||
if [ -n "$UPLOADS" ]; then
|
||
# Extract filenames + risk categories from upload events
|
||
echo "$REPORT" | jq -r '.events[] | select(.category == "upload") | .message' | while IFS= read -r msg; do
|
||
RISK_CAT=$(echo "$msg" | grep -oP 'Unknown \K[A-Z_]+' || echo "UNKNOWN")
|
||
FNAME=$(echo "$msg" | grep -oP 'file \K[^,]+' || echo "unknown")
|
||
[ -z "$FNAME" ] || [ "$FNAME" = "unknown" ] && continue
|
||
|
||
# Check if already tracked
|
||
grep -qF "$FNAME" "$SEEN_SCANS" 2>/dev/null && continue
|
||
|
||
# Find corresponding transaction ID from upload_complete events
|
||
HASH=""
|
||
if [ -n "$COMPLETES" ]; then
|
||
# Get first unmatched transaction ID, decode to extract hash
|
||
TXN_ID=$(echo "$COMPLETES" | grep -oP '\(\K[A-Za-z0-9+/=]+(?=\))' | head -1)
|
||
if [ -n "$TXN_ID" ]; then
|
||
HASH=$(echo "$TXN_ID" | base64 -d 2>/dev/null | cut -d: -f1)
|
||
fi
|
||
fi
|
||
|
||
# Create thread
|
||
RESULT=$("$SCRIPT_DIR/scan-thread.sh" "$FNAME" "$RISK_CAT" "${HASH:-pending}" 2>/dev/null)
|
||
THREAD_ID=$(echo "$RESULT" | jq -r '.threadId // empty')
|
||
|
||
if [ -n "$THREAD_ID" ] && [ -n "$HASH" ]; then
|
||
# Add to pending scans
|
||
PENDING=$(cat "$PENDING_FILE")
|
||
PENDING=$(echo "$PENDING" | jq --arg h "$HASH" --arg f "$FNAME" --arg t "$THREAD_ID" --arg r "$RISK_CAT" \
|
||
'. += [{"hash":$h,"filename":$f,"threadId":$t,"riskCategory":$r}]')
|
||
echo "$PENDING" > "$PENDING_FILE"
|
||
OUTPUT+="\n🛡️ Created scan thread: [$RISK_CAT] $FNAME (hash: ${HASH:0:12}...)\n"
|
||
fi
|
||
|
||
echo "$FNAME" >> "$SEEN_SCANS"
|
||
done
|
||
fi
|
||
|
||
# --- Follow up on pending scans ---
|
||
PENDING_COUNT=$(jq 'length' "$PENDING_FILE" 2>/dev/null || echo 0)
|
||
if [ "$PENDING_COUNT" -gt 0 ]; then
|
||
FOLLOWUP=$("$SCRIPT_DIR/followup.sh" 2>/dev/null)
|
||
if [ -n "$FOLLOWUP" ]; then
|
||
OUTPUT+="\n$FOLLOWUP\n"
|
||
fi
|
||
fi
|
||
|
||
# --- Plugin update check (creates Discord forum threads) ---
|
||
# Filter: only plugins subsystem entries, strip ANSI + multibyte artifacts, deduplicate
|
||
PLUGIN_UPDATES=$(tail -c 5000000 "$LOG_FILE" 2>/dev/null \
|
||
| grep '"subsystem.*plugins"' \
|
||
| grep -oP 'Update available: [^"\\]+' \
|
||
| sed 's/\x1b\[[0-9;]*m//g; s/\[3[0-9]m//g' \
|
||
| tr -d '\r' \
|
||
| sort -u)
|
||
if [ -n "$PLUGIN_UPDATES" ]; then
|
||
while IFS= read -r line; do
|
||
[ -z "$line" ] && continue
|
||
grep -qF "$line" "$SEEN_FILE" 2>/dev/null && continue
|
||
|
||
# Parse: "Update available: 0.4.0 → 0.6.0. Run: openclaw plugins install plugin-name"
|
||
OLD_VER=$(echo "$line" | grep -oP 'Update available: \K[0-9.]+')
|
||
NEW_VER=$(echo "$line" | grep -oP '→ \K[0-9.]+')
|
||
INSTALL_CMD=$(echo "$line" | grep -oP 'Run: \K.*')
|
||
PLUGIN_NAME=$(echo "$INSTALL_CMD" | grep -oP 'install \K\S+')
|
||
|
||
if [ -n "$PLUGIN_NAME" ] && [ -n "$OLD_VER" ] && [ -n "$NEW_VER" ]; then
|
||
RESULT=$("$SCRIPT_DIR/plugin-update-thread.sh" "$PLUGIN_NAME" "$OLD_VER" "$NEW_VER" "$INSTALL_CMD" 2>/dev/null)
|
||
if echo "$RESULT" | jq -e '.ok == true' >/dev/null 2>&1; then
|
||
OUTPUT+="\n📦 Created thread for plugin update: ${PLUGIN_NAME} ${OLD_VER} → ${NEW_VER}\n"
|
||
else
|
||
OUTPUT+="\n📦 Plugin update: ${PLUGIN_NAME} ${OLD_VER} → ${NEW_VER} (thread creation failed)\n"
|
||
fi
|
||
fi
|
||
|
||
echo "$line" >> "$SEEN_FILE"
|
||
done <<< "$PLUGIN_UPDATES"
|
||
fi
|
||
|
||
# --- Output ---
|
||
if [ -z "$OUTPUT" ]; then
|
||
echo "NO_REPLY"
|
||
else
|
||
echo -e "$OUTPUT"
|
||
fi
|