Skip to content

docs(security): document the bundled Node.js runtime the SBOM misses - #584

Merged
cevheri merged 4 commits into
libredb:mainfrom
dchaudhari7177:docs/bundled-node-runtime
Sep 7, 2026
Merged

docs(security): document the bundled Node.js runtime the SBOM misses#584
cevheri merged 4 commits into
libredb:mainfrom
dchaudhari7177:docs/bundled-node-runtime

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

Closes #545. Docs plus one test file; no script changes, as the issue specifies.

SECURITY.md already scoped the SBOM to the dependency closure and named the runtime gap honestly. What it did not do is answer the question that gap raises: which Node.js is inside, and where did the bytes come from. A reader could learn a gap exists and still be unable to act on it.

The new Bundled Node.js runtime entry sits directly under that paragraph and records: version 24.18.0 (Krypton LTS), the nodejs.org/dist/v24.18.0/ directory it is fetched from, the three exact artefacts (linux-x64.tar.xz, linux-arm64.tar.xz, win-x64.zip), both fetch scripts, sha256 pinned in-repo and verified after download, the licence, and which packaged artefacts ship it — including that the npm package does not, because it uses the host's Node.

I also recorded why the digests are pinned in-repo rather than read from the SHASUMS256.txt served next to the download. That is the reason the pin is duplicated across two scripts and looks redundant at a glance, and it is exactly the kind of thing a security reader wants stated rather than inferred.

The entry is hand-maintained, so it needs a guard. tests/unit/bundled-node-runtime-docs.test.ts reads NODE_VERSION out of both scripts, requires them to agree, and requires the document to name that version, its upstream dist directory, all three artefact filenames, and both scripts by path.

Two further assertions guard the surrounding claims rather than the table:

  • the "does not describe the pinned Node.js runtime" sentence must survive. If someone later extends the SBOM to cover the runtime, that failing is the prompt to rewrite this section rather than leave two contradictory statements in one security document;
  • the pin must stay at or above package.json's engines.node floor. Both scripts already instruct this in comments and nothing checked it.

Verified the guard bites: bumping NODE_VERSION to 25.1.0 in packaging/linux/fetch-node.sh alone fails 4 of the 7 tests — the two-scripts-agree check plus the version, dist-URL and artefact-name checks.

7 pass, 0 fail on the real tree.

SECURITY.md already scoped the SBOM to the dependency closure and named the
runtime gap, but never said which Node.js is inside the artefacts or where it
comes from. A reader could learn the gap exists and still not answer the
question the gap raises.

Adds a hand-maintained component entry under the SBOM section: version 24.18.0
(Krypton LTS), the nodejs.org dist directory it is fetched from, the three
exact artefacts (linux-x64, linux-arm64, win-x64), the two scripts that fetch
them, sha256 pinned in-repo, the licence, and which packaged artefacts ship it
and which do not. No script changes.

It also records why the digests are pinned in-repo rather than read from the
SHASUMS256.txt served alongside the download, since that is the reason the pin
is duplicated in two places and looks redundant.

tests/unit/bundled-node-runtime-docs.test.ts keeps the entry from going stale:
it reads NODE_VERSION out of both scripts, requires them to agree, and requires
the document to name that version, its upstream dist directory, all three
artefact filenames and both scripts. Two more assertions guard the surrounding
claims: the SBOM-does-not-cover-it sentence must survive, so the document
cannot end up with two contradictory statements, and the pin must stay at or
above package.json's engines.node floor, which the scripts ask for and nothing
checked.

Verified the guard bites: bumping NODE_VERSION in one script without touching
SECURITY.md fails four of the seven tests.

Closes libredb#545
@cevheri

cevheri commented Sep 6, 2026

Copy link
Copy Markdown
Member

Thanks, this is a good first PR.
I reproduced your mutation locally: bumping NODE_VERSION in the linux script alone does fail 4 of the 7 tests, and the entry matches both scripts on every field it names.

Two small things in what you already wrote:

  1. In tests/unit/bundled-node-runtime-docs.test.ts, indexOf returns -1 when the heading is missing, so slice(-1) gives a one-character string and expect(section.length).toBeGreaterThan(0) can never fail.
    Rename the heading locally and you will see the failure come from the next assertion instead, with Received: "\n". expect(SECURITY.indexOf("#### Bundled Node.js runtime")).not.toBe(-1) is the check you meant.

  2. SECURITY.md writes the floor as >=24.0.0 in prose and nothing guards that literal, so it goes stale the day the floor moves. You already read engines.node in the last test; one more expect(SECURITY).toContain(engines) closes it.

