Skip to content

fix(ci): build the musl @cipherstash/auth binary in Alpine, and check every binary's C library - #1018

Merged
auxesis merged 3 commits into
mainfrom
fix/auth-musl-links-musl
Oct 2, 2026
Merged

auxesis merged 3 commits into
mainfrom
fix/auth-musl-links-musl

Conversation

@auxesis

@auxesis auxesis commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

This PR fixes the musl build of @cipherstash/auth. Today the linux-x64-musl binary links glibc, the C library most Linux systems use, instead of musl, the C library on systems such as Alpine Linux. On a musl system, the binary fails to load. This PR builds that binary inside Alpine, and makes every build check its own binary's C library before the package is packed.

It must merge before PR E, #1009, which turns on publishing for @cipherstash/auth during today's freeze for the stack crates import (Linear issue CIP-4274).

The preflight found the fault

auth-preflight.yml is a dry run of the @cipherstash/auth release. It builds the seven packages, then checks each binary before installing it. For each Linux binary, it reads the binary's list of required libraries with readelf -d. A gnu binary must require libc.so.6, which is glibc, and the musl binary must not.

Run 37049240996, against PR E's branch, failed that check: linux-x64-musl links glibc — it is the gnu binary.

The fault is older than this repository. The suite's published @cipherstash/auth-linux-x64-musl 0.44.0 requires libc.so.6 and the glibc loader ld-linux-x86-64.so.2. The suite built musl the same way, and never ran this check.

Why the binary linked glibc

_build-auth-artifacts.yml built the musl binary on the Ubuntu runner. It asked Rust for the x86_64-unknown-linux-musl target and installed musl-tools, but it never told Cargo which linker to use for that target. So Cargo used the runner's default linker, which links against glibc.

Build the musl binary inside Alpine

Alpine Linux is a musl system, so its compiler and runtime libraries are musl's own. This is how napi-rs's own build templates build musl binaries. For the linux-x64-musl leg only, the workflow now:

  1. skips the host's dependency install and build;
  2. starts node:22-alpine, pinned by image digest, because its output is published with provenance;
  3. installs Rust at the version the root mise.toml pins, 1.94.1, and pnpm at the root packageManager version;
  4. installs only the auth package's dependencies, and runs the same napi build command as the other legs;
  5. sets RUSTFLAGS=-C target-feature=-crt-static, so the binary links musl when it loads. A Node.js native module has to be built that way.

Every later step is the same as before.

Two other ways failed on 2 October 2026:

  • Ubuntu's musl-gcc cannot link a Rust shared library, because musl-tools has no musl libgcc_s: cannot find libgcc_s.so.1.
  • The musl.cc toolchain that _build-ffi-artifacts.yml uses timed out from GitHub's runners on six tries in a row.

Check every Linux binary as it is built

Until now, only the preflight read which C library each binary links, and nothing runs the preflight automatically. release.yml builds through the same file and publishes, so a release could have published a glibc-linked musl binary. Now every build checks its own binary before the package is packed:

  • After each Linux build, a step reads the binary's required libraries. The gnu binaries must require libc.so.6. The musl binary must not, and it must require musl's libc.musl-x86_64.so.1. A statically linked binary has no C library entry at all, and Node.js cannot load it as a native module.
  • Inside the Alpine container, straight after the build, Node.js loads the musl binary. That also catches a missing runtime library or an unresolved symbol, which reading the library list cannot see.

The preflight also loads the musl package inside the same Alpine image now, through the wrapper. Before, its smoke test installed only the runner's own linux-x64-gnu package, so no job ever loaded the musl binary.

The three commits

  1. 13d46c52 builds the musl binary inside Alpine, and adds the C library check to every Linux build.
  2. ea9dc4a0 acts on cipherstash-bot's review:
    • the musl rule now requires musl, not only refuses glibc;
    • the preflight loads the musl package inside Alpine;
    • the container hands its files back on every exit, through a trap;
    • the musl leg skips the host Rust target and node-gyp, which only the host build uses;
    • new tests cover each change.
  3. 6ba1a0dd loads the musl binary inside the build container, as freshtonic suggested.

