From c72f366354ae48c40c4c2281021ec4165f64c434 Mon Sep 17 00:00:00 2001 From: davida-ps Date: Thu, 9 Apr 2026 10:30:20 +0300 Subject: [PATCH] fix(ci): harden nvd/scorecard dependency guardrails (#177) * fix(ci): harden nvd/scorecard dependency guardrails * fix(ci): upsert nvd advisory PRs and dedupe stale branches * fix(ci): paginate NVD PR lookup and expand scorecard triggers --- .github/workflows/poll-nvd-cves.yml | 162 +++++++++++++++++++++------- .github/workflows/scorecard.yml | 13 +++ package.json | 3 +- 3 files changed, 140 insertions(+), 38 deletions(-) diff --git a/.github/workflows/poll-nvd-cves.yml b/.github/workflows/poll-nvd-cves.yml index 90b5925..b80bad9 100644 --- a/.github/workflows/poll-nvd-cves.yml +++ b/.github/workflows/poll-nvd-cves.yml @@ -762,6 +762,24 @@ jobs: exit 1 fi + - name: Guard dependency manifests from NVD updates + if: steps.transform.outputs.new_count != '0' || steps.updates.outputs.update_count != '0' + run: | + set -euo pipefail + + BLOCKED_FILES=() + for file in package.json package-lock.json npm-shrinkwrap.json; do + if ! git diff --quiet -- "$file"; then + BLOCKED_FILES+=("$file") + fi + done + + if [ "${#BLOCKED_FILES[@]}" -gt 0 ]; then + echo "::error::NVD workflow must not modify dependency manifests: ${BLOCKED_FILES[*]}" + git --no-pager diff -- "${BLOCKED_FILES[@]}" || true + exit 1 + fi + - name: Sign advisory feed and verify if: steps.transform.outputs.new_count != '0' || steps.updates.outputs.update_count != '0' uses: ./.github/actions/sign-and-verify @@ -785,49 +803,119 @@ jobs: git checkout -- .github/ 2>/dev/null || true git clean -fd .github/ 2>/dev/null || true - - name: Create Pull Request + - name: Upsert NVD advisory PR if: steps.transform.outputs.new_count != '0' || steps.updates.outputs.update_count != '0' - id: create-pr - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: automated/nvd-cve-update-${{ github.run_id }} - delete-branch: true - title: "chore: CVE advisories - ${{ steps.transform.outputs.new_count }} new, ${{ steps.updates.outputs.update_count }} updated" - body: | - ## Summary - Automated update from NVD CVE feed. - - - **Mode:** ${{ inputs.force_full_scan == true && 'full-rebuild (ignore feed state)' || 'delta (incremental)' }} - - **New advisories:** ${{ steps.transform.outputs.new_count }} - - **Updated advisories:** ${{ steps.updates.outputs.update_count }} - - **Poll window:** ${{ steps.dates.outputs.start_date }} → ${{ steps.dates.outputs.end_date }} - - **Keywords:** ${{ env.KEYWORDS }} - - --- - *This PR was automatically generated by the NVD CVE polling workflow.* - commit-message: | - chore: CVE advisories - ${{ steps.transform.outputs.new_count }} new, ${{ steps.updates.outputs.update_count }} updated - - Automated update from NVD CVE feed. - Keywords: ${{ env.KEYWORDS }} - Poll window: ${{ steps.dates.outputs.start_date }} to ${{ steps.dates.outputs.end_date }} - add-paths: | - ${{ env.FEED_PATH }} - ${{ env.FEED_SIG_PATH }} - ${{ env.SKILL_FEED_PATH }} - ${{ env.SKILL_FEED_SIG_PATH }} - - - name: Run CodeQL on generated PR branch - if: steps.create-pr.outputs.pull-request-number != '' + id: upsert-pr env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - BRANCH="${{ steps.create-pr.outputs.pull-request-branch }}" + BRANCH_PREFIX="automated/nvd-cve-update" + PR_COMMENT="Superseded by newer automated NVD advisory update." + TITLE="chore: CVE advisories - ${{ steps.transform.outputs.new_count }} new, ${{ steps.updates.outputs.update_count }} updated" + COMMIT_SUBJECT="$TITLE" + COMMIT_BODY=$'Automated update from NVD CVE feed.\nKeywords: ${{ env.KEYWORDS }}\nPoll window: ${{ steps.dates.outputs.start_date }} to ${{ steps.dates.outputs.end_date }}' + + if [ "${{ inputs.force_full_scan }}" = "true" ]; then + MODE="full-rebuild (ignore feed state)" + else + MODE="delta (incremental)" + fi + + BODY_FILE="$(mktemp)" + cat > "$BODY_FILE" <> "$GITHUB_OUTPUT" + echo "pull-request-url=$TARGET_PR_URL" >> "$GITHUB_OUTPUT" + echo "pull-request-branch=$TARGET_BRANCH" >> "$GITHUB_OUTPUT" + + - name: Run CodeQL on generated PR branch + if: steps.upsert-pr.outputs.pull-request-number != '' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + BRANCH="${{ steps.upsert-pr.outputs.pull-request-branch }}" if [ -z "$BRANCH" ]; then - echo "::error::Missing pull-request-branch output from create-pull-request" + echo "::error::Missing pull-request-branch output from upsert-pr step" exit 1 fi @@ -891,7 +979,7 @@ jobs: if [ "${{ steps.transform.outputs.new_count }}" != "0" ] || [ "${{ steps.updates.outputs.update_count }}" != "0" ]; then echo "" >> $GITHUB_STEP_SUMMARY - echo "🔀 Created PR: ${{ steps.create-pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY + echo "🔀 Upserted PR: ${{ steps.upsert-pr.outputs.pull-request-url }}" >> $GITHUB_STEP_SUMMARY else echo "" >> $GITHUB_STEP_SUMMARY echo "✅ No new or updated CVEs found." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 95bc4ec..6fbc135 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -7,10 +7,23 @@ on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: + # Run immediately after dependency changes on main so vulnerability alerts close quickly. + push: + branches: [main] + paths: + - package.json + - package-lock.json + - npm-shrinkwrap.json + - requirements*.txt + - .github/requirements*.txt + - .github/requirements-lint-python.txt + - .github/workflows/scorecard.yml # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '19 23 * * 0' + # Allow maintainers to rescan main on demand after hotfixes. + workflow_dispatch: # Declare default permissions as read only. permissions: read-all diff --git a/package.json b/package.json index 510e36c..487e6c7 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "ajv": "6.14.0", "balanced-match": "4.0.3", "brace-expansion": "5.0.5", - "minimatch": "10.2.4" + "minimatch": "10.2.4", + "picomatch": "4.0.4" } }