leaktor 0.4.1

A secrets scanner with pattern matching, entropy analysis, and live validation
Documentation
name: 'Leaktor Secrets Scanner'
description: 'Scan your repository for leaked secrets, API keys, and credentials with Leaktor'
author: 'Jonas Resch'

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

inputs:
  version:
    description: 'Leaktor version to use (e.g., "0.2.0" or "latest")'
    required: false
    default: 'latest'
  scan-path:
    description: 'Path to scan (defaults to repository root)'
    required: false
    default: '.'
  format:
    description: 'Output format: console, json, sarif, html'
    required: false
    default: 'sarif'
  output:
    description: 'Output file path'
    required: false
    default: 'leaktor-results.sarif'
  validate:
    description: 'Validate detected secrets against live APIs'
    required: false
    default: 'false'
  fail-on-found:
    description: 'Fail the action if any secrets are found'
    required: false
    default: 'true'
  baseline:
    description: 'Path to baseline file to suppress known findings'
    required: false
    default: ''
  scan-mode:
    description: 'Scan mode: full, pr-diff, or history'
    required: false
    default: 'full'
  min-confidence:
    description: 'Minimum confidence threshold (0.0-1.0)'
    required: false
    default: '0.6'
  exclude-tests:
    description: 'Exclude test files from scanning'
    required: false
    default: 'false'
  extra-args:
    description: 'Additional arguments to pass to leaktor scan'
    required: false
    default: ''

outputs:
  findings-count:
    description: 'Number of secrets found'
    value: ${{ steps.scan.outputs.findings_count }}
  sarif-file:
    description: 'Path to the SARIF output file (if format is sarif)'
    value: ${{ steps.scan.outputs.sarif_file }}

runs:
  using: 'composite'
  steps:
    - name: Install Leaktor
      shell: bash
      run: |
        set -euo pipefail

        VERSION="${{ inputs.version }}"

        if [ "$VERSION" = "latest" ]; then
          echo "Installing latest version of Leaktor..."
          cargo install leaktor 2>/dev/null || {
            # Fallback: download pre-built binary
            ARCH=$(uname -m)
            OS=$(uname -s | tr '[:upper:]' '[:lower:]')

            if [ "$OS" = "linux" ] && [ "$ARCH" = "x86_64" ]; then
              ASSET="leaktor-linux-amd64"
            elif [ "$OS" = "darwin" ] && [ "$ARCH" = "x86_64" ]; then
              ASSET="leaktor-macos-amd64"
            elif [ "$OS" = "darwin" ] && [ "$ARCH" = "arm64" ]; then
              ASSET="leaktor-macos-arm64"
            else
              echo "::error::Unsupported platform: $OS/$ARCH"
              exit 1
            fi

            DOWNLOAD_URL="https://github.com/reschjonas/leaktor/releases/latest/download/$ASSET"
            echo "Downloading from $DOWNLOAD_URL"
            curl -fsSL "$DOWNLOAD_URL" -o /usr/local/bin/leaktor
            chmod +x /usr/local/bin/leaktor
          }
        else
          echo "Installing Leaktor v${VERSION}..."
          cargo install leaktor --version "$VERSION" 2>/dev/null || {
            ARCH=$(uname -m)
            OS=$(uname -s | tr '[:upper:]' '[:lower:]')

            if [ "$OS" = "linux" ] && [ "$ARCH" = "x86_64" ]; then
              ASSET="leaktor-linux-amd64"
            elif [ "$OS" = "darwin" ] && [ "$ARCH" = "x86_64" ]; then
              ASSET="leaktor-macos-amd64"
            elif [ "$OS" = "darwin" ] && [ "$ARCH" = "arm64" ]; then
              ASSET="leaktor-macos-arm64"
            else
              echo "::error::Unsupported platform: $OS/$ARCH"
              exit 1
            fi

            DOWNLOAD_URL="https://github.com/reschjonas/leaktor/releases/download/v${VERSION}/$ASSET"
            echo "Downloading from $DOWNLOAD_URL"
            curl -fsSL "$DOWNLOAD_URL" -o /usr/local/bin/leaktor
            chmod +x /usr/local/bin/leaktor
          }
        fi

        leaktor --version

    - name: Run Leaktor Scan
      id: scan
      shell: bash
      run: |
        set -euo pipefail

        ARGS="scan ${{ inputs.scan-path }}"
        ARGS="$ARGS --format ${{ inputs.format }}"
        ARGS="$ARGS --min-confidence ${{ inputs.min-confidence }}"

        # Output file
        if [ -n "${{ inputs.output }}" ]; then
          ARGS="$ARGS --output ${{ inputs.output }}"
        fi

        # Validation
        if [ "${{ inputs.validate }}" = "true" ]; then
          ARGS="$ARGS --validate"
        fi

        # Fail on found
        if [ "${{ inputs.fail-on-found }}" = "true" ]; then
          ARGS="$ARGS --fail-on-found"
        fi

        # Exclude tests
        if [ "${{ inputs.exclude-tests }}" = "true" ]; then
          ARGS="$ARGS --exclude-tests"
        fi

        # Baseline
        if [ -n "${{ inputs.baseline }}" ] && [ -f "${{ inputs.baseline }}" ]; then
          ARGS="$ARGS --baseline ${{ inputs.baseline }}"
        fi

        # Scan mode
        SCAN_MODE="${{ inputs.scan-mode }}"
        if [ "$SCAN_MODE" = "pr-diff" ]; then
          # For PR diff scanning, use --since-commit with the merge base
          if [ -n "${GITHUB_BASE_REF:-}" ]; then
            MERGE_BASE=$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD 2>/dev/null || echo "")
            if [ -n "$MERGE_BASE" ]; then
              ARGS="$ARGS --since-commit $MERGE_BASE"
            fi
          fi
        elif [ "$SCAN_MODE" = "history" ]; then
          ARGS="$ARGS --git-history=true"
        fi

        # Extra args
        if [ -n "${{ inputs.extra-args }}" ]; then
          ARGS="$ARGS ${{ inputs.extra-args }}"
        fi

        echo "Running: leaktor $ARGS"

        # Run and capture exit code
        EXIT_CODE=0
        leaktor $ARGS || EXIT_CODE=$?

        # Count findings from output file if it exists
        FINDINGS_COUNT=0
        if [ -f "${{ inputs.output }}" ]; then
          if [ "${{ inputs.format }}" = "json" ]; then
            FINDINGS_COUNT=$(python3 -c "import json; d=json.load(open('${{ inputs.output }}')); print(d.get('summary',{}).get('total_findings',0))" 2>/dev/null || echo "0")
          elif [ "${{ inputs.format }}" = "sarif" ]; then
            FINDINGS_COUNT=$(python3 -c "import json; d=json.load(open('${{ inputs.output }}')); print(sum(len(r.get('results',[])) for r in d.get('runs',[])))" 2>/dev/null || echo "0")
          fi
        fi

        echo "findings_count=$FINDINGS_COUNT" >> "$GITHUB_OUTPUT"

        if [ "${{ inputs.format }}" = "sarif" ] && [ -f "${{ inputs.output }}" ]; then
          echo "sarif_file=${{ inputs.output }}" >> "$GITHUB_OUTPUT"
        fi

        exit $EXIT_CODE

    - name: Upload SARIF to GitHub Security
      if: inputs.format == 'sarif' && always()
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: ${{ inputs.output }}
      continue-on-error: true