name: Skill Security Scan # Runs SkillSpector (NVIDIA, Apache-2.0) static analysis on skill components # changed in a PR. Posts a report comment and blocks the PR if any changed # skill scores HIGH/CRITICAL (risk score > 50). on: pull_request: paths: - 'cli-tool/components/skills/**' - 'scripts/skillspector_scan.py' - '.github/workflows/skill-security-scan.yml' permissions: contents: read pull-requests: write security-events: write jobs: skill-scan: name: SkillSpector (changed skills) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0 # full history so git diff against the base ref works - name: Setup Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install SkillSpector run: pip install "git+https://github.com/NVIDIA/skillspector.git@main" - name: Run SkillSpector on changed skills id: scan continue-on-error: true run: | python scripts/skillspector_scan.py \ --changed \ --base-ref "origin/${{ github.base_ref }}" \ --fail-on-risk \ --threshold 50 \ --output-md skillspector-report.md \ --output-sarif skillspector.sarif - name: Upload report artifacts if: always() uses: actions/upload-artifact@v7 with: name: skillspector-report path: | skillspector-report.md skillspector.sarif retention-days: 30 - name: Upload SARIF to code scanning if: always() continue-on-error: true uses: github/codeql-action/upload-sarif@v3 with: sarif_file: skillspector.sarif category: skillspector - name: Comment PR with results if: always() uses: actions/github-script@v9 with: script: | const fs = require('fs'); const reportPath = 'skillspector-report.md'; if (!fs.existsSync(reportPath)) { console.log('No SkillSpector report found.'); return; } const body = fs.readFileSync(reportPath, 'utf8'); const marker = ''; const { owner, repo } = context.repo; const issue_number = context.issue.number; const existing = await github.paginate( github.rest.issues.listComments, { owner, repo, issue_number, per_page: 100 } ); const prev = existing.find(c => c.body && c.body.includes(marker)); if (prev) { await github.rest.issues.updateComment({ owner, repo, comment_id: prev.id, body, }); } else { await github.rest.issues.createComment({ owner, repo, issue_number, body, }); } - name: Enforce gate if: steps.scan.outputs.failed == 'true' run: | echo "::error::SkillSpector found HIGH/CRITICAL findings in changed skills (${{ steps.scan.outputs.high_critical_count }})." exit 1