How it was checked

  • auth-preflight at the head of this branch, 6ba1a0dd, run 37062179778, passes. All seven builds pass, and so do the WASM and wrapper build and both smoke tests. The musl binary loads inside its build container. The preflight reports linux-x64-musl: links musl, not glibc, and musl smoke OK from inside Alpine.
  • By hand, the musl binary from the first passing run loads under Node.js on x86-64 Alpine. The new Alpine smoke test fails on the suite's glibc-linked 0.44.0, with Failed to load native binding.
  • Tests in scripts/__tests__/auth-build-artifacts.test.mjs cover each part of the change. Each one fails when its part is removed.
  • pnpm test:scripts passes 1,103 tests, with 1 skipped, and pnpm run code:check passes.
  • A trial merge with PR E's branch has no conflicts.

What happens next

  1. After this PR merges, auth-preflight runs again against PR E's branch, to confirm the fix there.
  2. PR E, ci: arm publishing for @cipherstash/auth and the stack-* crates #1009, carries an @cipherstash/auth patch changeset for this fix, so the next release tells Alpine users. It cannot go here, because main's lint:auth-changeset refuses any @cipherstash/auth changeset until PR E removes the freeze.
  3. Linear issue CIP-4283 tracks keeping the Alpine image digest current and pinning its packages.
  4. Linear issue CIP-4284 tracks keeping one copy of the C library rules. Today, this file and both preflights each have a copy.
  5. protect-ffi's release build has two related gaps. It downloads its musl toolchain from musl.cc, which timed out from GitHub's runners today. Its C library check also runs only in ffi-preflight.yml. Linear issue CIP-4282 tracks both.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a

… every binary's C library

The linux-x64-musl leg of _build-auth-artifacts.yml targeted
x86_64-unknown-linux-musl on the Ubuntu runner but named no musl linker,
so cargo linked the binary against the runner's glibc. Such a binary
fails to load on musl systems such as Alpine. auth-preflight's libc
check caught it: "linux-x64-musl links glibc — it is the gnu binary".
The suite's published @cipherstash/auth-linux-x64-musl 0.44.0 has the
same fault, because its workflow built musl the same way and never ran
such a check.

