Skip to content

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

Merged
coderdan merged 11 commits into
mainfrom
feat/stack-protect-ffi-025
Jun 30, 2026
Merged

feat(stack): protect-ffi 0.26.0 + auth 0.39 OidcFederationStrategy (stacked on #496)#497
coderdan merged 11 commits into
mainfrom
feat/stack-protect-ffi-025

Conversation

@coderdan

@coderdan coderdan commented May 29, 2026

Copy link
Copy Markdown
Contributor

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.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added strategy-based, end-user identity-bound encryption via OidcFederationStrategy.
    • Encryption can be configured with config.strategy to drive ZeroKMS authentication.
    • Lock-context binding now supports passing identity claims directly via .withLockContext({ identityClaim }) (plain input or LockContext).
  • Documentation

    • Updated identity-aware encryption guidance/examples to match the strategy + identity-claim flow.
    • Workspace configuration now uses CS_WORKSPACE_CRN as the single source of truth (WASM/edge setup updated accordingly).
  • Deprecations

    • LockContext.identify() and LockContext.getLockContext() remain deprecated.
  • Chores / Tests

    • Updated WASM/CI, E2E tests, and examples to require CS_WORKSPACE_CRN; minor auth/protect-ffi bumps.

@coderdan coderdan requested a review from a team as a code owner May 29, 2026 08:45
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Updates auth and encryption flows to use strategy-based initialization, synchronous lock-context resolution, and workspaceCrn-based configuration across runtime code, tests, docs, and CI.

Changes

OIDC Strategy and Lock-Context Migration

Layer / File(s) Summary
Lock-context normalization
packages/stack/src/identity/index.ts, packages/stack/src/types.ts, packages/stack/src/types-public.ts, packages/stack/src/index.ts, packages/stack/src/encryption/index.ts
Adds LockContextInput and resolveLockContext(), exposes identityContext, deprecates identify() and getLockContext(), adds AuthStrategy to public types, re-exports auth strategies, and passes config.strategy through client initialization.
Operation lock-context flow
packages/stack/src/encryption/operations/*.ts, packages/stack/src/encryption/helpers/model-helpers.ts
Updates encryption and decryption operations to accept LockContextInput, resolve it locally, and call protect-ffi without serviceToken across encrypt, decrypt, model, bulk, query, and batch-query paths.
WASM auth strategy and workspace CRN
packages/stack/src/wasm-inline.ts
Requires workspaceCrn in WASM config, widens the auth strategy union to include OIDC federation, re-exports auth strategies, updates client creation, and derives access-key strategy configuration from the CRN.
Test fixtures and runtime coverage
packages/stack/__tests__/fixtures/index.ts, packages/stack/__tests__/init-strategy.test.ts, packages/stack/__tests__/lock-context-wiring.test.ts, packages/stack/__tests__/encrypt-query*.test.ts, packages/stack/__tests__/lock-context.test.ts, packages/stack/__tests__/resolve-lock-context.test.ts, packages/stack/__tests__/wasm-inline-strategy.test.ts, packages/stack/__tests__/auth-reexports.test.ts
Reworks lock-context fixtures, adds strategy-forwarding coverage, and updates query, WASM, and identity-bound tests to use direct identityClaim binding and workspaceCrn.
E2E, CI, examples, and release docs
e2e/wasm/*, .github/workflows/tests.yml, examples/supabase-worker/*, pnpm-workspace.yaml, README.md, AGENTS.md, .changeset/*, package.json
Updates WASM package versions, CI workspace CRN checks, example environment setup, Edge Function validation, README and AGENTS guidance, and the changeset note to reflect the strategy and CRN migration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • cipherstash/stack#350: Also updates the workspace @cipherstash/auth version set in workspace metadata.
  • cipherstash/stack#493: Touches the same encryption operation code paths and lock-context execution flow inside packages/stack/src/encryption/operations/*.
  • cipherstash/stack#496: Introduces the WASM-inline entry point that this PR further updates for workspaceCrn, strategy unions, and auth re-exports.

Suggested reviewers

  • calvinbrewer
  • tobyhede

Poem

🐇 Hop hop, the old token’s out of sight,
identityClaim binds the key just right.
workspaceCrn hums where region used to stay,
and OidcFederationStrategy bounces in to play. 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the stack auth/protect-ffi upgrade and OidcFederationStrategy work, even though it cites auth 0.39 instead of 0.40.
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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.

@changeset-bot

changeset-bot Bot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fa7b656

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

@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 coderdan requested a review from Copilot June 8, 2026 09:06

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.

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.

Comment thread packages/stack/src/wasm-inline.ts
Base automatically changed from feat/stack-wasm-inline to main June 19, 2026 13:22
@auxesis auxesis requested review from auxesis and freshtonic and removed request for a team June 23, 2026 23:55
coderdan added 6 commits June 24, 2026 11:39
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.
…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
…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).
@coderdan coderdan force-pushed the feat/stack-protect-ffi-025 branch from 1efdd03 to a23fe7e Compare June 24, 2026 01:57
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/cipherstash/stack/issues/comments/4572736076","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cipherstash/stack/pull/497?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: defaults\n> \n> **Review profile**: CHILL\n> \n> **Plan**: Pro\n> \n> **Run ID**: `f4cfb06e-dd68-43d7-ae11-abd864deb732`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between df9c8e33c00b87e7b19886eb79aeed9805e21134 and a23fe7e3defa17d4e20d1d55a7cf6fe6656c2ba2.\n> \n> </details>\n> \n> <details>\n> <summary>⛔ Files ignored due to path filters (1)</summary>\n> \n> * `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (34)</summary>\n> \n> * `.changeset/stack-protect-ffi-0-26-oidc-strategy.md`\n> * `.github/workflows/tests.yml`\n> * `AGENTS.md`\n> * `README.md`\n> * `e2e/wasm/deno.json`\n> * `e2e/wasm/roundtrip.test.ts`\n> * `examples/supabase-worker/.env.example`\n> * `examples/supabase-worker/README.md`\n> * `examples/supabase-worker/supabase/functions/cipherstash-roundtrip/index.ts`\n> * `packages/stack/__tests__/encrypt-query-searchable-json.test.ts`\n> * `packages/stack/__tests__/encrypt-query.test.ts`\n> * `packages/stack/__tests__/fixtures/index.ts`\n> * `packages/stack/__tests__/init-strategy.test.ts`\n> * `packages/stack/__tests__/lock-context-wiring.test.ts`\n> * `packages/stack/__tests__/lock-context.test.ts`\n> * `packages/stack/package.json`\n> * `packages/stack/src/encryption/helpers/model-helpers.ts`\n> * `packages/stack/src/encryption/index.ts`\n> * `packages/stack/src/encryption/operations/batch-encrypt-query.ts`\n> * `packages/stack/src/encryption/operations/bulk-decrypt-models.ts`\n> * `packages/stack/src/encryption/operations/bulk-decrypt.ts`\n> * `packages/stack/src/encryption/operations/bulk-encrypt-models.ts`\n> * `packages/stack/src/encryption/operations/bulk-encrypt.ts`\n> * `packages/stack/src/encryption/operations/decrypt-model.ts`\n> * `packages/stack/src/encryption/operations/decrypt.ts`\n> * `packages/stack/src/encryption/operations/encrypt-model.ts`\n> * `packages/stack/src/encryption/operations/encrypt-query.ts`\n> * `packages/stack/src/encryption/operations/encrypt.ts`\n> * `packages/stack/src/identity/index.ts`\n> * `packages/stack/src/index.ts`\n> * `packages/stack/src/types-public.ts`\n> * `packages/stack/src/types.ts`\n> * `packages/stack/src/wasm-inline.ts`\n> * `pnpm-workspace.yaml`\n> \n> </details>\n> \n> ```ascii\n>  ____________________________________________________________________________\n> < I've got bills to pay, so I'm gonna find, find, find those bugs every day. >\n>  ----------------------------------------------------------------------------\n>   \\\n>    \\   (\\__/)\n>        (•ㅅ•)\n>        /   づ\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>📝 Generate docstrings</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> Create stacked PR\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> Commit on current branch\n\n</details>\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `feat/stack-protect-ffi-025`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=cipherstash/stack&utm_content=497)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stack/src/encryption/operations/batch-encrypt-query.ts`:
- Around line 207-209: The resolveLockContext call is positioned outside the
withResult wrapper, which allows resolution errors to escape instead of being
caught and returned as a failure Result. Move the
resolveLockContext(this.lockContext) call from before the withResult statement
into the callback function that is passed to withResult, so that any errors
during lock context resolution are properly wrapped and returned as { failure }
according to the Result contract required by the encryption operations
guidelines.

In `@packages/stack/src/encryption/operations/encrypt-query.ts`:
- Around line 151-153: Move the resolveLockContext(this.lockContext) call inside
the withResult callback to ensure backward-compatible LockContext resolution
errors are properly caught and converted to the Result contract shape { failure
}. The context resolution should happen as the first step within the async
callback passed to withResult, matching the pattern used in other migrated
encryption operations in this codebase. This ensures all potential failures are
wrapped in the Result contract rather than rejecting before withResult can
handle them.

In `@packages/stack/src/identity/index.ts`:
- Around line 66-67: The OIDC example using OidcFederationStrategy.create() at
lines 66–67 uses the outdated signature with separate region and workspaceId
parameters. Update the example to use the current signature where workspaceCrn
is passed as the first parameter instead of region and workspaceId, while
keeping the callback function () => getJwt() as the second parameter to match
the `@cipherstash/auth` v0.39.0+ API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4cfb06e-dd68-43d7-ae11-abd864deb732

📥 Commits

Reviewing files that changed from the base of the PR and between df9c8e3 and a23fe7e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (34)
  • .changeset/stack-protect-ffi-0-26-oidc-strategy.md
  • .github/workflows/tests.yml
  • AGENTS.md
  • README.md
  • e2e/wasm/deno.json
  • e2e/wasm/roundtrip.test.ts
  • examples/supabase-worker/.env.example
  • examples/supabase-worker/README.md
  • examples/supabase-worker/supabase/functions/cipherstash-roundtrip/index.ts
  • packages/stack/__tests__/encrypt-query-searchable-json.test.ts
  • packages/stack/__tests__/encrypt-query.test.ts
  • packages/stack/__tests__/fixtures/index.ts
  • packages/stack/__tests__/init-strategy.test.ts
  • packages/stack/__tests__/lock-context-wiring.test.ts
  • packages/stack/__tests__/lock-context.test.ts
  • packages/stack/package.json
  • packages/stack/src/encryption/helpers/model-helpers.ts
  • packages/stack/src/encryption/index.ts
  • packages/stack/src/encryption/operations/batch-encrypt-query.ts
  • packages/stack/src/encryption/operations/bulk-decrypt-models.ts
  • packages/stack/src/encryption/operations/bulk-decrypt.ts
  • packages/stack/src/encryption/operations/bulk-encrypt-models.ts
  • packages/stack/src/encryption/operations/bulk-encrypt.ts
  • packages/stack/src/encryption/operations/decrypt-model.ts
  • packages/stack/src/encryption/operations/decrypt.ts
  • packages/stack/src/encryption/operations/encrypt-model.ts
  • packages/stack/src/encryption/operations/encrypt-query.ts
  • packages/stack/src/encryption/operations/encrypt.ts
  • packages/stack/src/identity/index.ts
  • packages/stack/src/index.ts
  • packages/stack/src/types-public.ts
  • packages/stack/src/types.ts
  • packages/stack/src/wasm-inline.ts
  • pnpm-workspace.yaml

Comment thread packages/stack/src/encryption/operations/batch-encrypt-query.ts Outdated
Comment thread packages/stack/src/encryption/operations/encrypt-query.ts Outdated
Comment thread packages/stack/src/identity/index.ts Outdated

@auxesis auxesis 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.

Test-coverage review — PR #497 (protect-ffi 0.26 / auth 0.39, strategy-based lock context)

Verdict: Strong test coverage for the core change. The new lock-context-wiring.test.ts exhaustively asserts that every operation forwards identityClaim and never re-introduces serviceToken, and init-strategy.test.ts covers config.strategy forwarding well. Two gaps are clearly worth closing: the PR's headline auth-strategy re-exports have no test guarding the deliberate ESM workaround, and the new central resolveLockContext branch has no direct unit test that distinguishes a constructed claim from the default.

No crypto/security concerns to flag — the change removes the per-operation CTS token entirely and moves auth onto the client strategy, which is a design decision reviewed elsewhere.

Additional coverage gaps not posted inline

  • packages/stack/src/identity/index.ts:187getLockContext()'s contract changed: the guard that threw when no CTS token was set has been removed, and ctsToken may now be undefined. No test asserts the new non-throwing behaviour. It's a deprecated method (low priority), but a cheap regression test would lock it down: const r = await new LockContext().getLockContext(); expect(r.failure).toBeUndefined(); expect(r.data.ctsToken).toBeUndefined() (with CS_WORKSPACE_CRN set).
  • packages/stack/src/wasm-inline.ts:374 — the WASM Encryption accessKey + workspaceCrn path now builds AccessKeyStrategy.create(cfg.workspaceCrn, …) (region derived from the CRN, replacing the old region field). This is only covered by the gated Deno e2e (e2e/wasm/roundtrip.test.ts), which skips without real CS_* secrets. There's no offline unit test — mirroring wasm-inline-normalize.test.ts — asserting the CRN reaches AccessKeyStrategy.create (or that strategy + accessKey together still throw).
  • packages/stack/__tests__/lock-context-wiring.test.ts — the plain { identityClaim } input (as opposed to a LockContext instance) is only asserted for encrypt/decrypt; the model/bulk/query operations are exercised only with a LockContext. Low risk since they all funnel through resolveLockContext, but the alternate-input axis is lopsided across the 11 operations.

Comment thread packages/stack/src/index.ts
Comment thread packages/stack/src/identity/index.ts

@auxesis auxesis 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.

Looks good, thanks @coderdan.

I have left some comments about test coverage to be addressed before merge.

…egy takes workspaceCrn

Picks up the CRN-based OidcFederationStrategy that shipped in
@cipherstash/auth 0.40.0 (cipherstash-suite#2049, CIP-3201): create()
now takes a single workspaceCrn instead of (region, workspaceId),
matching AccessKeyStrategy. Simplifies the strategy wiring uniformly.

- catalog: @cipherstash/auth* 0.39.0 → 0.40.0 (+ lockfile)
- lock-context test passes the CRN straight through; drops the local
  region/workspace-id splitter
- update OidcFederationStrategy.create examples (node + wasm-inline) and
  the changeset to the workspaceCrn signature
@coderdan

Copy link
Copy Markdown
Contributor Author

Bumped @cipherstash/auth 0.39 → 0.40.0 (commit 7e4358e). 0.40 ships the CRN-based OidcFederationStrategy from CIP-3201 (cipherstash-suite#2049, merged), so create() now takes a single workspaceCrn instead of (region, workspaceId) — matching AccessKeyStrategy. Updated the lock-context test (passes the CRN straight through, drops the region/workspace-id splitter), the OidcFederationStrategy.create examples, and the changeset.

For context: the standalone CIP-3245 PR (#528) and a LockContext follow-up issue (#529) were opened against this work and then closed as redundant — this PR already covers both.

Code-review caught three trailing inconsistencies after the
0.39 → 0.40 catalog bump:

- e2e/wasm/deno.json: the Deno import map still pinned
  @cipherstash/auth@0.39.0/wasm-inline, so the WASM E2E job (the only
  CI that executes the auth WASM path) was resolving 0.39, not the
  0.40 this PR ships. Bumped to 0.40.0.
- package.json: the duplicate `workspaces.catalogs.repo` block (read by
  npm/Bun, ignored by pnpm) still pinned @cipherstash/auth 0.35.0,
  contradicting pnpm-workspace.yaml. Synced the auth entry to 0.40.0.
  (The block's other entries are independently stale — pre-existing,
  left for a separate cleanup.)
- changeset: reworded "0.40 adds OidcFederationStrategy" — the strategy
  was added in 0.39; 0.40 reworked its constructor to take a
  workspaceCrn.
Add offline unit tests for the surfaces auxesis flagged as untested:

- auth-reexports.test.ts: assert the four strategy re-exports from
  @cipherstash/stack resolve to the real @cipherstash/auth binding,
  guarding the deliberate ESM default-import workaround in src/index.ts
  (a naive `export { X } from` goes undefined under Node ESM).
- resolve-lock-context.test.ts: directly unit-test resolveLockContext
  (the synchronous funnel that replaced the old getLockContext() flow) so
  a regression returning the default ['sub'] instead of the constructed
  claim is caught; plus a regression for getLockContext()'s now
  non-throwing, ctsToken-optional contract.
- wasm-inline-strategy.test.ts: export resolveStrategy and assert the
  workspace CRN reaches AccessKeyStrategy.create, an explicit strategy is
  used verbatim, and strategy+accessKey still throw — the access-key path
  was only covered by the gated Deno e2e.
- lock-context-wiring.test.ts: add plain { identityClaim } cases for the
  per-payload and per-query placement shapes.

No production behaviour change (resolveStrategy gains an `export`).

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stack/__tests__/wasm-inline-strategy.test.ts`:
- Around line 61-70: The current test for resolveStrategy only asserts the
mutual-exclusion error, so it can still pass even if AccessKeyStrategy.create()
is invoked first. Update the test around resolveStrategy in
wasm-inline-strategy.test.ts to also assert that strategy construction is not
reached when both accessKey and strategy are present, using a spy/mock on
AccessKeyStrategy.create or an equivalent construction path and verifying it is
never called before the error is thrown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e426a816-3098-42fc-a766-dc49f9e1e8d4

📥 Commits

Reviewing files that changed from the base of the PR and between d3f3a3f and b8fbfc6.

📒 Files selected for processing (5)
  • packages/stack/__tests__/auth-reexports.test.ts
  • packages/stack/__tests__/lock-context-wiring.test.ts
  • packages/stack/__tests__/resolve-lock-context.test.ts
  • packages/stack/__tests__/wasm-inline-strategy.test.ts
  • packages/stack/src/wasm-inline.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/stack/tests/auth-reexports.test.ts
  • packages/stack/tests/resolve-lock-context.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/stack/tests/lock-context-wiring.test.ts
  • packages/stack/src/wasm-inline.ts

Comment thread packages/stack/__tests__/wasm-inline-strategy.test.ts
…trategy

WasmClientConfig required workspaceCrn unconditionally, but the strategy
path never reads it — resolveStrategy only consumes workspaceCrn on the
access-key arm (to build AccessKeyStrategy), and a pre-built strategy
(e.g. OidcFederationStrategy.create(workspaceCrn, …)) already encapsulates
the CRN and region. Move workspaceCrn into the discriminated-union arms:
required with accessKey, optional with strategy. This also makes the OIDC
JSDoc example (which omits workspaceCrn) typecheck, resolving the Copilot
review comment on the example. No runtime change.
…ery ops

encrypt-query and batch-encrypt-query resolved the lock context before the
withResult wrapper, unlike the other 8 migrated operations which resolve it
inside the callback. Move the resolveLockContext call inside so any failure
is captured as a { failure } Result, matching the rest and the Result-contract
guideline. resolveLockContext is synchronous today so this is a no-op at
runtime — it's consistency + defense-in-depth.

Also strengthen the wasm-inline resolveStrategy mutual-exclusion test to assert
AccessKeyStrategy.create is never called, proving the guard short-circuits
before strategy construction.

Addresses the remaining CodeRabbit comments on #497.
coderdan added a commit that referenced this pull request Jun 30, 2026
Reflects the auth changes in #497: replace the deprecated
LockContext.identify() ceremony with client-level OidcFederationStrategy
+ .withLockContext({ identityClaim }), which makes every OIDC provider
(Clerk, Supabase, Auth0, Okta) first-class.
@coderdan coderdan merged commit 93fc5f9 into main Jun 30, 2026
9 checks passed
@coderdan coderdan deleted the feat/stack-protect-ffi-025 branch June 30, 2026 09:59
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.

3 participants