Initial commit: custom OpenClaw skills from docker-test
- 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.
This commit is contained in:
106
workspace-security/vt-monitor/scripts/followup.sh
Executable file
106
workspace-security/vt-monitor/scripts/followup.sh
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user