Skip to content
Cisco AI Defense logo
CiscoAI Security

GitHub Actions and Pre-commit — Skill Scanner

GitHub Actions and Pre-commit

Scan skills automatically on every push or pull request with the reusable GitHub Actions workflow, or before every commit with the pre-commit hook. In CI, findings appear as inline annotations on pull requests through GitHub Code Scanning.

Which preset and threshold to use is covered in Recommended Settings. The short version:

  • Every setup runs the LLM judge (use_llm: true). Rules alone catch only about 8% of malicious skills.
  • For your own skills, use low-noise with the judge, failing at HIGH.
  • For third-party skills, use balanced (highest F1) or quiet (lowest false-positive rate) with the judge.

Quick Start

Add your model provider's key as the repository secret SKILL_SCANNER_LLM_API_KEY, then add this file to your repository at .github/workflows/scan-skills.yml:

name: Scan Skills

on:
  push:
    paths: [".cursor/skills/**"]
  pull_request:
    paths: [".cursor/skills/**"]

jobs:
  scan:
    uses: cisco-ai-defense/skill-scanner/.github/workflows/scan-skills.yml@2.2.1
    with:
      scanner_version: "2.2.1"
      skill_path: .cursor/skills
      policy: low-noise
      fail_on_severity: high
      use_llm: true
      llm_model: anthropic/claude-sonnet-5-5
    secrets:
      llm_api_key: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
    permissions:
      security-events: write
      contents: read
      actions: read

For Bedrock, Vertex AI or a model you host, use the self-hosted workflow with the judge on Bedrock instead.

Pin the workflow to a release tag such as @2.2.1, not @main. The caller must grant the three permissions shown. A reusable workflow can only use permissions its caller grants, and the SARIF upload needs security-events: write.

This will:

  1. Install cisco-ai-skill-scanner from PyPI on a fresh runner (pin the version with the scanner_version input)
  2. Run skill-scanner scan-all --use-llm with SARIF output, recursive scanning, and cross-skill overlap detection
  3. Upload SARIF results to GitHub Code Scanning (findings appear as annotations on PRs)
  4. Fail the workflow if any findings at or above HIGH severity are detected

Reusable Workflow Inputs

InputTypeDefaultDescription
skill_pathstring(required)Path to skills directory or single skill
scan_modestringscan-allscan (single skill) or scan-all (directory)
formatstringsarifOutput format: summary, json, markdown, table, sarif, html
policystringbalancedPreset (balanced, low-noise, quiet, strict, permissive) or path to custom YAML
scanner_versionstring"" (latest)cisco-ai-skill-scanner release to install, e.g. 2.2.0. Pin it to the same release as the workflow ref
fail_on_severitystringhighFail if findings at/above this severity
python_versionstring3.12Numeric CPython version from 3.11 through 3.14
upload_sarifbooleantrueUpload SARIF to Code Scanning
use_llmbooleanfalseEnable LLM semantic analysis
llm_modelstring""LLM model, as provider/model (e.g., anthropic/claude-sonnet-5-5)
llm_providerstring""Provider override, e.g. openai-compatible for a gateway
llm_base_urlstring""Base URL for an OpenAI-compatible gateway
aws_regionstringus-east-1Region for bedrock/ models; pass a Bedrock API key as the llm_api_key secret
use_behavioralbooleanfalseEnable behavioral dataflow analysis
lenientbooleanfalseTolerate malformed skills
extra_argsstring""Additional CLI flags accepted by the workflow's explicit allowlist

The extra_args allowlist accepts additive analyzer and tuning options. These include --rule-packs, --use-osv, --use-virustotal, --use-aidefense, --use-trigger, --llm-decompose, --adjudicate, LLM limits, and repository-local policy and taxonomy paths. Unknown flags are rejected before the scan runs.

The reusable workflow can reach Bedrock with a Bedrock API key (the llm_api_key secret, a bedrock/ model and aws_region), but it cannot assume an AWS role or another cloud identity. For role-based Bedrock or Vertex AI, use the self-hosted workflow.

A run that cannot start as configured fails with "could not run as configured" (scanner exit code 2), which is different from "found findings". An unknown policy or a judge without a working key causes it.


Secrets

All secrets are optional and only needed for advanced analysis.

SecretMaps ToRequired For
llm_api_keySKILL_SCANNER_LLM_API_KEYuse_llm: true
virustotal_api_keyVIRUSTOTAL_API_KEY--use-virustotal (via extra_args)

Configure secrets in Settings > Secrets and variables > Actions. They are never exposed in logs.


Configuration Tiers

Tier 1: Rules + LLM Judge — your own skills

jobs:
  scan:
    uses: cisco-ai-defense/skill-scanner/.github/workflows/scan-skills.yml@2.2.1
    with:
      scanner_version: "2.2.1"
      skill_path: .cursor/skills
      policy: low-noise
      use_llm: true
      llm_model: anthropic/claude-sonnet-5-5
    secrets:
      llm_api_key: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
    permissions:
      security-events: write
      contents: read
      actions: read

