Skip to content

ci: pin every action to a Node 24 major, by commit SHA - #144

Open
Kartikey1306 wants to merge 7 commits into
embeddedos-org:masterfrom
Kartikey1306:ci/actions-node24
Open

Kartikey1306 wants to merge 7 commits into
embeddedos-org:masterfrom
Kartikey1306:ci/actions-node24

Conversation

@Kartikey1306

Copy link
Copy Markdown
Contributor

Stacked on #115 (origin/master does not pass its own checks without it, and this PR should not wear those reds). Review e152d8e..1e33831: one commit, 8 files, 26 uses: lines, nothing else. Land #115 first; this then rebases to its single commit.

Closes #143

Why now, precisely

Every job in this repository logs, on every run: "Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4, actions/setup-python@v5, …" — 21 such warnings on a single ebuild run (35207728484).

GitHub's changelog (2025-09-19, editor's note of 2026-08-25), quoted: "Beginning on June 16th, 2026, runners will begin using Node24 by default"; the opt-out ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true "will only work until we upgrade the runner and remove Node20 on September 23rd, 2026." What the changelog does not say is whether a node20-declared action errors after the 23rd — today ours run on the forced Node 24 and pass. This PR does not claim they will break; it removes the dependence on a fallback GitHub calls temporary, six days before it ends.

What changed

26 uses: references in 8 files, each moved from a major whose action.yml declares node20 to the current major that declares node24 — pinned by commit SHA with the release tag in a comment (OpenSSF Pinned-Dependencies, the same form scorecard.yml already uses). Each SHA was resolved from the exact release tag through the GitHub API and confirmed identical to what the floating major tag points at:

action pinned to tag runs.using
actions/checkout 3d3c42e v7.0.1 node24
actions/configure-pages 45bfe01 v6.0.0 node24
actions/deploy-pages 368f825 v5.0.1 node24
actions/github-script 3a2844b v9.0.0 node24
actions/setup-python 5fda3b9 v7.0.0 node24
actions/upload-artifact 043fb46 v7.0.1 node24
actions/upload-pages-artifact fc324d3 v5.0.0 composite
github/codeql-action b96794f v4.38.0 composite
softprops/action-gh-release efb3536 v3.0.3 node24

(upload-pages-artifact and codeql-action/codecov are composites; the first now wraps a SHA-pinned Node-24 upload-artifact, the others run Node-24 tooling.)

Breaking-change audit, per action, against this repository's actual usages

Read from each action's release notes at every major boundary crossed, then checked against every with: block here:

  • download-artifact v4→v8 — v5 changed the extracted path for single artifacts downloaded by ID; every use here is by name: or by pattern: + merge-multiple: true, the cases the notes mark "no action needed". v8 defaults digest-mismatch to error (a corrupted download now fails instead of warning) — wanted.
  • github-script v7→v9 — v9 breaks require('@actions/github'); the only script here uses the injected github/context objects and no require.
  • codecov-action v4→v7 — v5 replaced the uploader with the CLI wrapper and deprecated filefiles; usage here is files: + flags:, both still accepted.
  • codeql-action v3→v4 — minimum bundle 2.19.4, met on hosted runners; init's languages/config-file inputs unchanged. The check name comes from this workflow's name: keys, not the action, so the required-check registry is unaffected — confirmed on this PR's run.
  • configure-pages v4→v6 — v5's breaking changes concern Next.js static-export configuration; usage here passes no inputs.
  • checkout v4→v7, setup-python v5→v7, upload-artifact v4→v7, cache v4→v6, deploy-pages v4→v5, action-gh-release v2→v3 — runtime-only majors; require runner ≥ 2.327.1, which every hosted image here (*-latest, ubuntu-22.04) meets. No self-hosted runner exists in this org (grepped all three repos).

Verification

Every file parsed before and after; the parsed structure asserted equal apart from uses: valueson: triggers, job names, job-level keys, step count and every non-uses step key identical; action names unchanged; line endings unchanged. Not yaml.safe_load succeeding — the assertion.

pytest 82 passed.

Which files this PR's own run exercises, and which it cannot:

