diff --git a/.github/workflows/dependabot-critical-alerts.yml b/.github/workflows/dependabot-critical-alerts.yml deleted file mode 100644 index 62d68c3002..0000000000 --- a/.github/workflows/dependabot-critical-alerts.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Dependabot Critical Alerts - -on: - schedule: - - cron: "0 8 * * *" # Daily 08:00 UTC - workflow_dispatch: - inputs: - severity: - description: "Severity to alert on" - type: choice - options: - - critical - - high - - medium - - low - default: critical - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - alert: - name: Post critical alerts - # Set the ENABLE_DEPENDABOT_ALERTS repository variable to 'false' to turn off - # the Dependabot alert/summary notifiers — e.g. forks/mirrors that lack the - # DEPENDABOT_ALERTS_TOKEN / SLACK_BOT_TOKEN secrets. Defaults to enabled. - if: ${{ vars.ENABLE_DEPENDABOT_ALERTS != 'false' }} - runs-on: warp-ubuntu-latest-x64-2x - environment: dependabot-summary - env: - SEVERITY: ${{ inputs.severity || 'critical' }} - steps: - - name: Fetch alerts - id: alerts - env: - GH_TOKEN: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} - REPO: ${{ github.repository }} - run: | - set -euo pipefail - gh api -X GET "/repos/$REPO/dependabot/alerts" \ - -F state=open -F severity="$SEVERITY" --paginate > pages.json - jq -s 'add' pages.json > alerts.json - TOTAL=$(jq 'length' alerts.json) - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - if [ "$TOTAL" = "0" ]; then - exit 0 - fi - LIST=$(jq -r ' - map("• <\(.html_url)|#\(.number)> *\(.dependency.package.name)* - \(.security_advisory.summary)") - | join("\n") - ' alerts.json) - { - echo "list<> "$GITHUB_OUTPUT" - - - name: Build Slack payload - if: steps.alerts.outputs.total != '0' - env: - REPO: ${{ github.repository }} - CHANNEL: ${{ vars.SLACK_CHANNEL_ID }} - TOTAL: ${{ steps.alerts.outputs.total }} - LIST: ${{ steps.alerts.outputs.list }} - run: | - jq -n \ - --arg channel "$CHANNEL" \ - --arg repo "$REPO" \ - --arg total "$TOTAL" \ - --arg list "$LIST" \ - --arg severity "$SEVERITY" \ - '{ - channel: $channel, - text: ":bufo-alarma: `\($repo)` - *\($total) open \($severity) alert(s)*\n\($list)\n\n" - }' > payload.json - - - name: Post Slack alert - if: steps.alerts.outputs.total != '0' - uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_BOT_TOKEN }} - payload-file-path: payload.json diff --git a/.github/workflows/dependabot-weekly-summary.yml b/.github/workflows/dependabot-weekly-summary.yml deleted file mode 100644 index fc9eaebcb9..0000000000 --- a/.github/workflows/dependabot-weekly-summary.yml +++ /dev/null @@ -1,210 +0,0 @@ -name: Dependabot Weekly Summary - -on: - schedule: - - cron: "0 8 * * 1" # Mon 08:00 UTC - workflow_dispatch: - -# Single-purpose monitoring workflow; serialise on workflow name only - we never -# want two concurrent summary runs racing to post the same digest. -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - -permissions: - contents: read # gh CLI baseline - pull-requests: read # gh pr list (open dependabot PRs) - actions: read # gh run list / view (parse latest dependabot run logs) - -jobs: - summary: - name: Post weekly Dependabot summary - # Set the ENABLE_DEPENDABOT_ALERTS repository variable to 'false' to turn off - # the Dependabot alert/summary notifiers — e.g. forks/mirrors that lack the - # DEPENDABOT_ALERTS_TOKEN / SLACK_BOT_TOKEN secrets. Defaults to enabled. - if: ${{ vars.ENABLE_DEPENDABOT_ALERTS != 'false' }} - runs-on: warp-ubuntu-latest-x64-2x - environment: dependabot-summary - env: - # Severities surface in the actions list when their remaining TTR drops - # below this many days. Override via repo/env var ACTION_THRESHOLD_DAYS. - THRESHOLD_DAYS: ${{ vars.ACTION_THRESHOLD_DAYS || '7' }} - steps: - - name: Fetch alerts and compute summaries - id: alerts - env: - GH_TOKEN: ${{ secrets.DEPENDABOT_ALERTS_TOKEN }} - REPO: ${{ github.repository }} - run: | - if ! gh api -X GET "/repos/$REPO/dependabot/alerts" --paginate > pages.json 2> err.txt; then - echo "total=?" >> "$GITHUB_OUTPUT" - ERR=$(head -c 200 err.txt | tr '\n' ' ') - echo "by_severity=:x: _failed to fetch alerts: ${ERR}_" >> "$GITHUB_OUTPUT" - echo "actions=:x: _alerts unavailable_" >> "$GITHUB_OUTPUT" - exit 0 - fi - jq -s '[.[][] | select(.state == "open")]' pages.json > open.json - - TOTAL=$(jq 'length' open.json) - echo "total=$TOTAL" >> "$GITHUB_OUTPUT" - - if [ "$TOTAL" = "0" ]; then - echo "by_severity=:white_check_mark: No open alerts." >> "$GITHUB_OUTPUT" - echo "actions=_None_" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Severity breakdown - real newlines so jq --arg in the payload - # builder encodes them as proper \n in JSON (Slack renders as breaks). - BY_SEV=$(jq -r ' - group_by(.security_advisory.severity) - | map({sev: .[0].security_advisory.severity, - count: length, - weight: ({"critical":0,"high":1,"medium":2,"low":3}[.[0].security_advisory.severity])}) - | sort_by(.weight) - | map("• *\(.count)* \(.sev)") - | join("\n") - ' open.json) - { - echo "by_severity<> "$GITHUB_OUTPUT" - - # Actions: alerts within THRESHOLD_DAYS of their TTR (P0=7d, P1=30d, P2=90d, P3=no deadline) - # Grouped by (package, severity); shows earliest deadline per group. - ACTIONS=$(jq -r --argjson threshold "$THRESHOLD_DAYS" ' - [.[] - | (.security_advisory.severity) as $sev - | ({"critical":7,"high":30,"medium":90,"low":null}[$sev]) as $ttr - | select($ttr != null) - | ((now - (.created_at | fromdateiso8601)) / 86400 | floor) as $age - | {pkg: .dependency.package.name, sev: $sev, remaining: ($ttr - $age)} - ] - | group_by([.pkg, .sev]) - | map({pkg: .[0].pkg, sev: .[0].sev, count: length, min_remaining: ([.[].remaining] | min)}) - | map(select(.min_remaining < $threshold)) - | sort_by(.min_remaining) - | if length == 0 then "_None_" - else (map( - "• *\(.pkg)* (\(.sev))" + - (if .count > 1 then " ×\(.count)" else "" end) + " - " + - (if .min_remaining < 0 then "*OVERDUE* by \(-.min_remaining)d" - else "\(.min_remaining)d remaining" end) - ) | join("\n")) - end - ' open.json) - { - echo "actions<> "$GITHUB_OUTPUT" - - - name: Fetch open dependabot PRs - id: prs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - REPO_URL: https://github.com/${{ github.repository }} - run: | - if ! PR_JSON=$(gh pr list --repo "$REPO" --state open --author "app/dependabot" --json number,title 2> err.txt); then - ERR=$(head -c 200 err.txt | tr '\n' ' ') - echo "list=:x: _failed to fetch PRs: ${ERR}_" >> "$GITHUB_OUTPUT" - exit 0 - fi - LIST=$(echo "$PR_JSON" | jq -r --arg url "$REPO_URL" ' - if length == 0 then "_None_" - else (map("• <\($url)/pull/\(.number)|#\(.number)> \(.title)") | join("\n")) - end - ') - { - echo "list<> "$GITHUB_OUTPUT" - - - name: Find latest npm dependabot run - id: latest - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - run: | - # Repos without a dependabot.yml have no "Dependabot Updates" workflow; - # treat the lookup failure as "no recent run found" rather than failing. - if ! RUN_ID=$(gh run list --repo "$REPO" --workflow "Dependabot Updates" --status success --limit 30 --json databaseId,name --jq 'first(.[] | select(.name | startswith("npm_and_yarn")) | .databaseId) // empty' 2>/dev/null); then - RUN_ID="" - fi - echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT" - - - name: Extract stuck deps (only if actions pending) - id: stuck - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REPO: ${{ github.repository }} - RUN_ID: ${{ steps.latest.outputs.run_id }} - ACTIONS: ${{ steps.alerts.outputs.actions }} - run: | - # Skip the stuck section entirely when nothing in the actions list - # - keeps the digest tidy when there's nothing to actually act on. - if [ "$ACTIONS" = "_None_" ]; then - echo "section=" >> "$GITHUB_OUTPUT" - exit 0 - fi - HEADER=$'\n\n*Couldn\'t auto-fix (need manual `pnpm.overrides`):*\n' - if [ -z "$RUN_ID" ]; then - { - echo "section<> "$GITHUB_OUTPUT" - exit 0 - fi - gh run view "$RUN_ID" --repo "$REPO" --log > log.txt 2>&1 || true - STUCK=$(grep -oE "No update possible for [^[:space:]]+ [0-9][^[:space:]]*" log.txt | sed 's/No update possible for //' | sort -u || true) - if [ -z "$STUCK" ]; then - { - echo "section<> "$GITHUB_OUTPUT" - exit 0 - fi - LIST=$(echo "$STUCK" | awk 'NR>1{printf "\n"} {printf "• *%s* %s", $1, $2}') - { - echo "section<> "$GITHUB_OUTPUT" - - - name: Build Slack payload - env: - REPO: ${{ github.repository }} - CHANNEL: ${{ vars.SLACK_CHANNEL_ID }} - TOTAL: ${{ steps.alerts.outputs.total }} - BY_SEVERITY: ${{ steps.alerts.outputs.by_severity }} - PRS_LIST: ${{ steps.prs.outputs.list }} - ACTIONS: ${{ steps.alerts.outputs.actions }} - STUCK: ${{ steps.stuck.outputs.section }} - run: | - # Build payload via jq so PR titles or error strings containing - # quotes/backslashes/newlines can't break the JSON. - jq -n \ - --arg channel "$CHANNEL" \ - --arg repo "$REPO" \ - --arg total "$TOTAL" \ - --arg by_severity "$BY_SEVERITY" \ - --arg prs_list "$PRS_LIST" \ - --arg actions "$ACTIONS" \ - --arg stuck "$STUCK" \ - --arg threshold "$THRESHOLD_DAYS" \ - '{ - channel: $channel, - text: ":calendar: *Weekly Dependabot summary* - `\($repo)`\n\n*Open alerts (\($total)):*\n\($by_severity)\n\n*Open Dependabot PRs:*\n\($prs_list)\n\n*Actions needed (<\($threshold)d remaining):*\n\($actions)\($stuck)\n\n" - }' > payload.json - - - name: Post Slack summary - uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_BOT_TOKEN }} - payload-file-path: payload.json