Skip to content

feat(airt): proper multimodal — custom targets, per-media prompts, media-output scoring, injection images (1.6.6) #198

feat(airt): proper multimodal — custom targets, per-media prompts, media-output scoring, injection images (1.6.6)

feat(airt): proper multimodal — custom targets, per-media prompts, media-output scoring, injection images (1.6.6) #198

Workflow file for this run

# 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