file uses: bumped triggers evidence
auto-assign.yml 1 pull_request runs on this PR
codeql.yml 3 push, pull_request, schedule runs on this PR
deploy-pages.yml 4 push, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next push run after merge
nightly.yml 5 schedule, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next schedule run after merge
simulation-test.yml 5 push, pull_request, schedule, workflow_dispatch runs on this PR
sync-release-branch.yml 1 push NOT RUN here — no pull_request trigger; first exercised by its own next push run after merge
video-build.yml 4 push, release, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next push/release run after merge
weekly.yml 3 schedule, workflow_dispatch NOT RUN here — no pull_request trigger; first exercised by its own next schedule run after merge

The NOT RUN rows are stated rather than implied: their first execution with these pins is their next scheduled/release/dispatch run after merge, and a failure there would be attributable to this change.

Deliberately not touched — 6 files, to keep the queue mergeable

These also carry Node-20 pins but are modified by open pull requests; bumping them here would put every one of those PRs into CONFLICTING. They follow in one commit once the PRs below land:

file open PRs modifying it
book-build.yml #133, #135
build.yml #122, #142
ci.yml #115, #116, #122, #123, #125, #127, #129, #131, #133, #135, #138, #139, #142
eosim-sanity.yml #115, #116, #122, #123, #125, #127, #129, #131, #133, #135, #138, #139, #142
release.yml #122, #142
scorecard.yml #115, #116, #122, #123, #125, #127, #129, #131, #133, #135, #138, #139, #142

Twenty PRs were merged into master in ninety minutes on 09-07, each on the
base it was written against. Master has not configured, compiled, linked or
passed its tests since. Every nightly and every eos simulation run (which
builds eBoot master) has been red for the same reason.

Configure:
- tests/CMakeLists.txt registered eboot_test_fdt_loader twice. embeddedos-org#84 added it,
  and embeddedos-org#85 -- stacked on embeddedos-org#84 -- re-added it at a different anchor when it was
  replayed onto a master that already had embeddedos-org#84. Same hunk as embeddedos-org#111.

