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
15 changes: 15 additions & 0 deletions .changeset/auth-binding-lockstep.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions e2e/tests/supply-chain.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, Record<string, string>>
}
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@<range>`), never a blanket `pkg` pin — a blanket pin silently
Expand Down
38 changes: 23 additions & 15 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 11 additions & 5 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading