name: Process Community Advisory

on:
  issues:
    types: [labeled]

permissions: read-all

concurrency:
  group: community-advisory
  cancel-in-progress: false

env:
  FEED_PATH: advisories/feed.json
  FEED_SIG_PATH: advisories/feed.json.sig
  SKILL_FEED_PATH: skills/clawsec-feed/advisories/feed.json
  SKILL_FEED_SIG_PATH: skills/clawsec-feed/advisories/feed.json.sig

jobs:
  process-advisory:
    if: github.event.label.name == 'advisory-approved'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          fetch-depth: 1

      - name: Parse issue and create advisory
        id: parse
        env:
          ISSUE_BODY: ${{ github.event.issue.body }}
          ISSUE_NUMBER: ${{ github.event.issue.number }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_CREATED_AT: ${{ github.event.issue.created_at }}
        run: |
          # Generate advisory ID: CLAW-YYYY-{issue_number padded to 4 digits}
          # Use issue creation year, not current year, to ensure consistency
          YEAR=$(echo "$ISSUE_CREATED_AT" | cut -c1-4)
          PADDED_NUM=$(printf "%04d" "$ISSUE_NUMBER")
          ADVISORY_ID="CLAW-${YEAR}-${PADDED_NUM}"
          echo "advisory_id=$ADVISORY_ID" >> $GITHUB_OUTPUT
          echo "Generated advisory ID: $ADVISORY_ID"

          # Check if advisory already exists by issue URL (dedupe by issue, not by ID)
          # This prevents duplicates when the same issue is labeled in different years
          if jq -e --arg url "$ISSUE_URL" '.advisories[] | select(.github_issue_url == $url)' "$FEED_PATH" > /dev/null 2>&1; then
            echo "Advisory for issue $ISSUE_URL already exists in feed"
            echo "already_exists=true" >> $GITHUB_OUTPUT
            exit 0
          fi
          echo "already_exists=false" >> $GITHUB_OUTPUT

          # Parse opener type (human vs agent)
          if echo "$ISSUE_BODY" | grep -q '\[x\] Agent'; then
            OPENER_TYPE="agent"
          else
            OPENER_TYPE="human"
          fi
          echo "Opener type: $OPENER_TYPE"

          # Parse report type
          if echo "$ISSUE_BODY" | grep -q '\[x\] Malicious Prompt'; then
            REPORT_TYPE="prompt_injection"
          elif echo "$ISSUE_BODY" | grep -q '\[x\] Vulnerable Skill'; then
            REPORT_TYPE="vulnerable_skill"
          elif echo "$ISSUE_BODY" | grep -q '\[x\] Tampering Attempt'; then
            REPORT_TYPE="tampering_attempt"
          else
            REPORT_TYPE="unknown"
          fi
          echo "Report type: $REPORT_TYPE"

          # Parse severity
          if echo "$ISSUE_BODY" | grep -q '\[x\] Critical'; then
            SEVERITY="critical"
          elif echo "$ISSUE_BODY" | grep -q '\[x\] High'; then
            SEVERITY="high"
          elif echo "$ISSUE_BODY" | grep -q '\[x\] Medium'; then
            SEVERITY="medium"
          elif echo "$ISSUE_BODY" | grep -q '\[x\] Low'; then
            SEVERITY="low"
          else
            SEVERITY="medium"
          fi
          echo "Severity: $SEVERITY"

          # Parse title (between ## Title and ## Description)
          TITLE=$(echo "$ISSUE_BODY" | sed -n '/^## Title/,/^## Description/p' | grep -v '^## ' | grep -v '^<!--' | grep -v '^\s*$' | head -1 | xargs)
          if [ -z "$TITLE" ]; then
            TITLE="$ISSUE_TITLE"
          fi
          echo "Title: $TITLE"

          # Parse description (between ## Description and ---)
          DESCRIPTION=$(echo "$ISSUE_BODY" | sed -n '/^## Description/,/^---/p' | grep -v '^## Description' | grep -v '^---' | grep -v '^<!--' | sed '/^\s*$/d' | tr '\n' ' ' | xargs)
          if [ -z "$DESCRIPTION" ]; then
            DESCRIPTION="See issue for details."
          fi
          echo "Description: ${DESCRIPTION:0:100}..."

          # Parse skill name and version
          SKILL_NAME=$(echo "$ISSUE_BODY" | sed -n '/^### Skill Name/,/^### /p' | grep -v '^### ' | grep -v '^<!--' | grep -v '^\s*$' | head -1 | xargs)
          SKILL_VERSION=$(echo "$ISSUE_BODY" | sed -n '/^### Skill Version/,/^### /p' | grep -v '^### ' | grep -v '^<!--' | grep -v '^\s*$' | head -1 | xargs)

          # Build affected array
          AFFECTED="[]"
          if [ -n "$SKILL_NAME" ] && [ -n "$SKILL_VERSION" ]; then
            AFFECTED=$(jq -n --arg name "$SKILL_NAME" --arg ver "$SKILL_VERSION" '[$name + "@" + $ver]')
          elif [ -n "$SKILL_NAME" ]; then
            AFFECTED=$(jq -n --arg name "$SKILL_NAME" '[$name]')
          fi
          echo "Affected: $AFFECTED"

          # Build platforms array
          OPENCLAW_SELECTED="false"
          if echo "$ISSUE_BODY" | grep -qi '^[[:space:]]*-[[:space:]]*\[[xX]\][[:space:]]*OpenClaw'; then
            OPENCLAW_SELECTED="true"
          fi

          OTHER_PLATFORM_RAW=$(echo "$ISSUE_BODY" | sed -n 's/^[[:space:]]*-[[:space:]]*\[[xX]\][[:space:]]*Other:[[:space:]]*\(.*\)$/\1/p' | head -1 | xargs)
          OTHER_PLATFORM=""
          if [ -n "$OTHER_PLATFORM_RAW" ]; then
            OTHER_PLATFORM=$(echo "$OTHER_PLATFORM_RAW" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//')
            if echo "$OTHER_PLATFORM" | grep -q 'nanoclaw'; then
              OTHER_PLATFORM="nanoclaw"
            fi
          fi

          PLATFORMS=$(jq -n --arg open "$OPENCLAW_SELECTED" --arg other "$OTHER_PLATFORM" '
            [
              (if $open == "true" then "openclaw" else empty end),
              (if ($other | length) > 0 then $other else empty end)
            ] | unique
          ')
          if [ "$PLATFORMS" = "[]" ]; then
            PLATFORMS='["openclaw","nanoclaw"]'
          fi
          echo "Platforms: $PLATFORMS"

          # Parse recommended action
          ACTION=$(echo "$ISSUE_BODY" | sed -n '/^## Recommended Action/,/^---/p' | grep -v '^## Recommended Action' | grep -v '^---' | grep -v '^<!--' | sed '/^\s*$/d' | tr '\n' ' ' | xargs)
          if [ -z "$ACTION" ]; then
            ACTION="Review the advisory details and take appropriate action."
          fi
          echo "Action: ${ACTION:0:100}..."

          # Parse reporter name
          REPORTER_NAME=$(echo "$ISSUE_BODY" | grep -A1 '\*\*Agent/User Name:\*\*' | tail -1 | sed 's/\*\*Contact:\*\*.*//' | xargs)
          if [ -z "$REPORTER_NAME" ]; then
            REPORTER_NAME=$(echo "$ISSUE_BODY" | sed -n 's/.*\*\*Agent\/User Name:\*\*\s*\(.*\)/\1/p' | xargs)
          fi
          echo "Reporter: $REPORTER_NAME"

          # Get current timestamp
          PUBLISHED=$(date -u +%Y-%m-%dT%H:%M:%SZ)

          # Create advisory JSON
          jq -n \
            --arg id "$ADVISORY_ID" \
            --arg severity "$SEVERITY" \
            --arg type "$REPORT_TYPE" \
            --arg title "$TITLE" \
            --arg description "$DESCRIPTION" \
            --argjson affected "$AFFECTED" \
            --argjson platforms "$PLATFORMS" \
            --arg action "$ACTION" \
            --arg published "$PUBLISHED" \
            --arg source "Community Report" \
            --arg issue_url "$ISSUE_URL" \
            --arg reporter_name "$REPORTER_NAME" \
            --arg opener_type "$OPENER_TYPE" \
            '{
              id: $id,
              severity: $severity,
              type: $type,
              title: $title,
              description: $description,
              affected: $affected,
              platforms: $platforms,
              action: $action,
              published: $published,
              references: [],
              source: $source,
              github_issue_url: $issue_url,
              reporter: {
                agent_name: $reporter_name,
                opener_type: $opener_type
              }
            }' > tmp_advisory.json

          echo "Created advisory JSON:"
          cat tmp_advisory.json

      - name: Set up Python for exploitability analysis
        if: steps.parse.outputs.already_exists != 'true'
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: '3.10'

      - name: Analyze exploitability for community advisory
        if: steps.parse.outputs.already_exists != 'true'
        run: |
          set -euo pipefail

          echo "=== Analyzing exploitability for community advisory ==="

          scripts/ci/enrich_exploitability.sh \
            --mode single \
            --input tmp_advisory.json \
            --output tmp_advisory.json

          echo "=== Exploitability analysis complete ==="
          echo "Exploitability score: $(jq -r '.exploitability_score // "unknown"' tmp_advisory.json)"

      - name: Update feed
        if: steps.parse.outputs.already_exists != 'true'
        run: |
          NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)

          # Add new advisory to feed
          jq --argjson new "$(cat tmp_advisory.json)" --arg now "$NOW" '
            .updated = $now |
            .advisories = ([$new] + .advisories)
          ' "$FEED_PATH" > tmp_feed.json

          # Validate JSON
          if jq empty tmp_feed.json 2>/dev/null; then
            echo "Feed JSON is valid"
            mv tmp_feed.json "$FEED_PATH"

            # Sync to skill feed
            mkdir -p "$(dirname "$SKILL_FEED_PATH")"
            cp "$FEED_PATH" "$SKILL_FEED_PATH"

            echo "Updated feeds:"
            echo "  - $FEED_PATH"
            echo "  - $SKILL_FEED_PATH"

            TOTAL=$(jq '.advisories | length' "$FEED_PATH")
            echo "Total advisories: $TOTAL"
          else
            echo "Error: Generated invalid JSON"
            exit 1
          fi

      - name: Sign advisory feed and verify
        if: steps.parse.outputs.already_exists != 'true'
        uses: ./.github/actions/sign-and-verify
        with:
          private_key: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY }}
          private_key_passphrase: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE }}
          input_file: ${{ env.FEED_PATH }}
          signature_file: ${{ env.FEED_SIG_PATH }}
          verify_files: |
            ${{ env.FEED_PATH }}
            ${{ env.SKILL_FEED_PATH }}

      - name: Sync advisory signature to skill feed
        if: steps.parse.outputs.already_exists != 'true'
        run: cp "$FEED_SIG_PATH" "$SKILL_FEED_SIG_PATH"

      - name: Require automation token for write operations
        env:
          AUTOMATION_TOKEN: ${{ secrets.POLL_NVD_CVES_PAT }}
        run: |
          if [ -z "$AUTOMATION_TOKEN" ]; then
            echo "::error::Set POLL_NVD_CVES_PAT with repo write permissions."
            exit 1
          fi

      - name: Create Pull Request
        if: steps.parse.outputs.already_exists != 'true'
        id: create-pr
        uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
        with:
          token: ${{ secrets.POLL_NVD_CVES_PAT }}
          branch: automated/community-advisory-${{ github.event.issue.number }}
          delete-branch: true
          title: "chore: add community advisory ${{ steps.parse.outputs.advisory_id }}"
          body: |
            ## Summary
            Add community advisory `${{ steps.parse.outputs.advisory_id }}` from issue #${{ github.event.issue.number }}.

            - Issue: ${{ github.event.issue.html_url }}
            - Reporter: @${{ github.event.issue.user.login }}
            - Trigger: `advisory-approved` label

            ---
            *This PR was generated by the community advisory workflow.*
          commit-message: |
            chore: add community advisory ${{ steps.parse.outputs.advisory_id }}

            Added from issue #${{ github.event.issue.number }}
            Issue: ${{ github.event.issue.html_url }}
          add-paths: |
            ${{ env.FEED_PATH }}
            ${{ env.FEED_SIG_PATH }}
            ${{ env.SKILL_FEED_PATH }}
            ${{ env.SKILL_FEED_SIG_PATH }}

      - name: Comment on issue
        if: steps.parse.outputs.already_exists != 'true'
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        with:
          github-token: ${{ secrets.POLL_NVD_CVES_PAT }}
          script: |
            const advisoryId = '${{ steps.parse.outputs.advisory_id }}';
            const pullRequestUrl = '${{ steps.create-pr.outputs.pull-request-url }}';
            const operation = '${{ steps.create-pr.outputs.pull-request-operation }}';
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## Advisory Pull Request Opened

            This security report has been prepared for publication in the ClawSec advisory feed.

            **Advisory ID:** \`${advisoryId}\`
            **Pull Request:** ${pullRequestUrl || 'No PR generated (no file changes detected)'}
            **PR Operation:** \`${operation || 'none'}\`

            The advisory will be published after the pull request is merged.

            Thank you for your contribution to community security!`
            });

      - name: Comment if already exists
        if: steps.parse.outputs.already_exists == 'true'
        uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
        with:
          github-token: ${{ secrets.POLL_NVD_CVES_PAT }}
          script: |
            const advisoryId = '${{ steps.parse.outputs.advisory_id }}';
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## Advisory Already Exists

            An advisory with ID \`${advisoryId}\` already exists in the feed. No changes were made.

            If this is a different issue, please open a new issue.`
            });
