Skip to content

feat(stack): protect-ffi 0.26.0 + auth 0.39 OidcFederationStrategy (stacked on #496)#497

Open
coderdan wants to merge 6 commits into
feat/stack-wasm-inlinefrom
feat/stack-protect-ffi-025
Open

feat(stack): protect-ffi 0.26.0 + auth 0.39 OidcFederationStrategy (stacked on #496)#497
coderdan wants to merge 6 commits into
feat/stack-wasm-inlinefrom
feat/stack-protect-ffi-025

Conversation

@coderdan
Copy link
Copy Markdown
Contributor

@coderdan coderdan commented May 29, 2026

Stacked on top of #496 (feat/stack-wasm-inline) — review/merge that first; this PR's base is the #496 branch, not main.

Supersedes the earlier 0.25.0 work: bumps to @cipherstash/protect-ffi@0.26.0 and @cipherstash/auth@0.39.0, and uses the new OidcFederationStrategy to replace the lock-context token ceremony with a simpler, strategy-based approach for identity-bound encryption.

1. Version bumps

  • @cipherstash/protect-ffi 0.25.00.26.0. The public TypeScript API is identical to 0.25.0 (verified by diffing the published lib/index.d.cts); 0.26.0 is internal fixes only (per-isolate Neon Channel cleanup, try_catch around the JS getToken).
  • @cipherstash/auth catalog (and the six platform entries) 0.38.00.39.0, which adds OidcFederationStrategy.
  • e2e/wasm/deno.json pins + pnpm-lock.yaml regenerated.

2. Strategy-based, identity-bound encryption (replaces the ceremony)

protect-ffi 0.25 removed the per-operation serviceToken, which left the old LockContext ceremony half-broken: identify() fetched a CTS token the operations no longer sent, so the request authenticated as the service while identityClaim asked ZeroKMS to bind to a user it couldn't verify.

OidcFederationStrategy and identityClaim compose: the strategy federates the end user's OIDC JWT into a CTS token at the client level (so requests authenticate as the user), and identityClaim still selects which claim ZeroKMS bakes into the data-key tag.

import { Encryption, OidcFederationStrategy } from "@cipherstash/stack"

const client = await Encryption({
  schemas: [users],
  config: { strategy: OidcFederationStrategy.create(region, workspaceId, () => getUserJwt()) },
})

await client
  .encrypt("alice@example.com", { column: users.email, table: users })
  .withLockContext({ identityClaim: ["sub"] })
  • .withLockContext() now accepts a plain { identityClaim } (or a LockContext) and resolves the claim synchronously — no CTS token, no identify() call.
  • LockContext.identify() / getLockContext() are deprecated (kept for back-compat); the client strategy handles token acquisition.
  • This is a non-breaking minor: omit config.strategy and existing credential/env behaviour is unchanged; existing .withLockContext(lockContext) call sites still compile.

3. Strategy re-exports

  • OidcFederationStrategy, AccessKeyStrategy, AutoStrategy, DeviceSessionStrategy re-exported from @cipherstash/stack; OidcFederationStrategy / AccessKeyStrategy from @cipherstash/stack/wasm-inline (+ the wasm config.strategy type broadened to accept either). Integrators no longer need a separate @cipherstash/auth install.
  • @cipherstash/stack now declares the @cipherstash/auth-<platform> packages as optionalDependencies@cipherstash/auth ships them as optional peer deps (not auto-installed), so this is required for the re-exported Node strategies to resolve their native binding for consumers.

Also updated

  • lock-context-wiring.test.ts — asserts both a LockContext and a plain { identityClaim } forward identityClaim, and serviceToken is still never sent.
  • init-strategy.test.ts — adds an OidcFederationStrategy-shaped forwarding case.
  • lock-context.test.ts — live (USER_JWT-gated) round-trip rewritten to use OidcFederationStrategy + .withLockContext({ identityClaim }), confirming per-user binding.
  • JSDoc (Encryption(), LockContext, ClientConfig.strategy), AGENTS.md, README.md, changeset.

⚠️ Worth confirming in a live CTS round-trip that per-user binding behaves as intended under 0.26 — the offline tests guard the wiring; the live test guards the server-side semantics when USER_JWT is supplied.

coderdan added 2 commits May 29, 2026 18:44
protect-ffi 0.25.0 is a breaking release for both entries:

WASM (@cipherstash/stack/wasm-inline):
- newClient(strategy, opts) -> newClient(opts) with strategy nested.
- Config takes a workspaceCrn instead of region; the AccessKeyStrategy
  region is derived from the CRN (crn:<region>:<workspace-id>). CS_REGION
  is no longer consulted; set CS_WORKSPACE_CRN.

Node:
- serviceToken removed from the encrypt/decrypt/query option types (and
  the CtsToken export). The per-operation CTS token is no longer
  forwarded; lock contexts still travel as lockContext.identityClaim.
  Public LockContext/identify() API is unchanged.

Adds offline lock-context wiring tests (mock protect-ffi) asserting every
operation forwards identityClaim and never sends serviceToken, plus
extractRegionFromCrn unit tests. Updates the Deno e2e test, Supabase
example, and wasm-e2e CI job to CS_WORKSPACE_CRN.
protect-ffi 0.25 lets newClient take an AuthStrategy (any
{ getToken(): Promise<{ token }> } object). Expose it on the Node
Encryption client via config.strategy: when supplied, getToken() is
invoked on every ZeroKMS request, taking precedence over the
credentials-derived default (clientKey is still used for encryption).
Omitting it preserves existing credentials/env behaviour.

Kept on init (rather than a separate initWithStrategy) so a future
keyProvider option can land in the same config. AuthStrategy is
re-exported from @cipherstash/stack for consumers to type their own.
@coderdan coderdan requested a review from a team as a code owner May 29, 2026 08:45
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 692c2586-9ad2-4297-b2cc-27ec7fc1d334

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stack-protect-ffi-025

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 1efdd03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@cipherstash/stack Minor
@cipherstash/bench Patch
@cipherstash/prisma-next Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-next-example Patch
@cipherstash/e2e Patch

Not sure what this means? Click here to learn what changesets are.

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

…lace lock-context ceremony

Supersedes the 0.25.0 bump with protect-ffi 0.26.0 (API-identical; internal
fixes only) and @cipherstash/auth 0.39.0, and uses the new
OidcFederationStrategy to replace the lock-context token ceremony with a
strategy-based approach for identity-bound encryption.

- bump @cipherstash/protect-ffi 0.25.0 -> 0.26.0; @cipherstash/auth catalog
  (and platform entries) 0.38.0 -> 0.39.0; e2e/wasm/deno.json pins; lockfile
- .withLockContext() now accepts a plain { identityClaim } (or a LockContext)
  and resolves the claim synchronously — no CTS token, no identify() call
- deprecate LockContext.identify() / getLockContext(); the client strategy
  (OidcFederationStrategy) now handles user token acquisition
- re-export OidcFederationStrategy/AccessKeyStrategy/AutoStrategy/
  DeviceSessionStrategy from @cipherstash/stack, and the strategies from
  @cipherstash/stack/wasm-inline
- broaden the wasm-inline config strategy type to accept OidcFederationStrategy
- declare @cipherstash/auth platform optionalDependencies (auth ships them as
  optional peer deps, not auto-installed) so the re-exported Node strategies
  resolve their native binding for consumers
- update wiring/init/live tests, JSDoc, AGENTS.md, README, changeset
@coderdan coderdan changed the title feat(stack): protect-ffi 0.25.0 + auth strategy option (stacked on #496) feat(stack): protect-ffi 0.26.0 + auth 0.39 OidcFederationStrategy (stacked on #496) Jun 8, 2026
coderdan added 3 commits June 8, 2026 16:30
…Dependencies

The optionalDependencies block added to packages/stack/package.json was not
reflected in pnpm-lock.yaml, breaking `pnpm install --frozen-lockfile` in CI.
@cipherstash/auth 0.39 changed AccessKeyStrategy.create(region, accessKey)
to AccessKeyStrategy.create(workspaceCrn, accessKey) — it derives the region
from the CRN itself. The wasm-inline resolveStrategy still passed a derived
region, so the Deno WASM e2e failed with 'Invalid CRN: <region>'. Pass the
CRN directly and drop the now-obsolete extractRegionFromCrn helper + tests.
(OidcFederationStrategy.create still takes region + workspaceId.)
…ext tests

- index.ts: @cipherstash/auth's Node entry is CJS with `module.exports =
  { ...native }`; the spread defeats cjs-module-lexer so a static
  `export { AccessKeyStrategy } from` throws 'Named export not found' under
  real Node ESM (the E2E cli failure). Default-import the module (which is
  module.exports at runtime, all names present) and re-export each binding
  explicitly, with instance types for the strategy classes.
- encrypt-query / encrypt-query-searchable-json tests + fixtures: the ops no
  longer call getLockContext(); .withLockContext() takes a plain
  { identityClaim }. createMockLockContext() now returns that shape; dropped
  the getLockContext spy assertions and the obsolete failure / null-context
  cases (resolveLockContext is synchronous and cannot fail).
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR upgrades Stack’s Protect/Auth dependencies and updates the identity-bound encryption flow to match protect-ffi ≥0.25’s removal of per-operation serviceToken, moving authentication to a client-level config.strategy (notably via OidcFederationStrategy) while keeping lock context as a pure { identityClaim } value.

Changes:

  • Bump @cipherstash/protect-ffi to 0.26.0 and @cipherstash/auth to 0.39.0, updating workspace config to use workspaceCrn consistently (including /wasm-inline).
  • Replace the lock-context “token ceremony” with a synchronous lock-context resolution (.withLockContext({ identityClaim }) or LockContext), and stop forwarding serviceToken in all operations.
  • Re-export auth strategies from @cipherstash/stack (and selected ones from /wasm-inline), add wiring tests to ensure identityClaim is forwarded and serviceToken is never sent.

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates identity-aware encryption docs to the strategy-based approach.
pnpm-workspace.yaml Bumps @cipherstash/auth* catalog versions to 0.39.0.
pnpm-lock.yaml Regenerated lockfile reflecting new auth/protect versions and optional deps.
packages/stack/src/wasm-inline.ts Switches WASM config to workspaceCrn, supports OIDC strategy, updates protect-ffi wasm newClient call shape, and re-exports strategies.
packages/stack/src/types.ts Re-exports protect-ffi AuthStrategy type and adds ClientConfig.strategy.
packages/stack/src/types-public.ts Exposes AuthStrategy in public type surface.
packages/stack/src/index.ts Re-exports auth strategies from @cipherstash/auth in an ESM-compatible way.
packages/stack/src/identity/index.ts Adds LockContextInput + resolveLockContext, deprecates old token ceremony methods, makes CTS token optional in response type.
packages/stack/src/encryption/operations/encrypt.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/operations/encrypt-query.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/operations/encrypt-model.ts Uses synchronous lock-context resolution and passes Context through model helpers.
packages/stack/src/encryption/operations/decrypt.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/operations/decrypt-model.ts Uses synchronous lock-context resolution and passes Context through model helpers.
packages/stack/src/encryption/operations/bulk-encrypt.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/operations/bulk-encrypt-models.ts Uses synchronous lock-context resolution and passes Context through model helpers.
packages/stack/src/encryption/operations/bulk-decrypt.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/operations/bulk-decrypt-models.ts Uses synchronous lock-context resolution and passes Context through model helpers.
packages/stack/src/encryption/operations/batch-encrypt-query.ts Uses synchronous lock-context resolution; removes serviceToken forwarding.
packages/stack/src/encryption/index.ts Plumbs optional config.strategy into protect-ffi newClient and documents identity-bound usage.
packages/stack/src/encryption/helpers/model-helpers.ts Removes CTS token plumbing from model bulk encrypt/decrypt helpers.
packages/stack/package.json Bumps protect-ffi and adds optionalDependencies for auth platform binaries.
packages/stack/tests/lock-context.test.ts Rewrites live identity-bound tests to use OidcFederationStrategy + { identityClaim }.
packages/stack/tests/lock-context-wiring.test.ts Adds offline mocks ensuring identityClaim is forwarded and serviceToken is never sent.
packages/stack/tests/init-strategy.test.ts Adds tests verifying config.strategy is forwarded to protect-ffi newClient.
packages/stack/tests/fixtures/index.ts Simplifies lock-context fixtures to a plain { identityClaim } input.
packages/stack/tests/encrypt-query.test.ts Updates lock-context tests to use plain { identityClaim } input and removes CTS-token-failure cases.
packages/stack/tests/encrypt-query-searchable-json.test.ts Updates lock-context tests to use plain { identityClaim } input and removes CTS-token-failure cases.
examples/supabase-worker/supabase/functions/cipherstash-roundtrip/index.ts Updates example config to use CS_WORKSPACE_CRN (no CS_REGION).
examples/supabase-worker/README.md Updates env setup instructions to include CS_WORKSPACE_CRN.
examples/supabase-worker/.env.example Updates example env file to use CS_WORKSPACE_CRN and remove CS_REGION.
e2e/wasm/roundtrip.test.ts Updates WASM e2e to require/use CS_WORKSPACE_CRN and drop explicit region.
e2e/wasm/deno.json Pins protect/auth wasm-inline imports to 0.26.0 / 0.39.0.
AGENTS.md Updates guidance to the strategy-based identity-aware encryption flow.
.github/workflows/tests.yml Exposes CS_WORKSPACE_CRN to wasm e2e job and asserts it’s present.
.changeset/stack-protect-ffi-0-26-oidc-strategy.md Adds release notes for the dependency bumps and new identity-bound strategy flow.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/stack/tests/lock-context.test.ts:145

  • This test tries to assert that decrypting without the lock context fails, but decryptModel() returns a Result (it doesn’t throw). As written, the try/catch never runs and the test will pass without asserting anything. Assert on the returned Result’s failure instead.
    try {
      await protectClient.decryptModel(encryptedModel.data)
    } catch (error) {
      const e = error as Error
      expect(e.message.startsWith('Failed to retrieve key')).toEqual(true)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* "ap-southeast-2.aws", workspaceId, () => getClerkSessionToken(req),
* { store: cookieStore({ request: req, responseHeaders }) },
* )
* const client = await Encryption({ schemas, config: { strategy, clientId, clientKey } })
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.

2 participants