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,136 @@
#!/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