Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 38 additions & 18 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Security scan skills using cisco-ai-skill-scanner.
# 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

Expand Down Expand Up @@ -71,8 +77,9 @@ jobs:
fi
done
done
# If scan-policy.yaml changed, scan everything
if git diff --name-only "${DIFF_BASE}" "${DIFF_HEAD}" 2>/dev/null | grep -q '^scan-policy\.yaml$'; then
# 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
Expand Down Expand Up @@ -102,9 +109,13 @@ jobs:
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
failed=0
mkdir -p .sarif

CAPS_JSON='${{ needs.detect-changes.outputs.capabilities }}'
Expand All @@ -120,16 +131,23 @@ jobs:
continue
fi

echo "==> Scanning ${skills_dir}/ (${skill_count} skills)"
uvx --from cisco-ai-skill-scanner skill-scanner scan-all "${skills_dir}" \
--recursive \
--use-behavioral \
--policy scan-policy.yaml \
--format summary \
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 ".sarif/${cap}.sarif" \
--fail-on-severity high \
|| failed=1
--output ".sarif/${cap}.sarif" \
--no-llm \
|| echo " ⚠ ${cap} SARIF generation reported findings"

echo ""
done

Expand All @@ -138,14 +156,16 @@ jobs:
ls -la .sarif/
fi

if [[ "${failed}" -eq 1 ]]; then
echo "::error::Security scan found HIGH+ severity findings"
exit 1
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
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ Every directory under `capabilities/` is a shipped, working example. Read one al

## Security scanning

