diff --git a/.github/workflows/dependabot-provenance-check.yml b/.github/workflows/dependabot-provenance-check.yml new file mode 100644 index 000000000..2ac3288df --- /dev/null +++ b/.github/workflows/dependabot-provenance-check.yml @@ -0,0 +1,87 @@ +name: Dependabot Provenance Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: read + pull-requests: write + +jobs: + check-provenance: + if: >- + github.actor == 'dependabot[bot]' && + !contains(github.event.pull_request.labels.*.name, 'provenance-exception') + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Setup Node + if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn' + uses: actions/setup-node@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 }}" + NEW_VERSION="${{ steps.metadata.outputs.new-version }}" + + FAILED_DEPS="" + for DEP in "${DEPS[@]}"; do + DEP=$(echo "$DEP" | xargs) + 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: | + gh pr comment "$PR_NUMBER" --body "## 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" + + gh pr close "$PR_NUMBER"