From c69e6b50381207abda0cc28d1787f7f4d735a90e Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Fri, 17 Jul 2026 11:11:52 +1000 Subject: [PATCH] fix(deps): move @cipherstash/auth to 0.42.0, lockstep with its bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rc.2 skilltester run found every project-local npm install of the CLI/SDK dying at startup with "Failed to load native binding" (B1, reproduced 5/5 surfaces). Root cause: Dependabot bumped the six @cipherstash/auth-* catalog entries to 0.42.0 while @cipherstash/auth — individually ignored in dependabot.yml — stayed 0.41.0. auth pins its bindings as exact-version optional peerDependencies, so the skew makes npm nest per-consumer binding copies that the hoisted auth package cannot resolve. Reproduced against the published rc.2 and verified fixed with aligned versions (npm dedupes to one hoisted binding; auth loads): npm i @cipherstash/stack@1.0.0-rc.2 @cipherstash/stack-supabase@1.0.0-rc.2 npm i -D stash@1.0.0-rc.2 node -e "require('@cipherstash/auth')" # fatal before, loads after Three changes so it can't recur: - catalog: all seven @cipherstash/auth* entries at 0.42.0, with the lockstep invariant documented as load-bearing - dependabot: ignore @cipherstash/auth-* alongside @cipherstash/auth — partial bumps of this set are never safe - supply-chain e2e: a test that fails on any version skew across the seven entries (verified red against the exact rc.2 skew) auth 0.42.0 compatibility verified: stack 828/828 (incl. live ZeroKMS auth), cli 513/513, wizard 145/145. Claude-Session: https://claude.ai/code/session_01MxTTPaPP16m6br7Hoab94w --- .changeset/auth-binding-lockstep.md | 15 ++++++++++++ .github/dependabot.yml | 9 +++++++ e2e/tests/supply-chain.e2e.test.ts | 33 +++++++++++++++++++++++++ pnpm-lock.yaml | 38 +++++++++++++++++------------ pnpm-workspace.yaml | 16 ++++++++---- 5 files changed, 91 insertions(+), 20 deletions(-) create mode 100644 .changeset/auth-binding-lockstep.md diff --git a/.changeset/auth-binding-lockstep.md b/.changeset/auth-binding-lockstep.md new file mode 100644 index 000000000..d62bae654 --- /dev/null +++ b/.changeset/auth-binding-lockstep.md @@ -0,0 +1,15 @@ +--- +'@cipherstash/stack': patch +'stash': patch +'@cipherstash/wizard': patch +--- + +Fix "Failed to load native binding" on project-local installs of the CLI/SDK +(npm). `@cipherstash/auth` was pinned at 0.41.0 while the six +`@cipherstash/auth-*` platform bindings declared in stack/stash/wizard's +optionalDependencies were pinned at 0.42.0. Because auth pins its bindings as +exact-version optional peer dependencies, the skew made npm nest per-consumer +binding copies that the hoisted `auth` package could not resolve — any command +or import touching auth then died at startup. All seven packages now move in +lockstep at 0.42.0, Dependabot is barred from bumping any of them +independently, and a supply-chain CI test fails on any future skew. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 94de5ad68..5e475458e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -37,6 +37,15 @@ updates: ignore: # Catalog-managed; bump manually via pnpm-workspace.yaml + changeset. - dependency-name: "@cipherstash/auth" + # The platform bindings MUST move in lockstep with @cipherstash/auth: + # auth pins them as exact-version optional peer deps, so a skewed set + # makes npm nest per-consumer binding copies the hoisted auth package + # cannot resolve, and every project-local install of the CLI/SDK dies + # with "Failed to load native binding". Dependabot once bumped these + # six to 0.42.0 while the ignored auth stayed 0.41.0 (the rc.2 B1 + # bug). Bump all seven catalog entries together, manually. Lockstep is + # enforced by e2e/tests/supply-chain.e2e.test.ts. + - dependency-name: "@cipherstash/auth-*" # Release-managed manually alongside stack releases. Grouped bumps are # actively harmful here: Dependabot's "group consistency" once upgraded # the sunsetting packages/protect off its 0.23.0 pin (0.24+ renames the diff --git a/e2e/tests/supply-chain.e2e.test.ts b/e2e/tests/supply-chain.e2e.test.ts index 71ffc526a..c387eefd5 100644 --- a/e2e/tests/supply-chain.e2e.test.ts +++ b/e2e/tests/supply-chain.e2e.test.ts @@ -87,6 +87,39 @@ describe('supply chain — pnpm configuration', () => { } }) + it('@cipherstash/auth and its six platform bindings are catalog-pinned in lockstep', () => { + // Not tidiness — a load-bearing invariant. @cipherstash/auth pins its + // bindings as EXACT-version optional peerDependencies, while stash / + // stack / wizard declare the bindings in their own optionalDependencies + // (pnpm doesn't auto-install optional peer deps). If the seven catalog + // entries skew, npm nests per-consumer binding copies that the hoisted + // auth package cannot resolve, and every project-local install of the + // CLI/SDK dies at startup with "Failed to load native binding". That is + // exactly what happened in 1.0.0-rc.2: Dependabot bumped the six + // bindings to 0.42.0 while the ignored @cipherstash/auth stayed 0.41.0. + // Dependabot now ignores all seven names; this test catches every other + // way the set can drift. + const ws = readYaml('pnpm-workspace.yaml') as { + catalogs?: Record> + } + const repo = ws.catalogs?.repo ?? {} + const authEntries = Object.entries(repo).filter( + ([name]) => + name === '@cipherstash/auth' || name.startsWith('@cipherstash/auth-'), + ) + // The wrapper + the six platform bindings. A count change means a + // binding was added/removed upstream — update the consumers' package + // JSONs and this expectation together. + expect(authEntries.length).toBe(7) + const versions = new Set(authEntries.map(([, v]) => v)) + expect( + versions.size, + `@cipherstash/auth* catalog entries have skewed versions: ${authEntries + .map(([n, v]) => `${n}@${v}`) + .join(', ')}`, + ).toBe(1) + }) + it('security overrides stay range-scoped and remain a small allowlist (≤12 entries)', () => { // Every override must be scoped to the advisory's vulnerable range // (`pkg@`), never a blanket `pkg` pin — a blanket pin silently diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b41ee80d3..3909af4d6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: repo: '@cipherstash/auth': - specifier: 0.41.0 - version: 0.41.0 + specifier: 0.42.0 + version: 0.42.0 '@cipherstash/auth-darwin-arm64': specifier: 0.42.0 version: 0.42.0 @@ -241,7 +241,7 @@ importers: dependencies: '@cipherstash/auth': specifier: catalog:repo - version: 0.41.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) + version: 0.42.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) '@cipherstash/migrate': specifier: workspace:* version: link:../migrate @@ -548,7 +548,7 @@ importers: version: 0.2.0 '@cipherstash/auth': specifier: catalog:repo - version: 0.41.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) + version: 0.42.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) '@cipherstash/protect-ffi': specifier: 0.29.0 version: 0.29.0 @@ -748,7 +748,7 @@ importers: version: 0.110.0(zod@3.25.76) '@cipherstash/auth': specifier: catalog:repo - version: 0.41.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) + version: 0.42.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0) '@clack/prompts': specifier: 1.7.0 version: 1.7.0 @@ -1041,15 +1041,15 @@ packages: cpu: [x64] os: [win32] - '@cipherstash/auth@0.41.0': - resolution: {integrity: sha512-jhhCthk+vilCQAvOfKYPSnIOZR2J3/+0JEYLNY3+M19rEzZ2ZmTLZDqzu4c+y1P73LgzTX+oWL6ESUoKsMd0TQ==} + '@cipherstash/auth@0.42.0': + resolution: {integrity: sha512-rPpYxOs/Xb1Bfen/yoGZZFwDWJhqQFO7Q9QJ6O5HcMt6THx2zVSQ6tQzH2liIhdNLsYFIj9S4HxmDJy2B0408g==} peerDependencies: - '@cipherstash/auth-darwin-arm64': 0.41.0 - '@cipherstash/auth-darwin-x64': 0.41.0 - '@cipherstash/auth-linux-arm64-gnu': 0.41.0 - '@cipherstash/auth-linux-x64-gnu': 0.41.0 - '@cipherstash/auth-linux-x64-musl': 0.41.0 - '@cipherstash/auth-win32-x64-msvc': 0.41.0 + '@cipherstash/auth-darwin-arm64': 0.42.0 + '@cipherstash/auth-darwin-x64': 0.42.0 + '@cipherstash/auth-linux-arm64-gnu': 0.42.0 + '@cipherstash/auth-linux-x64-gnu': 0.42.0 + '@cipherstash/auth-linux-x64-musl': 0.42.0 + '@cipherstash/auth-win32-x64-msvc': 0.42.0 peerDependenciesMeta: '@cipherstash/auth-darwin-arm64': optional: true @@ -4151,7 +4151,7 @@ snapshots: '@cipherstash/auth-win32-x64-msvc@0.42.0': optional: true - '@cipherstash/auth@0.41.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0)': + '@cipherstash/auth@0.42.0(@cipherstash/auth-darwin-arm64@0.42.0)(@cipherstash/auth-darwin-x64@0.42.0)(@cipherstash/auth-linux-arm64-gnu@0.42.0)(@cipherstash/auth-linux-x64-gnu@0.42.0)(@cipherstash/auth-linux-x64-musl@0.42.0)(@cipherstash/auth-win32-x64-msvc@0.42.0)': dependencies: '@byteslice/result': 0.3.0 optionalDependencies: @@ -5157,6 +5157,14 @@ snapshots: optionalDependencies: vite: 7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.23.0)(yaml@2.9.0) + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.23.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(@types/node@26.1.1)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.23.0)(yaml@2.9.0) + '@vitest/pretty-format@3.2.7': dependencies: tinyrainbow: 2.0.0 @@ -6723,7 +6731,7 @@ snapshots: dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.7 - '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.23.0)(yaml@2.9.0)) + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@26.1.1)(jiti@2.7.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.23.0)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.7 '@vitest/runner': 3.2.7 '@vitest/snapshot': 3.2.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4815fd022..77d5eacbb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,11 +7,17 @@ catalogs: repo: # @cipherstash/auth ships per-platform native bindings as optional # peerDependencies. pnpm does not auto-install platform-matched - # optional peer deps, so the consuming packages (wizard, cli) - # declare them as `optionalDependencies` via `catalog:repo`. Keep - # all seven entries on the same version so a single bump here moves - # everything in lockstep. - '@cipherstash/auth': 0.41.0 + # optional peer deps, so the consuming packages (stack, cli, wizard) + # declare them as `optionalDependencies` via `catalog:repo`. + # + # LOCKSTEP IS LOAD-BEARING, not tidiness: auth pins its bindings as + # EXACT-version optional peer deps, so a skewed set makes npm nest + # per-consumer binding copies that the hoisted auth package cannot + # resolve — every project-local install of the CLI/SDK then dies with + # "Failed to load native binding" (the 1.0.0-rc.2 B1 bug). Enforced by + # e2e/tests/supply-chain.e2e.test.ts; Dependabot ignores all seven + # names — bump them together, manually. + '@cipherstash/auth': 0.42.0 '@cipherstash/auth-darwin-arm64': 0.42.0 '@cipherstash/auth-darwin-x64': 0.42.0 '@cipherstash/auth-linux-arm64-gnu': 0.42.0