mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
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
This commit is contained in:
@@ -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" <<EOF
|
||||
## Summary
|
||||
Automated update from NVD CVE feed.
|
||||
|
||||
- **Mode:** ${MODE}
|
||||
- **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.*
|
||||
EOF
|
||||
|
||||
PR_LIST_JSON="$(
|
||||
gh api --paginate "repos/${{ github.repository }}/pulls?state=open&base=main&per_page=100" \
|
||||
--jq '.[] | {number, headRefName: .head.ref, url: .html_url, updatedAt: .updated_at}' \
|
||||
| jq -s '.'
|
||||
)"
|
||||
|
||||
mapfile -t MATCHING_OPEN_PRS < <(
|
||||
echo "$PR_LIST_JSON" | jq -r --arg prefix "$BRANCH_PREFIX" '
|
||||
map(select(.headRefName | startswith($prefix)))
|
||||
| sort_by(.updatedAt)
|
||||
| reverse
|
||||
| .[]
|
||||
| @base64
|
||||
'
|
||||
)
|
||||
|
||||
TARGET_BRANCH="$BRANCH_PREFIX"
|
||||
TARGET_PR_NUMBER=""
|
||||
TARGET_PR_URL=""
|
||||
|
||||
if [ "${#MATCHING_OPEN_PRS[@]}" -gt 0 ]; then
|
||||
PRIMARY_JSON="$(echo "${MATCHING_OPEN_PRS[0]}" | base64 --decode)"
|
||||
TARGET_BRANCH="$(echo "$PRIMARY_JSON" | jq -r '.headRefName')"
|
||||
TARGET_PR_NUMBER="$(echo "$PRIMARY_JSON" | jq -r '.number')"
|
||||
TARGET_PR_URL="$(echo "$PRIMARY_JSON" | jq -r '.url')"
|
||||
|
||||
if [ "${#MATCHING_OPEN_PRS[@]}" -gt 1 ]; then
|
||||
echo "Found multiple open NVD advisory PRs. Closing duplicates."
|
||||
for encoded_pr in "${MATCHING_OPEN_PRS[@]:1}"; do
|
||||
pr_json="$(echo "$encoded_pr" | base64 --decode)"
|
||||
pr_number="$(echo "$pr_json" | jq -r '.number')"
|
||||
gh pr close "$pr_number" --delete-branch --comment "$PR_COMMENT"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Using target branch: $TARGET_BRANCH"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git fetch origin main
|
||||
git checkout -B "$TARGET_BRANCH" origin/main
|
||||
|
||||
git add "$FEED_PATH" "$FEED_SIG_PATH" "$SKILL_FEED_PATH" "$SKILL_FEED_SIG_PATH"
|
||||
if git diff --cached --quiet; then
|
||||
echo "::error::Expected advisory feed changes but none were staged."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git commit -m "$COMMIT_SUBJECT" -m "$COMMIT_BODY"
|
||||
git push --force origin "$TARGET_BRANCH"
|
||||
|
||||
if [ -n "$TARGET_PR_NUMBER" ]; then
|
||||
gh pr edit "$TARGET_PR_NUMBER" --title "$TITLE" --body-file "$BODY_FILE"
|
||||
else
|
||||
TARGET_PR_URL="$(gh pr create --base main --head "$TARGET_BRANCH" --title "$TITLE" --body-file "$BODY_FILE")"
|
||||
TARGET_PR_NUMBER="$(basename "$TARGET_PR_URL")"
|
||||
fi
|
||||
|
||||
if [ -z "$TARGET_PR_URL" ]; then
|
||||
TARGET_PR_URL="$(gh pr view "$TARGET_PR_NUMBER" --json url --jq '.url')"
|
||||
fi
|
||||
|
||||
echo "pull-request-number=$TARGET_PR_NUMBER" >> "$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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user