Build that leg inside node:22-alpine, pinned by digest: Alpine is a musl
system, so its compiler and runtime libraries are musl's own. The
container installs Rust at the version mise pins and pnpm at the root
packageManager version, installs only the auth package's dependencies,
and runs the same napi build, with -crt-static off so the binary links
musl at load time. Two other ways failed on 2 October 2026: Ubuntu's
musl-gcc cannot link a Rust shared library ("cannot find
libgcc_s.so.1"), and the musl.cc toolchain that _build-ffi-artifacts.yml
uses timed out from GitHub's runners.

Also check which C library each Linux binary links as it is built,
before the package is packed. Until now only auth-preflight.yml read it,
and nothing runs the preflight automatically, so a release could have
published a glibc-linked musl binary. The gnu binaries must need
libc.so.6, and the musl binary must not.

Tests in auth-build-artifacts.test.mjs cover the Alpine build, the
skipped host steps for the musl leg, and the C library check coming
before the pack.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
@auxesis
auxesis requested a review from a team as a code owner October 2, 2026 20:09
@changeset-bot

changeset-bot Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6ba1a0d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cipherstash-bot cipherstash-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Recommendation: 🟡 merge after changes

Moving the musl build into Alpine is the right fix. Running the C library check inside _build-auth-artifacts.yml closes a real gap: release.yml builds through this file, and nothing runs auth-preflight.yml automatically.

One thing to change before merge: the linux-x64-musl rule only proves the binary does not link glibc. It never proves the binary links musl, so a statically linked binary passes. Two test gaps are also worth closing in this pull request. Both gaps let a later edit remove protection while the tests still pass. Everything in the list below can wait for a follow-up pull request.

Other findings not posted as comments

  • No test asserts that the case statement has a rule for every Linux platform in the build matrix. The *) branch does fail the job, so the mistake is caught. But it is caught at the end of a 60 minute Rust build, during a release or preflight run. It is not caught in the pull request that added the platform. The first test in the file already derives the platform list from platforms/, so the pattern exists. .github/workflows/_build-auth-artifacts.yml:186
  • No test asserts that either rejection branch calls exit 1. An edit could keep the grep patterns and the error text but drop exit 1. The step would then print an error and still pack the binary. scripts/__tests__/auth-build-artifacts.test.mjs:113
  • The linux-x64-musl leg still installs a host Rust target and node-gyp that it does not use. Add the Rust target runs rustup target add x86_64-unknown-linux-musl on the host. Install node-gyp installs a global package. Neither reaches the Alpine container, which installs its own Rust. Add if: ${{ matrix.platform != 'linux-x64-musl' }} to both steps to save time on every release and preflight run. Keep the jdx/mise-action step, because mise current rust needs it. .github/workflows/_build-auth-artifacts.yml:96
  • chown -R "$HOST_UID:$HOST_GID" /build is the last command in the container. A failed pnpm install or napi build therefore leaves root-owned files in $GITHUB_WORKSPACE. GitHub hosted runners are deleted after the job, so nothing breaks today. It would break on a self-hosted runner. A trap would always run the chown. .github/workflows/_build-auth-artifacts.yml:147
  • Nothing updates the Alpine image digest, and the apk add line does not pin versions. .github/dependabot.yml has no docker entry. Its github-actions entry reads uses: lines only, so it cannot see an image name inside a run: script. The digest will stay at today's value and stop receiving Alpine security fixes. apk add --no-cache build-base cmake perl linux-headers git curl rustup resolves whatever versions Alpine serves on the day. Two builds of the same commit can therefore use different compilers. That matters here, because the build publishes the output with provenance. .github/workflows/_build-auth-artifacts.yml:128
How this review was made
Agent Model Review type Result
claude claude-opus-5 infracode 5 found, 2 posted
claude claude-opus-5 test-gap 4 found, 3 posted
codex gpt-5.6-terra infracode 1 found, 1 posted
codex gpt-5.6-terra test-gap 1 found, 0 posted

Synthesis: claude-opus-5 merged the findings, removed duplicates and dropped findings it could not confirm in the code. 1 posted finding(s) were raised by two or more models.

Plain language: claude-opus-5 read every comment as a new reader would. 2 comment(s) had a problem that stopped the reader acting; it rewrote 0. It also rewrote the review body.

Stack: not part of a stack.

Context loaded: the description, 0 linked issue(s) and 1 discussion entries.

grep -q 'NEEDED.*libc\.so\.6' <<< "$dynamic" || {
echo "::error::$PLATFORM does not link glibc"; exit 1; } ;;
linux-x64-musl)
if grep -q 'NEEDED.*libc\.so\.6' <<< "$dynamic" ; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Change before merge: the linux-x64-musl rule rejects glibc, but it does not require the musl C library.

Impact: A binary with no C library NEEDED entry at all passes this check. The Rust target x86_64-unknown-linux-musl turns on crt-static by default, which links musl statically. The RUSTFLAGS="-C target-feature=-crt-static" line on line 137 is the only thing that turns that off. If someone removes that line, this step still prints links the expected C library, and the release publishes a binary that Node.js cannot load as a native addon. Nothing else catches it: auth-preflight.yml installs only the linux-x64-gnu platform package, so no job loads the musl binary.

Evidence: The gnu arm on line 178 requires NEEDED.*libc\.so\.6. The musl arm only rejects that entry. The passing run message quoted in the description, linux-x64-musl: no glibc NEEDED entry, is true for a static binary as well.

Fix: Require the musl C library after the glibc rejection. On Alpine x86_64 the shared library name is libc.musl-x86_64.so.1.

linux-x64-musl)
  if grep -q 'NEEDED.*libc\.so\.6' <<< "$dynamic" ; then
    echo "::error::linux-x64-musl links glibc — it is the gnu binary"
    exit 1
  fi
  grep -q 'NEEDED.*libc\.musl-' <<< "$dynamic" || {
    echo "::error::linux-x64-musl has no musl libc NEEDED entry — it is linked statically"
    exit 1; } ;;