And one that is yours to take or leave. Your entry meets the done-when on docs/BACKLOG.md C7, and that file's rule is to delete an entry when the work lands, with no DONE marker. So C7 can go in this PR:

  • delete the C7 block from docs/BACKLOG.md
  • set the section index line to C3-C10 · 6, since C3 and C10 stay the extremes
  • drop ", tracked in docs/BACKLOG.md" from your SECURITY.md paragraph, or it points
    at an entry that is no longer there

tests/unit/backlog-structure.test.ts checks all three. I ran it with those edits: 100 pass, and forgetting the count fails "Security Phase 2 deferrals counts its entries", so you will know.

The narrower thing that stays open, making the SBOM itself carry the component, I will file as its own entry. That one is my claim to write, not yours.

Happy to do the C7 part myself if you would rather stop at the two fixes above.
Tell me either way.

cevheri added a commit that referenced this pull request Sep 6, 2026
…#603)

#584 closes C7 by giving SECURITY.md a hand-maintained "Bundled Node.js
runtime" table, which is exactly what C7's Done-when accepted: a sibling
document, or a hand-maintained component entry. The residual is the half a
scanner reads. The CycloneDX document the release job generates still has no
component for the runtime, and its own verify step names the three ecosystems
it expects, none of which is a shell script that curls a tarball.

Filed as its own entry rather than folded back into C7, because narrowing a
Done-when after the work that satisfies it has landed moves a pre-committed
acceptance. C7 is the contributor's to delete on #584; this is the claim that
outlives it.

The index line moves with it, C3-C10 7 becomes C3-C11 8, and both halves are
measured against tests/unit/backlog-structure.test.ts: leaving the count at 7
fails "counts its entries", leaving the extreme at C10 fails "names the real
extremes".
@cevheri

cevheri commented Sep 6, 2026

Copy link
Copy Markdown
Member

Heads up before you push the review fixes: main moved this afternoon. 0.13.7 became 0.14.0 and the chart 0.1.58 became 0.1.59, tagged at 14:05 +03. This branch is 17 commits behind and still carries Chart.yaml version: 0.1.58 / appVersion: "0.13.7".

Your checks are green, but they started at 13:05 +03, an hour before that tag, so they were measured against a main that no longer exists. Refreshing makes the green mean something again.

Please take current main before the next round. From a fork:

git remote add upstream https://github.com/libredb/libredb-studio.git   # once
git fetch upstream
git merge upstream/main          # or: git rebase upstream/main
git push                         # after a rebase: git push --force-with-lease

Why now rather than at merge time: the chart version sync guard is the first step of Lint, Typecheck and Build, and CI fetches main with --depth=1, so it compares your Chart.yaml against main's tip rather than against your merge base. libredb-studio-0.1.58 is a released tag, so the guard refuses it. When that step fails the job stops there and the eleven gates behind it (the three other drift guards, format, lint, typecheck, knip, build, build:lib, attw and the Go launcher tests) come back unmeasured rather than green. Measured on #582 earlier today; merging main cleared it.

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three things from the review.

The "SECURITY.md names the pinned version" test could not fail on a missing
heading: indexOf returns -1, slice(-1) is a one-character string, and
`length > 0` holds. Assert on the index, then read the section from it.

SECURITY.md quotes the engines.node floor as >=24.0.0 in prose, and nothing
guarded that literal. The floor test already reads engines.node out of
package.json, so one more assertion keeps the prose from going stale the day
the floor moves.

This entry meets the done-when on BACKLOG C7 (a hand-maintained component
entry), and that file retires an entry by deleting it rather than marking it
DONE. So C7 goes, the section index drops to C3-C10 · 6 with C3 and C10 still
the extremes, and SECURITY.md no longer points at a backlog entry that is not
there. The narrower gap -- making the SBOM itself carry the component -- stays
open as the maintainer's own entry to write.
@dchaudhari7177

Copy link
Copy Markdown
Contributor Author

Both fixes are in, plus the C7 retirement, and the branch now carries current main.

