agent-shield 0.2.0

Security scanner for AI agent extensions — offline-first, multi-framework, SARIF output
Documentation
name: 'AgentShield Security Scanner'
description: 'Scan AI agent extensions (MCP servers, OpenClaw skills) for security vulnerabilities'
author: 'Ronaldo Lima'

branding:
  icon: 'shield'
  color: 'blue'

inputs:
  path:
    description: 'Path to scan (relative to repo root)'
    required: false
    default: '.'
  fail-on:
    description: 'Minimum severity to fail the check (info, low, medium, high, critical)'
    required: false
    default: 'high'
  format:
    description: 'Output format (console, json, sarif)'
    required: false
    default: 'sarif'
  config:
    description: 'Path to .agentshield.toml config file'
    required: false
    default: ''
  upload-sarif:
    description: 'Upload SARIF results to GitHub Code Scanning'
    required: false
    default: 'true'
  version:
    description: 'AgentShield version to use (default: latest)'
    required: false
    default: 'latest'

outputs:
  finding-count:
    description: 'Number of findings detected'
    value: ${{ steps.scan.outputs.finding-count }}
  sarif-file:
    description: 'Path to the SARIF output file (if format=sarif)'
    value: ${{ steps.scan.outputs.sarif-file }}
  exit-code:
    description: 'Exit code from the scan (0=pass, 1=fail, 2=error)'
    value: ${{ steps.scan.outputs.exit-code }}

runs:
  using: 'composite'
  steps:
    - name: Determine version
      id: version
      shell: bash
      run: |
        if [ "${{ inputs.version }}" = "latest" ]; then
          VERSION=$(curl -s https://api.github.com/repos/limaronaldo/agentshield/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
          if [ -z "$VERSION" ]; then
            echo "::error::Could not determine latest version. Specify version explicitly."
            exit 1
          fi
        else
          VERSION="${{ inputs.version }}"
        fi
        echo "version=$VERSION" >> $GITHUB_OUTPUT

    - name: Determine platform
      id: platform
      shell: bash
      run: |
        case "${{ runner.os }}-${{ runner.arch }}" in
          Linux-X64)   TARGET="x86_64-unknown-linux-gnu" ;;
          Linux-ARM64) TARGET="aarch64-unknown-linux-gnu" ;;
          macOS-X64)   TARGET="x86_64-apple-darwin" ;;
          macOS-ARM64) TARGET="aarch64-apple-darwin" ;;
          Windows-X64) TARGET="x86_64-pc-windows-msvc" ;;
          *) echo "::error::Unsupported platform: ${{ runner.os }}-${{ runner.arch }}"; exit 1 ;;
        esac
        echo "target=$TARGET" >> $GITHUB_OUTPUT

    - name: Download AgentShield
      shell: bash
      run: |
        VERSION="${{ steps.version.outputs.version }}"
        TARGET="${{ steps.platform.outputs.target }}"
        BASE_URL="https://github.com/limaronaldo/agentshield/releases/download/${VERSION}"

        if [[ "$TARGET" == *"windows"* ]]; then
          ARCHIVE="agentshield-${VERSION}-${TARGET}.zip"
          curl -fsSL "${BASE_URL}/${ARCHIVE}" -o agentshield.zip
          unzip -o agentshield.zip -d ${{ runner.temp }}/agentshield
        else
          ARCHIVE="agentshield-${VERSION}-${TARGET}.tar.gz"
          curl -fsSL "${BASE_URL}/${ARCHIVE}" -o agentshield.tar.gz
          mkdir -p ${{ runner.temp }}/agentshield
          tar xzf agentshield.tar.gz -C ${{ runner.temp }}/agentshield
        fi

        chmod +x ${{ runner.temp }}/agentshield/agentshield* 2>/dev/null || true
        echo "${{ runner.temp }}/agentshield" >> $GITHUB_PATH

    - name: Run scan
      id: scan
      shell: bash
      run: |
        ARGS="scan ${{ inputs.path }}"
        ARGS="$ARGS --fail-on ${{ inputs.fail-on }}"

        SARIF_FILE=""
        if [ "${{ inputs.format }}" = "sarif" ]; then
          SARIF_FILE="${{ runner.temp }}/agentshield-results.sarif"
          ARGS="$ARGS --format sarif --output $SARIF_FILE"
        else
          ARGS="$ARGS --format ${{ inputs.format }}"
        fi

        if [ -n "${{ inputs.config }}" ]; then
          ARGS="$ARGS --config ${{ inputs.config }}"
        fi

        set +e
        agentshield $ARGS
        EXIT_CODE=$?
        set -e

        echo "exit-code=$EXIT_CODE" >> $GITHUB_OUTPUT
        echo "sarif-file=$SARIF_FILE" >> $GITHUB_OUTPUT

        # Count findings from SARIF if available
        if [ -n "$SARIF_FILE" ] && [ -f "$SARIF_FILE" ]; then
          COUNT=$(python3 -c "import json; d=json.load(open('$SARIF_FILE')); print(len(d.get('runs',[{}])[0].get('results',[])))" 2>/dev/null || echo "0")
          echo "finding-count=$COUNT" >> $GITHUB_OUTPUT
        else
          echo "finding-count=0" >> $GITHUB_OUTPUT
        fi

        # Don't fail here — let the upload step run first
        echo "AGENTSHIELD_EXIT=$EXIT_CODE" >> $GITHUB_ENV

    - name: Upload SARIF to GitHub Code Scanning
      if: inputs.upload-sarif == 'true' && inputs.format == 'sarif' && steps.scan.outputs.sarif-file != ''
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: ${{ steps.scan.outputs.sarif-file }}
        category: agentshield
      continue-on-error: true

    - name: Check result
      shell: bash
      run: |
        if [ "$AGENTSHIELD_EXIT" = "1" ]; then
          echo "::error::AgentShield found findings above the '${{ inputs.fail-on }}' threshold"
          exit 1
        elif [ "$AGENTSHIELD_EXIT" = "2" ]; then
          echo "::error::AgentShield encountered a scan error"
          exit 2
        fi