- 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.
107 lines
3.6 KiB
Bash
Executable File
107 lines
3.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Follow up on pending VT scans — poll VT API, update Discord threads
|
|
# Usage: followup.sh
|
|
set -e
|
|
|
|
STATE_DIR="/home/node/.openclaw/workspace-security/memory"
|
|
PENDING_FILE="$STATE_DIR/pending-scans.json"
|
|
CHANNEL_ID="1470849667737714851"
|
|
|
|
# Initialize if missing
|
|
if [ ! -f "$PENDING_FILE" ] || [ ! -s "$PENDING_FILE" ]; then
|
|
echo '[]' > "$PENDING_FILE"
|
|
exit 0
|
|
fi
|
|
|
|
PENDING=$(cat "$PENDING_FILE")
|
|
COUNT=$(echo "$PENDING" | jq 'length')
|
|
[ "$COUNT" -eq 0 ] && exit 0
|
|
|
|
TOKEN=$(printenv DISCORD_BOT_TOKEN)
|
|
VT_KEY=$(printenv VIRUSTOTAL_API_KEY)
|
|
|
|
if [ -z "$TOKEN" ] || [ -z "$VT_KEY" ]; then
|
|
echo "Missing DISCORD_BOT_TOKEN or VIRUSTOTAL_API_KEY" >&2
|
|
exit 1
|
|
fi
|
|
|
|
UPDATED="[]"
|
|
RESOLVED=0
|
|
|
|
for i in $(seq 0 $((COUNT - 1))); do
|
|
ENTRY=$(echo "$PENDING" | jq ".[$i]")
|
|
HASH=$(echo "$ENTRY" | jq -r '.hash')
|
|
THREAD_ID=$(echo "$ENTRY" | jq -r '.threadId')
|
|
FILENAME=$(echo "$ENTRY" | jq -r '.filename')
|
|
RISK_CAT=$(echo "$ENTRY" | jq -r '.riskCategory')
|
|
|
|
# Query VT API
|
|
VT_RESULT=$(curl -s --max-time 10 -H "x-apikey: $VT_KEY" \
|
|
"https://www.virustotal.com/api/v3/files/$HASH" 2>/dev/null)
|
|
|
|
STATS=$(echo "$VT_RESULT" | jq '.data.attributes.last_analysis_stats // empty' 2>/dev/null)
|
|
|
|
if [ -z "$STATS" ] || [ "$STATS" = "null" ]; then
|
|
# Still pending or not found — keep in queue
|
|
UPDATED=$(echo "$UPDATED" | jq --argjson entry "$ENTRY" '. += [$entry]')
|
|
continue
|
|
fi
|
|
|
|
# Extract verdict
|
|
MALICIOUS=$(echo "$STATS" | jq '.malicious // 0')
|
|
SUSPICIOUS=$(echo "$STATS" | jq '.suspicious // 0')
|
|
UNDETECTED=$(echo "$STATS" | jq '.undetected // 0')
|
|
TOTAL=$((MALICIOUS + SUSPICIOUS + UNDETECTED))
|
|
TYPE_DESC=$(echo "$VT_RESULT" | jq -r '.data.attributes.type_description // "Unknown"')
|
|
SHA256=$(echo "$VT_RESULT" | jq -r '.data.attributes.sha256 // "unknown"')
|
|
VT_LINK="https://www.virustotal.com/gui/file/$SHA256"
|
|
|
|
# Determine verdict
|
|
if [ "$MALICIOUS" -gt 0 ]; then
|
|
VERDICT="🚨 MALICIOUS"
|
|
EMOJI="🚨"
|
|
VERDICT_SHORT="MALICIOUS ($MALICIOUS/$TOTAL)"
|
|
elif [ "$SUSPICIOUS" -gt 0 ]; then
|
|
VERDICT="⚠️ SUSPICIOUS"
|
|
EMOJI="⚠️"
|
|
VERDICT_SHORT="SUSPICIOUS ($SUSPICIOUS/$TOTAL)"
|
|
else
|
|
VERDICT="✅ CLEAN"
|
|
EMOJI="✅"
|
|
VERDICT_SHORT="CLEAN (0/$TOTAL)"
|
|
fi
|
|
|
|
# Post verdict to thread
|
|
MSG=$(printf '%s **Analysis Complete — %s**\n\n**File:** `%s`\n**Type:** %s\n**SHA-256:** `%s`\n\n**Results:**\n• Malicious: %s\n• Suspicious: %s\n• Undetected: %s engines\n\n**VT Link:** %s\n\n**Verdict:** %s' \
|
|
"$EMOJI" "$VERDICT_SHORT" "$FILENAME" "$TYPE_DESC" "$SHA256" \
|
|
"$MALICIOUS" "$SUSPICIOUS" "$UNDETECTED" "$VT_LINK" "$VERDICT")
|
|
|
|
curl -s -X POST \
|
|
-H "Authorization: Bot $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg content "$MSG" '{content: $content}')" \
|
|
"https://discord.com/api/v10/channels/$THREAD_ID/messages" > /dev/null
|
|
|
|
# Update thread title
|
|
NEW_TITLE=$(printf '[%s] %s — %s' "$RISK_CAT" "$FILENAME" "$VERDICT")
|
|
curl -s -X PATCH \
|
|
-H "Authorization: Bot $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg name "$NEW_TITLE" '{name: $name}')" \
|
|
"https://discord.com/api/v10/channels/$THREAD_ID" > /dev/null
|
|
|
|
RESOLVED=$((RESOLVED + 1))
|
|
echo "✅ Resolved: $FILENAME → $VERDICT_SHORT"
|
|
|
|
# VT rate limit: 4 req/min on free tier, be conservative
|
|
sleep 1
|
|
done
|
|
|
|
# Save remaining pending scans
|
|
echo "$UPDATED" > "$PENDING_FILE"
|
|
|
|
if [ "$RESOLVED" -gt 0 ]; then
|
|
REMAINING=$(echo "$UPDATED" | jq 'length')
|
|
echo "Resolved $RESOLVED scan(s), $REMAINING still pending"
|
|
fi
|