1. The heading check that could not fail. You were right that slice(-1) gives a one-character string, so expect(section.length).toBeGreaterThan(0) held no matter what. I renamed the heading locally to confirm it: the old test only went red on the next assertion, with Received: "\n". It now asserts on the index itself and reads the section from it:

const heading = SECURITY.indexOf("#### Bundled Node.js runtime");
expect(heading, "SECURITY.md has no bundled-runtime section").not.toBe(-1);
expect(SECURITY.slice(heading)).toContain(version);

With the heading renamed that fails on the check that means what it says.

2. The unguarded >=24.0.0 literal. Added to the floor test, which already reads engines.node:

expect(SECURITY, `SECURITY.md does not quote the engines.node floor ${engines}`).toContain(engines);

3. C7. Taken — thanks for pointing at the file's rule. The block is gone from docs/BACKLOG.md, the section index line is updated, and ", tracked in docs/BACKLOG.md" is dropped from the SECURITY.md paragraph so it no longer points at an entry that is not there.

One thing changed since you wrote that: main has picked up a C11, so the index line is C3–C11 · 7, not C3–C10 · 6. C3 and C11 are the extremes now, and the section holds C3, C4, C5, C6, C8, C10, C11. backlog-structure.test.ts agrees — dropping the count to 6 fails "Security Phase 2 deferrals counts its entries", exactly as you said it would.

The narrower gap, making the SBOM itself carry the component, is untouched and yours to file.

4. Refreshed against main. Merged upstream/main — the branch was 33 commits behind by the time I got to it. Chart.yaml is now version: 0.1.60 / appVersion: "0.14.1" from main rather than the released 0.1.58, so the chart version sync guard has something current to compare against and the eleven gates behind it actually run.

Local run of the two guard suites: 100 pass, 0 fail. (First run, one test timed out at the 5s default while it walked the repo cold; it passes well inside the limit once the cache is warm.)

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixes are in, C7 is retired, and I ran each one against a mutation rather than reading the diff. Baseline on your head: 7 pass in the new guard, 93 in backlog-structure, the 100 you reported.

Mutation Result
rename the #### Bundled Node.js runtime heading 1 fail, SECURITY.md has no bundled-runtime section
raise engines.node to >=26.0.0 1 fail, toBeGreaterThanOrEqual
lower engines.node to >=22.0.0 1 fail, SECURITY.md does not quote the engines.node floor >=22.0.0
bump NODE_VERSION in the linux script alone 4 fail
soften the SBOM-gap sentence 1 fail

The first row is the fix I asked for, and it now fails on the check that means what it says. The third row is the one worth keeping in mind. Raising the floor is the obvious probe, but it dies at the major-version comparison two lines earlier, so it never reaches your new assertion — it would pass identically if that line were not there. Lowering the floor keeps the comparison satisfied and leaves only the prose to fail. Two assertions in one test can shadow each other; a probe has to be aimed at the one you are trying to prove.

C7: block gone, index line correct, and the pointer dropped from the paragraph. You were right that main had moved it to C3–C11 · 7 — reverting the count to 6 fails "Security Phase 2 deferrals counts its entries", so it is guarded either way. C11 is the narrower entry, filed in #603.

I pushed one commit to your branch, fa419b7f. The Check formatting (Biome) step wanted your floor assertion on a single line: joined it is 118 characters, and this repo's lineWidth is 120, so Biome will not accept the wrapped form. Nothing about the code, purely the formatter's opinion. Worth knowing because that step is the ninth of seventeen in Lint, Typecheck and Build and the job stops there — lint, typecheck, knip, build, build:lib, attw and the Go launcher tests all came back skipped, so a single wrapped line reads as eight gates unmeasured. bun run format:fix before you push is the whole fix; CLAUDE.md lists it first in the pre-commit set for that reason. I ran the other seven guards on your head locally and they all pass.

Good work on this one. You took a documentation issue and left behind a guard that fails for the right reason, which is the harder half.

@cevheri cevheri added the hacktoberfest-accepted Merged Hacktoberfest PR; counts for the participant label Sep 7, 2026
@cevheri
cevheri merged commit 2f93854 into libredb:main Sep 7, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hacktoberfest-accepted Merged Hacktoberfest PR; counts for the participant

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document the bundled Node.js runtime version and provenance the SBOM misses

2 participants