Add the same requirement to auth-preflight.yml:90, so the dry run and the build agree.

Found by 2 models: claude, codex

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ea9dc4a0. The linux-x64-musl rule now requires a NEEDED entry for libc.musl-, here and in auth-preflight.yml. A statically linked binary now fails with linked statically.

const build = runs(binaries).filter((run) => /\bnapi build\b/.test(run))
expect(build).toHaveLength(1)
// One host build, and one inside Alpine for the musl leg.
expect(build).toHaveLength(2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Change before merge: this test now expects two napi build steps, but the step-order test below still checks only the first one, so the new Alpine build is not covered.

Impact: Verify the build changed no tracked file runs git diff --exit-code on the auth package. It must run after every build step, because napi build regenerates native.d.ts and the committed copy must match. The test on line 68 uses all.findIndex((run) => /\bnapi build\b/.test(run)), which returns the index of the host build only. If an edit moves the Alpine build step after the git diff step, that test still passes. The musl leg would then pack a regenerated native.d.ts that nothing compared against the committed copy.

Fix: Check the order against every build step.

it('fails the leg when the build changed a tracked file', () => {
  const all = runs(binaries)
  const builds = all
    .map((run, index) => index)
    .filter((index) => /\bnapi build\b/.test(all[index]))
  const check = all.findIndex((run) =>
    new RegExp(`git diff --exit-code\\b.*${AUTH_DIR}`).test(run),
  )
  expect(builds).toHaveLength(2)
  for (const build of builds) expect(check).toBeGreaterThan(build)
})

Found by 1 model: claude

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ea9dc4a0. The test now checks that Verify the build changed no tracked file comes after both napi build steps.

)
const run = musl.map((step) => String(step?.run ?? '')).join('\n')
const env = Object.assign({}, ...musl.map((step) => step?.env ?? {}))
expect(env.ALPINE_NODE_IMAGE).toMatch(/-alpine@sha256:[0-9a-f]{64}$/)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Change before merge: this test checks the value of ALPINE_NODE_IMAGE, but it never checks that docker run uses that variable.

Impact: An edit can write node:22-alpine as a literal in the docker run command and leave ALPINE_NODE_IMAGE unused. Lines 86 and 87 both still pass. The musl binary would then come from a mutable tag. The workflow comment on line 119 gives the reason the image must be pinned by digest: the output is published with npm provenance.

Fix: Assert that docker run names the variable, and that it carries no unpinned tag.

expect(env.ALPINE_NODE_IMAGE).toMatch(/-alpine@sha256:[0-9a-f]{64}$/)
expect(run).toMatch(/docker run[\s\S]*"\$ALPINE_NODE_IMAGE"/)
expect(run).not.toMatch(/docker run[\s\S]*\bnode:\d+-alpine(?!@)/)

Found by 1 model: claude

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ea9dc4a0. The test now checks that docker run uses "$ALPINE_NODE_IMAGE", and that no node:22-alpine tag appears without a digest.

Keeping the digest current, and pinning the apk packages, is in Linear issue CIP-4283.


# Every build checks which C library its binary links, so a release
# cannot publish a musl binary that links glibc even if nobody ran
# auth-preflight first. The same check as auth-preflight.yml's smoke test.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fix in a follow-up: this comment says the check is the same as the one in auth-preflight.yml, and nothing in the repository keeps the two copies the same.

Impact: The same C library rules now exist in three files: here, auth-preflight.yml:84-97 and ffi-preflight.yml:109-118. If someone changes a rule or adds a platform in one file, the others keep the old rule. The build step would then accept a binary the preflight rejects, or the reverse. The preflight is the dry run people read before a release, so the two disagreeing is worse than either rule alone. The new test in scripts/__tests__/auth-build-artifacts.test.mjs reads only this file.