Compile (eboot_core):
- embeddedos-org#55 restored a core/sha512.c from 02b7dac that keeps its byte count in
  ctx->count; the eos_sha512_ctx_t master ships has bitlen[2]/buffer_len
  (embeddedos-org#69/embeddedos-org#93). Back to the pre-embeddedos-org#55 file, blob-identical (d9aa57c).
- embeddedos-org#55's source-list "correction" dropped core/boot_log.c, core/secure_boot.c
  and core/fdt_loader.c, which embeddedos-org#72/embeddedos-org#84 had added after embeddedos-org#55 was written.
  CMakeLists.txt is blob-identical to pre-embeddedos-org#55 again (f8fe6eb).
- embeddedos-org#55 replaced the eos_boot_log_get_head() declaration with a second copy of
  eos_boot_log_read(); embeddedos-org#91 had already fixed the prototype it meant to fix.
  Header restored (964ebb8).
- embeddedos-org#94 and embeddedos-org#105 each repaired the Ed25519 verifier and each added an identical
  static scalarbase(); both merged. One copy removed.
- The same pair each added k_low_order[]/messages[] to test_ed25519.c. The
  embeddedos-org#105 copy is removed; embeddedos-org#94's stays because it also carries k_non_canonical[].

Tests that stopped passing because two merged PRs disagree on behaviour:
- embeddedos-org#104 verifies the image signature at install unconditionally, before the
  anti-rollback check embeddedos-org#103 added, so embeddedos-org#103's unsigned images are refused as
  EOS_ERR_SIGNATURE before they can be refused as EOS_ERR_ANTI_ROLLBACK, and
  test_fw_transport's XMODEM install can no longer finalize. Both suites now
  stream genuinely signed images. eBoot has no Ed25519 signer in C, so
  tools/gen_fw_update_test_sigs.py signs the exact header prefixes those
  suites build under the RFC 8032 section 7.1 TEST 1 key and emits
  tests/vectors/fw_update_test_sigs.h; the suites serve that key from a
  simulated OTP slot 0. Negative control: one flipped signature byte fails
  test_write_streams_tlv_then_finalize_rejects_below_floor with
  EOS_ERR_SIGNATURE.
- embeddedos-org#103's step 5b reads the TLV counter through the HAL slot containing the
  image; test_secure_boot_policy (embeddedos-org#82) declared no slots, so eos_secure_boot()
  returned EOS_SBOOT_ERR_BAD_HEADER two steps before the one under test. The
  fixture now places its image in slot A.

Guards from embeddedos-org#95 that later merges walked back, never run until now because the
C configure step failed first:
- embeddedos-org#103 replayed the hand-written Valgrind foreach over the derived one.
  Restored foreach(TEST_NAME ${EBLDR_UNIT_TESTS}); eleven registered suites
  had no list(APPEND ...) and so no Valgrind run.
- Seven suites assign tests_run = <literal> and their TEST() does not count;
  four suites have no TEST() macro at all. Counted, and classified.
- embeddedos-org#101 added fuzz-build after embeddedos-org#90's gate; the gate did not wait for it.

CI plumbing:
- eosim-sanity.yml: the install-validate job is written in bash but ran under
  PowerShell on the Windows legs (no shell:), where SITE_PACKAGES=$(...) is an
  unknown command and `|| { exit 1 }` is an unexecuted script block.
- scorecard.yml: ossf/scorecard-action@v2.4.0 pulls gcr.io, which now demands
  GCP billing. v2.4.3 pulls ghcr.io; eos already pins it and is green.

Verified locally (macOS, clang): Release build clean, 31/31 ctest; the same
under -DEBLDR_SANITIZE=ON (ASan+UBSan); 78/78 pytest.

Not fixed here, reported separately: core/keystore.c's compiled-in
default_dev_key is described as the RFC 8032 TEST 1 public key but differs
from byte 21 on and is not a point on the curve, so nothing can verify
against it on any board without OTP. With embeddedos-org#104 that makes firmware update
refuse every image on such boards.
… -1.0.0 as an option

test_out_of_range_version_is_rejected[-1.0.0] passed locally (Python 3.14)
and failed in CI (ubuntu-22.04, Python 3.10) with argparse's own
"expected one argument": the older negative-number matcher does not accept
-1.0.0, so the token was read as an unknown option and imgpack.py's range
check -- the thing under test -- never ran. The joined form is unambiguous
on every interpreter and the test now reaches the tool's message.
tests/vectors/fw_update_test_sigs.h is the output of
tools/gen_fw_update_test_sigs.py, committed because eBoot has no Ed25519
signer in C. Nothing checked that the two agree: a generator edit without a
regeneration leaves test_fw_update and test_fw_transport verifying against
stale signatures, failing with EOS_ERR_SIGNATURE and nothing to say why.

tests/unit/test_fw_update_test_sigs.py runs the generator with the test's
own interpreter and compares its stdout to the committed header byte for
byte, so a line-ending change counts too. It follows the same dependency
rule as test_eos_sign_payload_offset.py: with EOS_REQUIRE_SIGNING_TESTS set
(the CI workflow sets it before the pytest step) a missing cryptography
module fails the job instead of skipping. Negative control: one flipped hex
byte in the header fails the test with a unified diff naming the line.

build_image() and build_container() now carry a comment naming the
coupling: the signed prefix is assembled both there and in the generator,
and changing any field in it means changing the generator's copy and
regenerating the header.
…epair

embeddedos-org#103 and embeddedos-org#104 were both merged and disagree on whether the anti-rollback
counter or the signature is checked first in eos_fw_update_finalize(). The
master design orders boot as verify image, then version policy (section
8.1), but its update flow (section 15) never places the anti-rollback
check, so the order the install path uses existed only in a PR body.
ADR-020 records it: the signature over the signed header prefix is
verified first, the TLV counter is read only after the prefix that binds
it is authenticated, and an image that fails verification is refused as
EOS_ERR_SIGNATURE without its counter being consulted. docs/adr/README.md
is added in the shape of the eos repository's index; 020 avoids reusing
001 through 019.

CHANGELOG.md gains the Unreleased entries for the repair: the configure,
compile and link breakage after the 09-07 batch merge, the settled check
ordering with the suites streaming signed images, the re-derived Valgrind
list, fuzz-build in the CI gate, counted tests_run, the EoSim Windows legs
running under bash, and the Scorecard action on its ghcr.io-hosted release.
The changelog said eleven suites had no Valgrind run. Eleven were missing
from EBLDR_UNIT_TESTS, but four of those were named in the hand-written
foreach and did run; seven had no run at all. Say which.

ADR-020: its design-document citation now says where it comes from (the
architecture review of embeddedos-org#115), and the sentence about the two PRs' bases is
replaced with what the history shows -- embeddedos-org#103's commits predate embeddedos-org#104's merge,
and embeddedos-org#104 was written without embeddedos-org#103's check in place.
The regeneration test's failure path -- the unified diff under the
regenerate command -- ran only when the header was stale, so a green run
never executed it and the coverage report said so. The report is now a
helper the match asserts with, and a second test drives the helper with
two byte strings that differ in one byte and checks the command and both
sides of the changed line appear.
Every job logs "Node.js 20 is deprecated. The following actions target
Node.js 20 but are being forced to run on Node.js 24". GitHub's changelog
(2025-09-19, editor's note 2026-08-25): Node 24 is the default since
2026-06-16, and the Node 20 opt-out stops working when Node 20 is removed
from the runners on 2026-09-23. Whether a node20-declared action then errors
is not stated; today they run on the forced Node 24 and pass. This moves
26 uses: references in 8 workflow files to majors that declare
node24, so nothing here depends on a fallback GitHub calls temporary.

Pinned by commit SHA with the release tag in a comment (OpenSSF
Pinned-Dependencies), each SHA resolved from the exact release tag and
confirmed identical to what the floating major tag points at:

  actions/checkout                   -> 3d3c42e  # v7.0.1  (runs.using: node24)
  actions/configure-pages            -> 45bfe01  # v6.0.0  (runs.using: node24)
  actions/deploy-pages               -> 368f825  # v5.0.1  (runs.using: node24)
  actions/github-script              -> 3a2844b  # v9.0.0  (runs.using: node24)
  actions/setup-python               -> 5fda3b9  # v7.0.0  (runs.using: node24)
  actions/upload-artifact            -> 043fb46  # v7.0.1  (runs.using: node24)
  actions/upload-pages-artifact      -> fc324d3  # v5.0.0  (runs.using: composite)
  github/codeql-action               -> b96794f  # v4.38.0  (runs.using: composite)
  softprops/action-gh-release        -> efb3536  # v3.0.3  (runs.using: node24)

Breaking-change audit against this repository's usages, from each action's
release notes: download-artifact is used by name or by pattern with
merge-multiple (v5's single-ID path change: "no action needed"; v8's
digest-mismatch=error default is wanted); github-script scripts use only
the injected github/context objects, never require(); codecov uses
files/flags, both still accepted; codeql-action v4's bundle minimum is met
on hosted runners and init's languages/config-file inputs are unchanged;
configure-pages v5's break is Next.js-only; checkout/setup-python/
upload-artifact/cache/deploy-pages/gh-release majors are runtime-only
(runner >= 2.327.1, met by every hosted image used here; no self-hosted
runner exists in this org).

Only workflow files that no open pull request modifies are touched, so
this cannot conflict with the review queue; the rest are listed in the PR
and follow once those PRs land.

Verified: every file parsed before and after, with the parsed structure
asserted equal apart from uses: values -- triggers, job names, job-level
keys, step count and every non-uses step key unchanged, line endings
unchanged. pytest 82 passed.

Closes embeddedos-org#143
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
tests/unit/test_fw_update_test_sigs.py 93.33% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@Kartikey1306

Copy link
Copy Markdown
Contributor Author

Run evidence for this head (1e33831), per bumped file — 26 checks, 0 failing.

file run result
codeql.yml 35211504355 success — check names unchanged
simulation-test.yml 35211504377 success
auto-assign.yml 35211504468 NOT RUN — job skipped by design: if: head.repo == github.repository, and this PR is from a fork; the github-script@v9 line was not executed, the no-require() audit is the evidence

ci.yml and build.yml ran green (35211504347, 35211504432) but are deferred, not changed here — they belong to #115's and #122's stacks.

The other 5 bumped files (deploy-pages, nightly, sync-release-branch, video-build, weekly) have no pull_request trigger: NOT RUN, first exercised after merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Every workflow runs actions pinned to Node 20 majors; GitHub removes Node 20 from runners on 2026-09-23

2 participants