ci: Node 24 action upgrades, SHA-pinning, security hardening & workflow consolidation - #1218
Conversation
Ahead of GitHub's 2026-06-02 forced Node20->Node24 action migration (and Node20 removal from runners in fall 2026), upgrade every action to a Node24 release and pin to full commit SHAs. Actions (all SHA-pinned, Node24-verified): - actions/checkout v4 -> v6.0.2 - actions/setup-node v4 -> v6.4.0 - oven-sh/setup-bun v1 -> v2.2.0 (bun-version pinned 1.3.14, was 'latest') - actions/attest-build-provenance v2 -> v4.1.0 - actions/github-script v6 -> v9.0.0 - jwalton/gh-find-current-pr v1 -> v1.3.5 - rtCamp/action-slack-notify v2 -> v2.3.3 Build toolchain Node 20 -> 24 (Node 20 reached EOL 2026-04-30; @types/node is already 24). Dropped the single-value node matrix. Security hardening: - Replace Ilshidur/action-discord@master (moving-branch = supply-chain RCE, DISCORD_WEBHOOK is live) with a plain curl webhook call (jq-encoded). - Least-privilege permissions: top-level contents:read everywhere; release job keeps its annotated write scopes; notify job pinned to contents:read. - Remove dead NPM_TOKEN from the Release step (@semantic-release/npm has npmPublish:false; the plugin is kept because it drives the version bump). - Env-bind github-script inputs and use `node -p` for version extraction to remove script-injection surface. - harden-runner (egress audit) on release + CI build/test jobs. Modernization: - Consolidate test.yml + build-with-lint.yml into ci.yml (push/PR to master, paths-ignore docs/md, cancel-in-progress concurrency, persist-credentials false, --frozen-lockfile) + dependency-review on PRs. - Add CodeQL code scanning (codeql.yml). - Add Dependabot for github-actions + bun (root + docs) so actions never silently drift again. - Serialized concurrency on release.yml; drop the no-op `build --if-present`. Local validation: `bun install --frozen-lockfile` clean, `bun run build` OK, 1083 tests pass.
📝 WalkthroughWalkthroughThis PR refactors GitHub Actions infrastructure: adding Dependabot configuration for automated dependency updates, consolidating test and build-lint workflows into a unified CI pipeline, introducing CodeQL security scanning, and modernizing the release workflow with pinned actions, improved concurrency control, and updated notification logic for Slack and Discord. ChangesCI & Release Infrastructure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
Deploying quickadd with
|
| Latest commit: |
0278df4
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://cb3c0147.quickadd.pages.dev |
| Branch Preview URL: | https://ci-node24-actions-upgrade-ha.quickadd.pages.dev |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/release.yml (1)
108-137:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix
RELEASE_EXIT_CODEcapture under defaulterrexitin the release step.GitHub Actions’ default Linux/macOS bash for
run:isbash -e(errexit). Withbunx semantic-releaseinside theif/elif/elsebodies, a non-zero exit aborts the step beforeRELEASE_EXIT_CODE=$?and thereleased=falsewrite run. Temporarily disable errexit around the release call so the exit code and output logic execute.♻️ Capture exit code with errexit disabled (also quotes the user-supplied inputs)
EXTRA_ARGS="" if [ "$DRY_RUN" == "true" ]; then echo "Running in dry-run mode" EXTRA_ARGS="--dry-run" fi + # Disable errexit so a non-zero semantic-release exit is captured + # instead of aborting the step before the status logic runs. + set +e if [ -n "$PRE_RELEASE" ]; then echo "Running pre-release: $PRE_RELEASE" - bunx semantic-release --prerelease $PRE_RELEASE $EXTRA_ARGS - RELEASE_EXIT_CODE=$? + bunx semantic-release --prerelease "$PRE_RELEASE" $EXTRA_ARGS elif [ "$RELEASE_TYPE" == "auto" ]; then echo "Running auto release" bunx semantic-release $EXTRA_ARGS - RELEASE_EXIT_CODE=$? else echo "Running release with type: $RELEASE_TYPE" - bunx semantic-release --release-as $RELEASE_TYPE $EXTRA_ARGS - RELEASE_EXIT_CODE=$? + bunx semantic-release --release-as "$RELEASE_TYPE" $EXTRA_ARGS fi + RELEASE_EXIT_CODE=$? + set -e # Set release status based on exit code if [ "$DRY_RUN" != "true" ] && [ $RELEASE_EXIT_CODE -eq 0 ]; then echo "released=true" >> $GITHUB_OUTPUT else echo "released=false" >> $GITHUB_OUTPUT fi exit $RELEASE_EXIT_CODE🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 108 - 137, The release step currently suffers from bash "errexit" aborting before RELEASE_EXIT_CODE is captured when running bunx semantic-release; wrap each bunx invocation so errexit is temporarily disabled (e.g., using set +e before the bunx semantic-release calls and set -e after) and then immediately capture the exit code into RELEASE_EXIT_CODE; also ensure user-provided variables PRE_RELEASE, RELEASE_TYPE and EXTRA_ARGS are safely quoted when passed to bunx so they are treated as single arguments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 108-137: The release step currently suffers from bash "errexit"
aborting before RELEASE_EXIT_CODE is captured when running bunx
semantic-release; wrap each bunx invocation so errexit is temporarily disabled
(e.g., using set +e before the bunx semantic-release calls and set -e after) and
then immediately capture the exit code into RELEASE_EXIT_CODE; also ensure
user-provided variables PRE_RELEASE, RELEASE_TYPE and EXTRA_ARGS are safely
quoted when passed to bunx so they are treated as single arguments.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7e1ab51f-091a-4ef7-839e-5ba5e33ee15d
📒 Files selected for processing (6)
.github/dependabot.yml.github/workflows/build-with-lint.yml.github/workflows/ci.yml.github/workflows/codeql.yml.github/workflows/release.yml.github/workflows/test.yml
💤 Files with no reviewable changes (2)
- .github/workflows/build-with-lint.yml
- .github/workflows/test.yml
|
🎉 This PR is included in version 2.13.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Why
The last Release run warned that GitHub forces Node 20 actions onto the Node 24 runtime on 2026-06-02 and removes Node 20 from runners in fall 2026 (any action still declaring
runs.using: node20then fails to start, taking down Release/CI). Separately, Node 20 itself reached EOL on 2026-04-30, so the build toolchain was on an unsupported runtime.This PR upgrades every action to a Node 24 release pinned to a full commit SHA, hardens permissions and the supply chain, and consolidates the CI workflows. Every version/SHA below was pulled from the GitHub API and independently re-verified.
Action upgrades (all SHA-pinned, Node 24 confirmed)
@v4@de0fac2…# v6.0.2@v4@48b55a0…# v6.4.0@v1(latest)@0c5077e…# v2.2.0(1.3.14)@v2@a2bbfa2…# v4.1.0@v6@3a2844b…# v9.0.0@v1@f3d61b4…# v1.3.5@v2@e31e87e…# v2.3.3@7211b7c…# v4@a1d282b…# v5.0.0@ab7a940…# v2.19.4Security hardening
Ilshidur/action-discord@master— a moving branch is a supply-chain RCE vector (tj-actions/changed-files class), andDISCORD_WEBHOOKis set, so it ran live on every release. Replaced with a plaincurlwebhook call (message JSON-encoded withjq).GITHUB_TOKEN: top-levelpermissions: contents: readon every workflow; the release job keeps its (now annotated) write scopes; the notify job is pinned tocontents: read.NPM_TOKENfrom the Release step (@semantic-release/npmhasnpmPublish:false). The plugin itself is kept — it drives theversionlifecycle hook that runsversion-bump.mjsto updatemanifest.json/versions.json.github-scriptinputs are now passed viaenv:and read withprocess.env; version extraction usesnode -pinstead ofnode -e … && source.audit) on the release + CI build/test jobs.persist-credentials: falseon CI checkouts; release checkout keepsfetch-depth: 0+ token (semantic-release needs both).Modernization
test.yml+build-with-lint.yml→ci.yml(push/PR tomaster,paths-ignoredocs/markdown,cancel-in-progressconcurrency,--frozen-lockfile) + a dependency-review job on PRs.codeql.yml,security-extended, weekly cron).github-actions+bun(root) +bun(docs) — the root-cause fix so actions never silently drift again.release.yml; dropped the no-opbun run build --if-present.Repo settings applied out-of-band (via API)
master)master). After merge: Settings → Actions → General → "Require actions to be pinned to a full-length commit SHA".Rollout / test plan
release.ymlon Node 24 without publishing (it's dispatchable on a branch because the workflow already exists on the default branch):Behavior changes to be aware of
pushnow triggers only onmaster(feature branches rely on the PR trigger) — removes duplicate runs; opening a PR still runs full CI.--frozen-lockfile(matches release.yml; fails loudly on lockfile drift). Verified the currentbun.lockis in sync.bunupdater has a known quirk of stripping the lockfileconfigVersionline on some Bun versions; if that creates--frozen-lockfilefriction, thebunecosystem entries can be dropped without affecting the criticalgithub-actionsupdater.Out of scope (flagged for a follow-up)
release.ymlpasses--release-asand--prereleaseto semantic-release, neither of which is a valid flag in v24 (it derives the version from commits) — so thereleaseType/preReleasedispatch inputs are inert and the "pre-release" path would cut a normal release. Left unchanged here to keep this PR scoped to the Node 24 / hardening work; worth fixing or removing the inputs separately.Validation
bun install --frozen-lockfileclean ·bun run buildOK · 1083 tests pass.Summary by CodeRabbit