Skip to content

v4.7.0 — PM becomes a production toolkit (54 skills, ~30k lines added) #4

v4.7.0 — PM becomes a production toolkit (54 skills, ~30k lines added)

v4.7.0 — PM becomes a production toolkit (54 skills, ~30k lines added) #4

Workflow file for this run

# ============================================================================
# VirusTotal Scan — Malware scanning for release artifacts
# ============================================================================
# Scans release archives through the VirusTotal API and posts results
# as a comment on the release. Provides a trust signal for users
# downloading skill packages.
#
# Required secret: VIRUSTOTAL_API_KEY (free tier: 4 req/min, 500 req/day)
# ============================================================================
name: VirusTotal Scan
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to scan (e.g., v3.0.0)'
required: true
permissions:
contents: write
jobs:
scan:
name: Scan release artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Determine release tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
fi
- name: Create archive for scanning
run: |
TAG="${{ steps.tag.outputs.tag }}"
ARCHIVE="claude-skills-${TAG}.tar.gz"
# Package only skill content (exclude .git, docs, etc.)
tar czf "$ARCHIVE" \
--exclude='.git' \
--exclude='documentation' \
--exclude='__pycache__' \
--exclude='.DS_Store' \
.
echo "Created archive: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
echo "ARCHIVE=$ARCHIVE" >> "$GITHUB_ENV"
- name: Submit to VirusTotal
id: vt-scan
env:
VT_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
run: |
if [ -z "$VT_API_KEY" ]; then
echo "::warning::VIRUSTOTAL_API_KEY not configured. Skipping scan."
echo "skipped=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Uploading $ARCHIVE to VirusTotal..."
RESPONSE=$(curl -s --request POST \
--url https://www.virustotal.com/api/v3/files \
--header "x-apikey: ${VT_API_KEY}" \
--form "file=@${ARCHIVE}")
ANALYSIS_ID=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['id'])" 2>/dev/null || true)
if [ -z "$ANALYSIS_ID" ]; then
echo "::error::Failed to submit file. Response: $RESPONSE"
exit 1
fi
echo "Analysis ID: $ANALYSIS_ID"
echo "analysis_id=$ANALYSIS_ID" >> "$GITHUB_OUTPUT"
echo "skipped=false" >> "$GITHUB_OUTPUT"
- name: Wait for analysis and fetch results
if: steps.vt-scan.outputs.skipped != 'true'
env:
VT_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
ANALYSIS_ID: ${{ steps.vt-scan.outputs.analysis_id }}
run: |
echo "Waiting for VirusTotal analysis to complete..."
for i in $(seq 1 12); do
sleep 15
RESULT=$(curl -s \
--url "https://www.virustotal.com/api/v3/analyses/${ANALYSIS_ID}" \
--header "x-apikey: ${VT_API_KEY}")
STATUS=$(echo "$RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['attributes']['status'])" 2>/dev/null || echo "unknown")
if [ "$STATUS" = "completed" ]; then
echo "$RESULT" | python3 -c "
import sys, json
data = json.load(sys.stdin)['data']['attributes']['stats']
print(f\"Malicious: {data.get('malicious', 0)}\")
print(f\"Suspicious: {data.get('suspicious', 0)}\")
print(f\"Undetected: {data.get('undetected', 0)}\")
print(f\"Harmless: {data.get('harmless', 0)}\")
if data.get('malicious', 0) > 0:
print('::error::VirusTotal detected malicious content!')
sys.exit(1)
print('Scan passed — no threats detected.')
"
exit $?
fi
echo " Attempt $i/12 — status: $STATUS"
done
echo "::warning::Analysis did not complete within 3 minutes."