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:
2026-02-16 15:32:44 +00:00
commit 3b7d6bb67c
37 changed files with 3358 additions and 0 deletions

View 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