diff --git a/.github/workflows/dependabot-provenance-check.yml b/.github/workflows/dependabot-provenance-check.yml new file mode 100644 index 000000000..a3fdb503a --- /dev/null +++ b/.github/workflows/dependabot-provenance-check.yml @@ -0,0 +1,102 @@ +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@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 }}" + 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(.dependency-name == $name) | .new-version + ' | 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" <