feat(airt): proper multimodal — custom targets, per-media prompts, media-output scoring, injection images (1.6.6) #198
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Security scan skills using NVIDIA SkillSpector. | |
| # | |
| # Triggers on all pushes to main and PRs. The detect-changes job | |
| # auto-discovers capability directories (capabilities/<cap>/capability.yaml) | |
| # and filters to only those with changed files. | |
| # | |
| # Note: SkillSpector is installed from GitHub (not PyPI yet) and runs in | |
| # static-only mode (--no-llm) so the workflow does not require provider | |
| # API keys. Security-focused capabilities may score high by design; the | |
| # workflow uploads SARIF to GitHub Code Scanning and reports findings but | |
| # does not block merges while thresholds are being tuned. | |
| name: Security Scan | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: security-scan-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| security-events: write | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Detect which capabilities have changed | |
| # --------------------------------------------------------------------------- | |
| detect-changes: | |
| name: Detect changed capabilities | |
| runs-on: ubuntu-latest | |
| outputs: | |
| capabilities: ${{ steps.resolve.outputs.capabilities }} | |
| any_changed: ${{ steps.resolve.outputs.any_changed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve changed capabilities | |
| id: resolve | |
| run: | | |
| set -euo pipefail | |
| # Auto-discover all capability directories | |
| ALL_CAPS=() | |
| for dir in capabilities/*/; do | |
| cap=$(basename "${dir}") | |
| if [[ -f "capabilities/${cap}/capability.yaml" ]]; then | |
| ALL_CAPS+=("${cap}") | |
| fi | |
| done | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| CAPS=("${ALL_CAPS[@]}") | |
| else | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| DIFF_BASE="origin/${{ github.base_ref }}" | |
| DIFF_HEAD="HEAD" | |
| else | |
| DIFF_BASE="${{ github.event.before }}" | |
| DIFF_HEAD="${{ github.sha }}" | |
| fi | |
| # Extract capability names from changed paths under capabilities/ | |
| CHANGED=$(git diff --name-only "${DIFF_BASE}" "${DIFF_HEAD}" \ | |
| | awk -F/ '/^capabilities\// {print $2}' | sort -u || true) | |
| CAPS=() | |
| for cap in ${CHANGED}; do | |
| for known in "${ALL_CAPS[@]}"; do | |
| if [[ "${cap}" == "${known}" ]]; then | |
| CAPS+=("${cap}") | |
| break | |
| fi | |
| done | |
| done | |
| # If security-scan workflow/script changed, scan everything | |
| if git diff --name-only "${DIFF_BASE}" "${DIFF_HEAD}" 2>/dev/null \ | |
| | grep -qE '^(\.github/workflows/security-scan\.yml|scripts/security-scan\.sh)$'; then | |
| CAPS=("${ALL_CAPS[@]}") | |
| fi | |
| fi | |
| JSON=$(printf '%s\n' "${CAPS[@]:-}" | jq -Rc . | jq -sc .) | |
| ANY="false" | |
| [[ ${#CAPS[@]} -gt 0 ]] && ANY="true" | |
| echo "capabilities=${JSON}" >> "$GITHUB_OUTPUT" | |
| echo "any_changed=${ANY}" >> "$GITHUB_OUTPUT" | |
| echo "### Security scan: ${#CAPS[@]} capability(ies) to scan" >> "$GITHUB_STEP_SUMMARY" | |
| echo "Capabilities: ${CAPS[*]:-none}" >> "$GITHUB_STEP_SUMMARY" | |
| # --------------------------------------------------------------------------- | |
| # Scan changed capabilities | |
| # --------------------------------------------------------------------------- | |
| scan: | |
| name: Skill security scan | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.any_changed == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| - name: Scan changed capabilities | |
| id: scan | |
| # Continue on error: security capabilities often score high by design. | |
| # SARIF is uploaded for review; merge gating can be enabled once | |
| # thresholds are tuned. | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| mkdir -p .sarif | |
| CAPS_JSON='${{ needs.detect-changes.outputs.capabilities }}' | |
| for cap in $(echo "${CAPS_JSON}" | jq -r '.[]'); do | |
| skills_dir="capabilities/${cap}/skills" | |
| if [[ ! -d "${skills_dir}" ]]; then | |
| continue | |
| fi | |
| skill_count=$(find "${skills_dir}" -name "SKILL.md" -type f 2>/dev/null | wc -l | tr -d ' ') | |
| if [[ "${skill_count}" -eq 0 ]]; then | |
| echo "==> ${skills_dir}/ — no skills, skipping" | |
| continue | |
| fi | |
| echo "==> Scanning capabilities/${cap}/ (${skill_count} skills)" | |
| # Human-readable terminal output for logs | |
| uvx --from git+https://github.com/NVIDIA/SkillSpector \ | |
| skillspector scan "capabilities/${cap}" \ | |
| --format terminal \ | |
| --no-llm \ | |
| || echo " ⚠ ${cap} scan reported findings (see logs above)" | |
| # SARIF output for GitHub Code Scanning | |
| uvx --from git+https://github.com/NVIDIA/SkillSpector \ | |
| skillspector scan "capabilities/${cap}" \ | |
| --format sarif \ | |
| --output ".sarif/${cap}.sarif" \ | |
| --no-llm \ | |
| || echo " ⚠ ${cap} SARIF generation reported findings" | |
| echo "" | |
| done | |
| if compgen -G ".sarif/*.sarif" > /dev/null; then | |
| echo "SARIF reports generated:" | |
| ls -la .sarif/ | |
| fi | |
| - name: Upload SARIF results | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@v3 | |
| with: | |
| sarif_file: .sarif/ | |
| continue-on-error: true | |
| - name: Report scan outcome | |
| if: always() | |
| run: | | |
| if [[ "${{ steps.scan.outcome }}" == "failure" ]]; then | |
| echo "::warning::SkillSpector reported HIGH/CRITICAL risk findings. Review the uploaded SARIF before merging." | |
| fi |