Fix: Put the rules in one shell script, for example .github/scripts/check-c-library.sh <platform> <binary-path>, and call it from both auth workflows. If you keep the copies, add a test that reads the case block out of each file and asserts they match. The repository already does this for duplicated workflow logic in scripts/__tests__/eql-workflow-filters.test.mjs.

Found by 1 model: claude

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is now Linear issue CIP-4284. It moves the rules into one script that all three workflows call, and tests that script directly.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Building the musl binary in Alpine is the correct fix, and the root cause in the description agrees with the old workflow. I read the diff and the earlier automated review. I agree with its first finding: the linux-x64-musl rule must also require a musl NEEDED entry, not only reject glibc.

Two more items that the earlier review does not include:

  1. Load the binary in Alpine after the build (inline comment). At the moment no job loads the musl binary. readelf only infers that it will load. The container is already running, so a require costs almost nothing. It proves the claim of this PR directly.

  2. Add a changeset for the fix. @cipherstash/auth-linux-x64-musl 0.44.0 on npm does not load on musl. This PR is the fix for that published artefact. #1009 has six @cipherstash/auth changesets, so the version will increase and the fixed binary will ship. But none of the six tells Alpine users that the next version fixes their install. AGENTS.md (checklist item 9) asks for a changeset for a bug fix to a published package. A short '@cipherstash/auth': patch entry is sufficient, for example: "The linux-x64-musl binary now links musl. In 0.44.0 it linked glibc and did not load on Alpine Linux." If the changeset must wait until #1009 merges, put it in #1009.

I did not run the workflow. I depend on run 37057641199 in the description for the result of the Alpine build.

Comment thread .github/workflows/_build-auth-artifacts.yml Outdated
cipherstash-bot's review of #1018, verified:

- The linux-x64-musl rule only refused glibc, so a statically linked
  binary, with no libc entry at all, passed it. Node.js cannot load that
  as a native module, and no job loaded the musl binary: auth-preflight
  installs only the runner's own linux-x64-gnu package. Both the build
  and the preflight now require a NEEDED entry for musl's libc, and the
  preflight installs the wrapper and the musl package inside the pinned
  Alpine image and loads them. That step loads the fixed build, and
  fails on the suite's glibc-linked 0.44.0 with "Failed to load native
  binding".
- The container's chown ran last, so a failed build left root-owned
  files in the workspace. A trap now runs it on every exit.
- The musl leg no longer adds a host Rust target or installs node-gyp,
  which only the host build uses.

Tests now cover: the tracked-file check coming after both builds;
docker run using the pinned image variable, not a tag; the trap; musl
being required in the build and the preflight; every rejection exiting
1; a rule for every Linux platform in the matrix; and the Alpine load
step using the build's image. Each fails when its part is removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
readelf only infers that the musl binary will load. Require it with
Node.js inside the Alpine container straight after the build, so every
build proves it, release builds included, and a missing runtime library
or an unresolved symbol fails the job before the package is packed. A
statically linked binary fails here too. freshtonic suggested this in
review of #1018.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
auxesis added a commit that referenced this pull request Oct 2, 2026
…usl again

@cipherstash/auth-linux-x64-musl 0.44.0 linked glibc, so the package
did not load on musl systems such as Alpine Linux. #1018 fixes the
build. None of the six carried changesets says so, and AGENTS.md asks
for a changeset for a bug fix to a published package. It lives here
because main's lint:auth-changeset refuses any @cipherstash/auth
changeset until this PR removes the freeze. freshtonic raised it in
review of #1018.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

No blocking issues were identified, and the linked preflight run completed successfully.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes the @cipherstash/auth musl build before publishing is enabled, and adds checks to prevent incorrectly linked Linux binaries from shipping.

Changes:

  • Builds the musl binary inside a digest-pinned Alpine container.
  • Checks every Linux binary’s C library before packing.
  • Adds an Alpine smoke test and workflow regression tests.
File Description
scripts/​__tests__/​auth-build-artifacts.test.mjs Tests Alpine build configuration and validation steps.
.github/​workflows/​auth-preflight.yml Requires musl linkage and smoke-tests loading in Alpine.
.github/​workflows/​_build-auth-artifacts.yml Builds musl in Alpine and validates Linux linkage before packing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I read the two new commits (ea9dc4a0, 6ba1a0dd) against my earlier review and the cipherstash-bot review. The merge-blocking items are fixed. I approve.