Fails on HIGH; MEDIUM findings appear as annotations for the reviewer.

Tier 2: Rules + LLM Judge — third-party skills

jobs:
  scan:
    uses: cisco-ai-defense/skill-scanner/.github/workflows/scan-skills.yml@2.2.1
    with:
      scanner_version: "2.2.1"
      skill_path: vendor/skills
      policy: balanced        # quiet for the lowest false-positive rate
      use_llm: true
      llm_model: anthropic/claude-sonnet-5-5
    secrets:
      llm_api_key: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
    permissions:
      security-events: write
      contents: read
      actions: read

For an OpenAI-compatible gateway, add llm_provider: openai-compatible and llm_base_url.

Tier 3: Judge + Behavioral + VirusTotal

jobs:
  scan:
    uses: cisco-ai-defense/skill-scanner/.github/workflows/scan-skills.yml@2.2.1
    with:
      scanner_version: "2.2.1"
      skill_path: vendor/skills
      use_llm: true
      use_behavioral: true
      extra_args: --use-virustotal --use-osv
    secrets:
      llm_api_key: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
      virustotal_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
    permissions:
      security-events: write
      contents: read
      actions: read

Keep strict for audits where a person reads every finding; it is not measured as a gate.


Branch Protection

Block PRs with security findings from merging:

  1. Go to Settings > Branches > Branch protection rules
  2. Enable Require status checks to pass before merging
  3. Search for and select the Skill Scanner check
  4. Save changes

PRs that touch skill files must pass the security scan before they can be merged.


Pre-commit Hook

Scan skills, with the judge, before every commit using the pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/cisco-ai-defense/skill-scanner
    rev: 2.2.1  # the latest release tag (no "v" prefix)
    hooks:
      - id: skill-scanner

Turn the judge on in .skill_scannerrc at the repository root (use_llm is off by default):

{
  "skills_path": ".claude/skills",
  "policy": "low-noise",
  "use_llm": true,
  "llm_model": "anthropic/claude-sonnet-5-5",
  "severity_threshold": "high",
  "fail_fast": true
}

The hook scans only the skills a commit touches. The key comes from SKILL_SCANNER_LLM_API_KEY, or from cloud credentials for Bedrock and Vertex AI. llm_model and llm_provider fall back to SKILL_SCANNER_LLM_MODEL and SKILL_SCANNER_LLM_PROVIDER, so the hook can point at a local model. If the judge cannot be built, the commit is blocked with exit code 2 instead of passing on the rules alone. For a bedrock/ model, add additional_dependencies: [boto3] to the hook. Run pre-commit install once, or skill-scanner-pre-commit --install without the pre-commit framework.

For incremental CI scans, let pre-commit supply the changed paths between two checked-out revisions:

pre-commit run skill-scanner --from-ref "$BASE_SHA" --to-ref "$HEAD_SHA"

Both revisions must be available in the checkout (for example, fetch-depth: 0 with actions/checkout).


Self-Hosted Workflow

If you prefer not to use the reusable workflow, copy this standalone workflow:

name: Scan Skills

on:
  push:
    paths: [".cursor/skills/**"]
  pull_request:
    paths: [".cursor/skills/**"]

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - run: pip install "cisco-ai-skill-scanner==2.2.1"

      - name: Scan skills
        env:
          SKILL_SCANNER_LLM_API_KEY: ${{ secrets.SKILL_SCANNER_LLM_API_KEY }}
          SKILL_SCANNER_LLM_MODEL: anthropic/claude-sonnet-5-5
        run: |
          skill-scanner scan-all .cursor/skills \
            --use-llm \
            --policy low-noise \
            --format sarif \
            --output results.sarif \
            --recursive \
            --check-overlap \
            --fail-on-severity high

      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: results.sarif

Custom Policy in CI

Commit a custom policy to the repository and reference it:

with:
  skill_path: .cursor/skills
  policy: .github/scan-policy.yaml
  use_llm: true
  llm_model: anthropic/claude-sonnet-5-5

Or in the self-hosted workflow:

skill-scanner scan-all .cursor/skills \
  --use-llm \
  --policy .github/scan-policy.yaml \
  --format sarif \
  --output results.sarif \
  --fail-on-severity high

Self-Hosted Workflow with the Judge on Bedrock

The model the published figures were measured with runs on Bedrock. Assume a role with OIDC, then run the CLI:

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      security-events: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.BEDROCK_ROLE_ARN }}
          aws-region: us-east-1
      - run: pip install "cisco-ai-skill-scanner[bedrock]==2.2.1"
      - name: Scan skills
        env:
          SKILL_SCANNER_LLM_MODEL: bedrock-mantle/google.gemma-4-26b-a4b
        run: |
          skill-scanner scan-all vendor/skills --recursive --use-llm --policy balanced \
            --fail-on-severity high --format sarif --output results.sarif
      - uses: github/codeql-action/upload-sarif@v4
        if: always()
        with:
          sarif_file: results.sarif

If the judge cannot start, for example because the role cannot reach the model, the scan exits with code 2 instead of passing with rules only.