name: Dependabot Provenance Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

permissions:
  contents: read
  pull-requests: write
  issues: write

jobs:
  check-provenance:
    if: >-
      github.event.pull_request.user.login == 'dependabot[bot]' &&
      !contains(github.event.pull_request.labels.*.name, 'provenance-exception')
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"

      - name: Setup Node
        if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn'
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: "24.13.0"

      - name: Check npm provenance attestation
        if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn'
        id: npm-provenance
        run: |
          IFS=',' read -ra DEPS <<< "${{ steps.metadata.outputs.dependency-names }}"
          UPDATED_DEPS_JSON='${{ steps.metadata.outputs.updated-dependencies-json }}'

          FAILED_DEPS=""
          for DEP in "${DEPS[@]}"; do
            DEP=$(echo "$DEP" | xargs)

            NEW_VERSION=$(echo "$UPDATED_DEPS_JSON" | jq -r --arg name "$DEP" '
              .[] | select(.dependencyName == $name) | .newVersion
            ' | head -n 1)

            if [ -z "$NEW_VERSION" ] || [ "$NEW_VERSION" = "null" ]; then
              echo "::error::Could not determine updated version for dependency $DEP from Dependabot metadata"
              FAILED_DEPS="${FAILED_DEPS:+$FAILED_DEPS, }$DEP"
              continue
            fi
            echo "::group::Checking provenance for $DEP@$NEW_VERSION"

            ATTESTATIONS=$(npm view "$DEP@$NEW_VERSION" --json 2>/dev/null | jq -r '.dist.attestations // empty')

            if [ -z "$ATTESTATIONS" ]; then
              echo "::error::No provenance attestation found for $DEP@$NEW_VERSION"
              FAILED_DEPS="${FAILED_DEPS:+$FAILED_DEPS, }$DEP"
            else
              echo "Provenance attestation found for $DEP@$NEW_VERSION"
            fi
            echo "::endgroup::"
          done

          if [ -n "$FAILED_DEPS" ]; then
            echo "failed=true" >> "$GITHUB_OUTPUT"
            echo "failed-deps=$FAILED_DEPS" >> "$GITHUB_OUTPUT"
          else
            echo "failed=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Close PR if provenance check failed
        if: steps.npm-provenance.outputs.failed == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          FAILED_DEPS: ${{ steps.npm-provenance.outputs.failed-deps }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
        run: |
          COMMENT_BODY_FILE="$(mktemp)"
          cat > "$COMMENT_BODY_FILE" <<EOF
          ## Provenance Attestation Check Failed
          
          This pull request has been automatically closed because the following dependencies do not have [provenance attestation](https://docs.npmjs.com/generating-provenance-statements):
          
          **Failed:** $FAILED_DEPS
          
          ### What is provenance attestation?
          
          Provenance attestation cryptographically proves that a package was built from a specific source repository using a specific build process. This helps protect against supply chain attacks by verifying the link between published packages and their source code.
          
          ### What to do
          
          - Check if a newer version of the dependency publishes provenance
          - Contact the package maintainer to request [npm provenance](https://docs.npmjs.com/generating-provenance-statements) support
          - If this dependency is trusted and an exception is warranted, a maintainer can reopen this PR and add the \`provenance-exception\` label
          EOF
          
          gh pr comment "$PR_NUMBER" --body-file "$COMMENT_BODY_FILE"
          gh pr close "$PR_NUMBER"