Fixed:

  • The linux-x64-musl rule now requires a NEEDED entry for libc.musl-, in the build and in auth-preflight.yml. A static binary now fails.
  • The build container loads the binary with node -e "require(…)" after napi build. The file name stack-auth-node.linux-x64-musl.node agrees with napi.name in the auth package.json. A trap … EXIT now runs the chown, so a failed build does not leave root-owned files.
  • auth-preflight.yml installs the wrapper and the musl tarball in the same pinned Alpine image, and loads them.
  • The tests now check that the tracked-file check comes after both builds, that docker run uses $ALPINE_NODE_IMAGE, that each ::error:: has an exit 1, and that each Linux platform in the matrix has a C library rule. These are the test gaps from the bot review.
  • The musl leg skips the host rustup target add and node-gyp steps.

Not blocking. Do these in #1009 or in a follow-up:

  1. Changeset. This PR still has no changeset. As I wrote before, a changeset in #1009 is sufficient. Please make sure that one of its @cipherstash/auth entries tells Alpine users that the linux-x64-musl binary now links musl. In 0.44.0 that binary did not load.
  2. Three copies of the C library rules. _build-auth-artifacts.yml, auth-preflight.yml and ffi-preflight.yml each have a copy, and no test keeps them the same. This is the bot's open follow-up thread.
  3. Alpine image digest and apk add versions. Nothing updates the digest, and apk add does not pin versions.

Not verified by me: I did not run the workflow. Run 37057641199 in the description tested 618ced3b, which is before these two commits. Thus no run in the description shows the new Alpine smoke step in auth-preflight.yml or the require in the build container. The ea9dc4a0 commit message says that the smoke step loads the fixed build. Before #1009 merges, please run auth-preflight again on this head, or on #1009's branch after the merge, as the description plans.

@auxesis

auxesis commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Thank you both. Here is where each open item went.

After this PR merges, auth-preflight runs again against #1009's branch, before #1009 merges.

@auxesis
auxesis merged commit 37c23d7 into main Oct 2, 2026
36 checks passed
@auxesis
auxesis deleted the fix/auth-musl-links-musl branch October 2, 2026 20:59
auxesis added a commit that referenced this pull request Oct 3, 2026
…packing

Only ffi-preflight.yml checked which C library the Linux protect-ffi
binaries link, and nothing runs that dry run automatically. release.yml
builds through _build-ffi-artifacts.yml, so a release could publish a
musl binary that links glibc. The suite published a glibc-linked
@cipherstash/auth-linux-x64-musl 0.44.0 that way.

Each Linux leg now runs scripts/check-c-library.sh on its binary after
placing it and before packing it, as _build-auth-artifacts.yml does
since #1018. scripts/__tests__/ffi-build-artifacts.test.mjs pins the
step's place, its Linux condition and the binary it reads.

Refs: CIP-4282

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
auxesis added a commit that referenced this pull request Oct 3, 2026
_build-ffi-artifacts.yml downloaded its musl toolchain from musl.cc. On
2 October 2026 that download timed out from GitHub's runners on six
tries, so protect-ffi's next release would fail at that step. Its digest
also pinned the file without authenticating where it came from.

The linux-x64-musl leg now builds inside the node:22-alpine image, at
the digest that _build-auth-artifacts.yml uses since #1018. Alpine is a
musl system, so its own compiler links musl. The container runs the
matrix's build script and log with -crt-static, places the binary as
the host legs do, and loads it with Node.js on musl. The host build
steps skip that leg, and the Rust version is the runner's, as on the
other five legs.

ffi-preflight.yml now also installs the wrapper and the musl tarball
inside the same Alpine image and loads the binding, as auth-preflight
does. A test checks that every workflow uses one pinned Alpine image.

Refs: CIP-4282

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PaY5xYydZUWhv8Nex9Sw8a
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.

5 participants