Every skill in this repo is scanned with [cisco-ai-defense/skill-scanner](https://github.com/cisco-ai-defense/skill-scanner) for prompt injection, data exfiltration, tool-chaining abuse, and supply chain risk. CI fails on HIGH+ findings and uploads SARIF reports to GitHub Code Scanning. The repo policy in [`scan-policy.yaml`](scan-policy.yaml) tunes the scanner for security-focused content.
Every skill in this repo is scanned with [NVIDIA SkillSpector](https://github.com/NVIDIA/SkillSpector) for prompt injection, data exfiltration, tool-chaining abuse, and supply chain risk. CI runs SkillSpector in static mode (`--no-llm`) for deterministic scans without provider API keys, uploads SARIF reports to GitHub Code Scanning, and reports findings. Because security-focused capabilities intentionally contain offensive security content, the workflow currently reports findings without blocking merges while thresholds are tuned.

```bash
just security-scan # scan all capabilities
just security-scan web-security # scan one capability
just security-scan behavioral="true" # deep dataflow analysis
just security-scan behavioral="true" # ignored by SkillSpector; kept for compatibility
```

> **Note:** SkillSpector is not yet published to PyPI. The scanner is installed from `git+https://github.com/NVIDIA/SkillSpector` on each run; uv caches the build aggressively.

## Contributing

This repo is published for reference, not as a contribution target — we don't generally accept external PRs that add new capabilities. See [CONTRIBUTING.md](CONTRIBUTING.md) for what's useful to send and how to build your own capabilities instead.
Expand Down
6 changes: 4 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ validate strict="false":
[[ "{{ strict }}" == "true" ]] && cmd+=(--strict)
"${cmd[@]}"

# Security scan all skills (pass capability to scan one, behavioral="true" for deep analysis)
# Security scan all skills (pass capability to scan one)
# Note: behavioral="true" is a no-op kept for compatibility; SkillSpector uses
# --no-llm static analysis by default. Pass --llm to scripts/security-scan.sh
# to enable LLM semantic analysis (requires API keys).
security-scan capability="" behavioral="false":
#!/usr/bin/env bash
set -euo pipefail
cmd=(./scripts/security-scan.sh)
[[ "{{ behavioral }}" == "true" ]] && cmd+=(--behavioral)
[[ -n "{{ capability }}" ]] && cmd+=("{{ capability }}")
"${cmd[@]}"

Expand Down
File renamed without changes.
123 changes: 40 additions & 83 deletions scripts/security-scan.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
#!/usr/bin/env bash
# security-scan.sh — Run cisco-ai-skill-scanner across all capabilities
# security-scan.sh — Run NVIDIA SkillSpector across all capabilities
#
# Usage:
# ./scripts/security-scan.sh # scan all capabilities, summary
# ./scripts/security-scan.sh web-security # scan one capability
# ./scripts/security-scan.sh --format json # JSON output
# ./scripts/security-scan.sh --ci # CI mode: SARIF + fail on high
# ./scripts/security-scan.sh --behavioral # enable behavioral analysis
# ./scripts/security-scan.sh --sarif FILE # SARIF output
#
# Requires: uv (https://docs.astral.sh/uv/)
# Package: cisco-ai-skill-scanner (installed automatically via uvx)
# Package: skillspector (installed from git, see pyproject.toml)
#
# Note: SkillSpector is not yet on PyPI. We install from the public
# GitHub repo. Use --no-llm in CI to keep scans deterministic and avoid
# needing provider API keys.

set -euo pipefail

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
POLICY="${REPO_ROOT}/scan-policy.yaml"
SCANNER="uvx --from cisco-ai-skill-scanner skill-scanner"
# SkillSpector is not published to PyPI yet; install from git.
SCANNER="uvx --from git+https://github.com/NVIDIA/SkillSpector skillspector"

# Auto-discover capability directories under capabilities/
CAPABILITY_DIRS=()
Expand All @@ -27,28 +30,24 @@ for dir in "${REPO_ROOT}"/capabilities/*/; do
done

# Defaults
FORMAT="summary"
CI_MODE=false
USE_BEHAVIORAL=false
FAIL_SEVERITY=""
FORMAT="terminal"
TARGET_CAPABILITY=""
OUTPUT_SARIF=""
OUTPUT_JSON=""
TARGET_CAPABILITY=""
NO_LLM=true
EXTRA_ARGS=()

usage() {
cat <<EOF
Usage: $(basename "$0") [OPTIONS] [CAPABILITY]

Scan capabilities for security issues using cisco-ai-skill-scanner.
Scan capabilities for security issues using NVIDIA SkillSpector.

Options:
--ci CI mode: produce SARIF, fail on high+ severity
--format FMT Output format: summary|json|markdown|table|sarif|html
--behavioral Enable behavioral dataflow analysis (slower, deeper)
--fail-on SEV Fail if findings >= severity (critical|high|medium|low)
--format FMT Output format: terminal|json|markdown|sarif [default: terminal]
--sarif FILE Write SARIF report to FILE
--json FILE Write JSON report to FILE
--llm Enable LLM semantic analysis (requires API keys)
-h, --help Show this help

Arguments:
Expand All @@ -57,40 +56,31 @@ Arguments:
Examples:
$(basename "$0") # scan everything, summary output
$(basename "$0") web-security # scan one capability
$(basename "$0") --ci # CI pipeline mode
$(basename "$0") --behavioral # deep analysis
$(basename "$0") --format sarif --sarif report.sarif
EOF
exit 0
}

while [[ $# -gt 0 ]]; do
case "$1" in
--ci)
CI_MODE=true
FORMAT="summary"
FAIL_SEVERITY="high"
shift
;;
--format)
FORMAT="$2"
shift 2
;;
--behavioral)
USE_BEHAVIORAL=true
shift
;;
--fail-on)
FAIL_SEVERITY="$2"
shift 2
;;
--sarif)
OUTPUT_SARIF="$2"
FORMAT="sarif"
shift 2
;;
--json)
OUTPUT_JSON="$2"
FORMAT="json"
shift 2
;;
--llm)
NO_LLM=false
shift
;;
-h|--help)
usage
;;
Expand All @@ -117,25 +107,16 @@ fi
# Build scanner command
build_cmd() {
local cap_dir="$1"
local cmd=(${SCANNER} scan-all "${REPO_ROOT}/capabilities/${cap_dir}")
cmd+=(--recursive --lenient)
cmd+=(--policy "${POLICY}")
local output_path="$2"
local cmd=(${SCANNER} scan "${REPO_ROOT}/capabilities/${cap_dir}")
cmd+=(--format "${FORMAT}")

if [[ "${USE_BEHAVIORAL}" == true ]]; then
cmd+=(--use-behavioral)
if [[ "${NO_LLM}" == true ]]; then
cmd+=(--no-llm)
fi

if [[ -n "${FAIL_SEVERITY}" ]]; then
cmd+=(--fail-on-severity "${FAIL_SEVERITY}")
fi

if [[ -n "${OUTPUT_SARIF}" ]]; then
cmd+=(--output-sarif "${OUTPUT_SARIF}")
fi

if [[ -n "${OUTPUT_JSON}" ]]; then
cmd+=(--output-json "${OUTPUT_JSON}")
if [[ -n "${output_path}" ]]; then
cmd+=(--output "${output_path}")
fi

cmd+=("${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}")
Expand All @@ -144,8 +125,6 @@ build_cmd() {
}

# Run scans
overall_exit=0

for cap_dir in "${CAPABILITY_DIRS[@]}"; do
if [[ ! -d "${REPO_ROOT}/capabilities/${cap_dir}" ]]; then
continue
Expand All @@ -160,45 +139,23 @@ for cap_dir in "${CAPABILITY_DIRS[@]}"; do

echo "==> Scanning ${cap_dir}/ (${skill_count} skills)"

cmd=$(build_cmd "${cap_dir}")

if [[ "${CI_MODE}" == true ]]; then
# In CI mode, capture SARIF per-capability and merge later
sarif_file="${REPO_ROOT}/.security-scan-${cap_dir}.sarif"
cmd="${cmd} --output-sarif ${sarif_file}"
output_path=""
if [[ -n "${OUTPUT_SARIF}" ]]; then
output_path="${OUTPUT_SARIF}"
elif [[ -n "${OUTPUT_JSON}" ]]; then
output_path="${OUTPUT_JSON}"
fi

cmd=$(build_cmd "${cap_dir}" "${output_path}")

# SkillSpector exits 1 when risk_score > 50. Security-focused
# capabilities often score high, so we report findings but do not
# fail the wrapper by default. CI can decide whether to gate merges.
if eval "${cmd}"; then
echo " ✓ ${cap_dir}/ passed"
echo " ✓ ${cap_dir}/ scan completed"
else
exit_code=$?
echo " ✗ ${cap_dir}/ has findings (exit ${exit_code})"
overall_exit=1
echo " ⚠ ${cap_dir}/ scan completed with findings (exit ${exit_code})"
fi
echo ""
done

# CI summary
if [[ "${CI_MODE}" == true ]]; then
sarif_files=()
for cap_dir in "${CAPABILITY_DIRS[@]}"; do
f="${REPO_ROOT}/.security-scan-${cap_dir}.sarif"
if [[ -f "${f}" ]]; then
sarif_files+=("${f}")
fi
done

if [[ ${#sarif_files[@]} -gt 0 ]]; then
if [[ -n "${OUTPUT_SARIF}" ]]; then
cp "${sarif_files[-1]}" "${OUTPUT_SARIF}"
fi
echo "SARIF reports: ${sarif_files[*]}"
fi
fi

if [[ "${overall_exit}" -eq 0 ]]; then
echo "All scans passed."
else
echo "Security scan found issues above threshold."
exit 1
fi
Loading
Loading