---
name: OpenHands Vulnerability Remediation
description: Scan repositories for vulnerabilities and create PRs with fixes using OpenHands agents
author: OpenHands

branding:
    icon: shield
    color: red

inputs:
    llm-model:
        description: >
            LLM model to use for remediation.
            Example: 'anthropic/claude-sonnet-4-5-20250929' or 'openai/gpt-4o'
        required: false
        default: anthropic/claude-sonnet-4-5-20250929
    llm-base-url:
        description: LLM base URL (optional, for custom LLM endpoints)
        required: false
        default: ''
    max-vulnerabilities:
        description: Maximum number of vulnerabilities to remediate per run (0 = unlimited)
        required: false
        default: '5'
    severity-threshold:
        description: "Minimum severity to remediate: 'CRITICAL', 'HIGH', 'MEDIUM', or 'LOW'"
        required: false
        default: HIGH
    extensions-repo:
        description: GitHub repository for extensions (owner/repo)
        required: false
        default: OpenHands/extensions
    extensions-version:
        description: Git ref to use for extensions (tag, branch, or commit SHA)
        required: false
        default: main
    llm-api-key:
        description: LLM API key (required)
        required: true
    github-token:
        description: GitHub token for API access and creating PRs (required)
        required: true

outputs:
    vulnerabilities-found:
        description: Number of vulnerabilities found that match the severity threshold
        value: ${{ steps.scan.outputs.vulnerabilities-found }}
    scan-only:
        description: Whether this was a scan-only run (no vulnerabilities to remediate)
        value: ${{ steps.scan.outputs.scan-only }}

runs:
    using: composite
    steps:
        - name: Checkout extensions repository
          uses: actions/checkout@v4
          with:
              repository: ${{ inputs.extensions-repo }}
              ref: ${{ inputs.extensions-version }}
              path: extensions

        - name: Checkout target repository
          uses: actions/checkout@v4
          with:
              fetch-depth: 0
              persist-credentials: true
              path: target-repo

        - name: Set up Python
          uses: actions/setup-python@v5
          with:
              python-version: '3.12'

        - name: Install uv
          uses: astral-sh/setup-uv@v6
          with:
              enable-cache: true

        - name: Install Trivy
          shell: bash
          run: |
              TRIVY_VERSION=$(curl -sL "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
              curl -sL "https://github.com/aquasecurity/trivy/releases/download/${TRIVY_VERSION}/trivy_${TRIVY_VERSION#v}_Linux-64bit.tar.gz" | tar xzf - -C /usr/local/bin trivy
              trivy --version

        - name: Install GitHub CLI
          shell: bash
          run: |
              if ! command -v gh &> /dev/null; then
                sudo apt-get update
                sudo apt-get install -y gh
              fi

        - name: Check required configuration
          shell: bash
          env:
              LLM_API_KEY: ${{ inputs.llm-api-key }}
              GITHUB_TOKEN: ${{ inputs.github-token }}
              INPUTS_EXTENSIONS_VERSION: ${{ inputs.extensions-version }}
              INPUTS_LLM_MODEL: ${{ inputs.llm-model }}
              INPUTS_SEVERITY_THRESHOLD: ${{ inputs.severity-threshold }}
              INPUTS_MAX_VULNERABILITIES: ${{ inputs.max-vulnerabilities }}
          run: |
              if [ -z "$LLM_API_KEY" ]; then
                echo "Error: llm-api-key is required."
                exit 1
              fi

              if [ -z "$GITHUB_TOKEN" ]; then
                echo "Error: github-token is required."
                exit 1
              fi

              echo "Repository: ${{ github.repository }}"
              echo "Extensions Version: $INPUTS_EXTENSIONS_VERSION"
              echo "LLM Model: $INPUTS_LLM_MODEL"
              echo "Severity Threshold: $INPUTS_SEVERITY_THRESHOLD"
              echo "Max Vulnerabilities: $INPUTS_MAX_VULNERABILITIES"

        - name: Run vulnerability scan
          id: scan
          shell: bash
          env:
              SEVERITY_THRESHOLD: ${{ inputs.severity-threshold }}
              MAX_VULNERABILITIES: ${{ inputs.max-vulnerabilities }}
          run: |
              cd target-repo
              uv run python ../extensions/plugins/vulnerability-remediation/scripts/scan_and_remediate.py --scan-only
              
              # Read the vulnerability count from the scan results with error handling
              if [ -f "scan-results.json" ]; then
                VULN_COUNT=$(python3 -c "
              import sys, json
              try:
                  with open('scan-results.json') as f:
                      data = json.load(f)
                  print(data.get('vulnerabilities_to_fix', 0))
              except (json.JSONDecodeError, KeyError, FileNotFoundError) as e:
                  print(f'Error parsing scan results: {e}', file=sys.stderr)
                  print(0)
              " 2>&1) || VULN_COUNT=0
                
                # Ensure VULN_COUNT is a valid number
                if ! [[ "$VULN_COUNT" =~ ^[0-9]+$ ]]; then
                  echo "⚠️ Failed to parse vulnerability count, defaulting to 0"
                  VULN_COUNT=0
                fi
                
                echo "vulnerabilities-found=$VULN_COUNT" >> $GITHUB_OUTPUT
                
                if [ "$VULN_COUNT" -eq 0 ]; then
                  echo "scan-only=true" >> $GITHUB_OUTPUT
                  echo "✅ No vulnerabilities found that match criteria. Skipping remediation."
                else
                  echo "scan-only=false" >> $GITHUB_OUTPUT
                  echo "🔍 Found $VULN_COUNT vulnerabilities to remediate."
                fi
              else
                echo "vulnerabilities-found=0" >> $GITHUB_OUTPUT
                echo "scan-only=true" >> $GITHUB_OUTPUT
                echo "✅ No scan results found. Skipping remediation."
              fi

        - name: Run remediation agent
          if: steps.scan.outputs.vulnerabilities-found != '0'
          shell: bash
          env:
              LLM_MODEL: ${{ inputs.llm-model }}
              LLM_BASE_URL: ${{ inputs.llm-base-url }}
              LLM_API_KEY: ${{ inputs.llm-api-key }}
              GITHUB_TOKEN: ${{ inputs.github-token }}
              REPO_NAME: ${{ github.repository }}
              SEVERITY_THRESHOLD: ${{ inputs.severity-threshold }}
              MAX_VULNERABILITIES: ${{ inputs.max-vulnerabilities }}
          run: |
              cd target-repo
              echo "🤖 Starting OpenHands agent for vulnerability remediation..."
              uv run --with openhands-sdk --with openhands-tools \
                python ../extensions/plugins/vulnerability-remediation/scripts/scan_and_remediate.py --remediate

        - name: Upload scan results
          uses: actions/upload-artifact@v4
          if: always()
          with:
              name: vulnerability-scan-results
              path: |
                  target-repo/trivy-results.json
                  target-repo/scan-results.json
                  target-repo/remediation-report.json
              retention-days: 30
              if-no-files-found: ignore
