Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fix-keyring-cross-compile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"clerk": patch
---

Store macOS credentials in the system Keychain instead of a plaintext file.

- Previously, macOS builds silently stored the OAuth token in `~/Library/Application Support/clerk-cli/credentials` because cross-compiled binaries were missing the native Keychain binding.
- Run `clerk login` after upgrading so the CLI writes a fresh token into the Keychain and removes the old plaintext file.
9 changes: 8 additions & 1 deletion .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@
- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"
- run: bun install --frozen-lockfile
# --cpu=* --os=* forces bun to install every platform's optional deps,
# not just the Linux host's. Cross-compiling with bun build --compile
# embeds each target's native bindings (e.g. @napi-rs/keyring-<target>)
# into the executable; without this flag the darwin and win32 bindings
# are absent from node_modules and the bundler silently replaces them
# with throw stubs, leaving those binaries unable to reach the OS
# keychain and falling back to plaintext-file credential storage.
- run: bun install --frozen-lockfile --cpu='*' --os='*'

- name: Build all targets

Check failure

Code scanning / CodeQL

Checkout of untrusted code in a privileged context Critical

Potential execution of untrusted code on a privileged workflow (
issue_comment
)
env:
CLI_VERSION: ${{ inputs.version }}
ENV_PROFILES: ${{ secrets.ENV_PROFILES }}
Expand Down
30 changes: 29 additions & 1 deletion scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { parseArgs } from "node:util";
import { DEV_CLI_VERSION } from "../packages/cli-core/src/lib/version.ts";
import { targets } from "./lib/targets.ts";
import { type Target, targets } from "./lib/targets.ts";

function keyringBindingPath(target: Target): string {
const libcSuffix = target.libc === "musl" ? "-musl" : target.libc === "glibc" ? "-gnu" : "";
const winSuffix = target.os === "win32" ? "-msvc" : "";
const bindingName = `${target.os}-${target.cpu}${libcSuffix}${winSuffix}`;
return join("node_modules", "@napi-rs", `keyring-${bindingName}`, `keyring.${bindingName}.node`);
}

const { values } = parseArgs({
args: Bun.argv.slice(2),
Expand Down Expand Up @@ -48,6 +55,27 @@ if (selectedTargets.length === 0) {
);
}

// Cross-compile embeds each target's @napi-rs/keyring native binding into
// the output. A default `bun install` on any single host skips the bindings
// for other platforms (optional deps are filtered by os/cpu), so without a
// platform-unfiltered install the darwin/win32/arm64 builds would silently
// ship without the binding and fall back to plaintext-file credentials at
// runtime. Fail fast here so the dev sees the actionable fix.
const bindingChecks = await Promise.all(
selectedTargets.map(async (t) => {
const path = keyringBindingPath(t);
return { target: t, path, exists: await Bun.file(path).exists() };
}),
);
const missingBindings = bindingChecks.filter((check) => !check.exists);
if (missingBindings.length > 0) {
const list = missingBindings.map(({ target, path }) => ` - ${target.name}: ${path}`).join("\n");
throw new Error(
`Missing @napi-rs/keyring native bindings for:\n${list}\n\n` +
`Run \`bun install --frozen-lockfile --cpu='*' --os='*'\` to install every platform's optional deps.`,
);
}

console.log(`Building ${selectedTargets.length} target(s) with version ${version}\n`);

let failed = false;
Expand Down
Loading