name: PR Skill Scan on: pull_request: paths: - "skills/**" - "scan_skills.py" - "scan_pr_skills.py" - "pyproject.toml" - "uv.lock" - ".github/workflows/pr-skill-scan.yml" permissions: contents: read pull-requests: write concurrency: group: pr-skill-scan-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: scan: name: Scan changed skills runs-on: ubuntu-latest timeout-minutes: 60 steps: - name: Checkout PR uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - name: Detect changed skills id: changed run: | set -euo pipefail BASE_SHA="${{ github.event.pull_request.base.sha }}" HEAD_SHA="${{ github.event.pull_request.head.sha }}" echo "Base: $BASE_SHA" echo "Head: $HEAD_SHA" # Files added/copied/modified/renamed under skills//... CHANGED_FILES=$(git diff --name-only --diff-filter=ACMR "$BASE_SHA" "$HEAD_SHA" -- 'skills/**' || true) echo "Changed files under skills/:" echo "$CHANGED_FILES" # Derive unique top-level skill directories and keep only those that still exist with a SKILL.md SKILL_DIRS=$(echo "$CHANGED_FILES" \ | awk -F/ 'NF>=2 && $1=="skills" {print $1 "/" $2}' \ | sort -u) EXISTING="" for d in $SKILL_DIRS; do if [ -f "$d/SKILL.md" ]; then EXISTING="$EXISTING $d" fi done EXISTING=$(echo "$EXISTING" | xargs || true) echo "Skill dirs to scan: '$EXISTING'" echo "skill_dirs=$EXISTING" >> "$GITHUB_OUTPUT" - name: Set up uv if: steps.changed.outputs.skill_dirs != '' uses: astral-sh/setup-uv@v8.0.0 with: enable-cache: true cache-dependency-glob: uv.lock python-version: "3.13" - name: Install dependencies if: steps.changed.outputs.skill_dirs != '' run: uv sync --python 3.13 - name: Run scanner on changed skills if: steps.changed.outputs.skill_dirs != '' id: scan env: SKILL_SCANNER_LLM_API_KEY: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }} SKILL_SCANNER_LLM_MODEL: ${{ vars.SKILL_SCANNER_LLM_MODEL || 'claude-sonnet-4-6' }} run: | uv run python scan_pr_skills.py \ --output pr_scan_comment.md \ --fail-on HIGH \ ${{ steps.changed.outputs.skill_dirs }} - name: Prepare no-op comment if: steps.changed.outputs.skill_dirs == '' run: | cat > pr_scan_comment.md <<'EOF' ## 🛡️ Skill Security Scan No skill directories (with a `SKILL.md`) were changed in this PR — nothing to scan. EOF - name: Upload scan comment as artifact if: always() uses: actions/upload-artifact@v4 with: name: pr-skill-scan-comment path: pr_scan_comment.md if-no-files-found: ignore - name: Post or update PR comment if: always() && hashFiles('pr_scan_comment.md') != '' uses: marocchino/sticky-pull-request-comment@v2 with: header: skill-security-scan path: pr_scan_comment.md