Skip to content

feat(stack): fall back to env credentials on the wasm-inline entry#801

Open
coderdan wants to merge 1 commit into
mainfrom
feat/wasm-env-credentials
Open

feat(stack): fall back to env credentials on the wasm-inline entry#801
coderdan wants to merge 1 commit into
mainfrom
feat/wasm-env-credentials

Conversation

@coderdan

@coderdan coderdan commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

The native entry resolves an omitted credential from CS_* env vars or ~/.cipherstash, inside protect-ffi. The WASM entry had no fallback at all, so every edge caller plumbed four values by hand — even in Deno, Node, and Bun, where the environment is right there.

config is now optional and each field falls back individually:

Field Environment variable
clientId CS_CLIENT_ID
clientKey CS_CLIENT_KEY
workspaceCrn CS_WORKSPACE_CRN
accessKey CS_CLIENT_ACCESS_KEY (then CS_ACCESS_KEY)
// Deno / Node / Bun, with the four CS_* vars set
const client = await Encryption({ schemas: [users] })

An explicit value always wins, so nothing changes for callers passing a full config today.

Why here and not in protect-ffi

The stack already depends on @cipherstash/auth, already builds AccessKeyStrategy for the access-key path, and already owns the config.authStrategy concept.

protect-ffi's wasm entry is raw wasm-bindgen output with no JS layer, and its auth seam is deliberately duck-typed ({ getToken }). Defaulting there would have meant adding a JS wrapper module and a hard dependency on @cipherstash/auth — inverting that seam for a fallback only this layer needs. Its only runtime dependency today is @neon-rs/load.

Three details that are the substance of the change

typeof process, not process?.env. This module is the edge entry. In a Worker or browser process is undeclared, so a bare read is a ReferenceError on client construction — and optional chaining does not help, since it still evaluates the identifier. The test deletes the global to reproduce that runtime honestly rather than asserting against an empty process.env. This is the same trap that broke the wasm entry on import in #798, so it is guarded from the start here; both process.env reads in the built dist/wasm-inline.js are typeof-guarded.

clientId / clientKey are a keypair, filled only when the environment supplies both — matching protect-ffi's native reader. Mixing a stale env CS_CLIENT_ID with a config clientKey would produce a mismatched client that fails inside ZeroKMS, well away from the cause.

accessKey is not filled on the strategy path. It is never on that arm and mutually exclusive with a strategy, so filling it from a stray env var would make resolveStrategy reject a config the caller wrote correctly.

Where it does not work, and why that is said out loud

Cloudflare Workers and browsers have no process.env. Workers hand their environment to the fetch handler instead. Missing-credential errors now name the field, the env var that would have supplied it, and — when there is no process — say the fallback is unavailable in this runtime and point at the Worker env argument. Without that, a Worker developer reads "set CS_CLIENT_ID" and reasonably tries exactly that, in a runtime where it can never be read.

Browser builds should not use the fallback at all. Vite and webpack commonly inline process.env at build time, which would bake the access key into client-side JavaScript. The skill now says so; pass a pre-built authStrategy instead.

Also

CS_CLIENT_ACCESS_KEY is read first because that is the name this repo documents and the entry's own example uses. CS_ACCESS_KEY is accepted after it because protect-ffi's native JS credential reader uses that spelling — both names are in circulation, and taking the documented one first keeps the docs true.

The existing strategy test asserted the old "both fields are required" message; errors now name only what is actually missing, so it is updated to the narrower assertion.

Verification

1059 tests pass (12 new); code:check clean; build clean; both process.env reads in the shipped bundle confirmed guarded.

Skill and changeset updated per AGENTS.md — the skill now shows the no-config form, the Worker env form, and the browser warning.

Related

Follows from cipherstash/protectjs-ffi#142 / #143, where strategy was renamed to authStrategy and the wasm option types were declared. This PR does not yet bump protect-ffi — the rename is backwards-compatible, so that can land with the version bump.

Summary by CodeRabbit

  • New Features

    • WASM encryption clients can now automatically fill missing credentials from supported environment variables.
    • Explicit configuration values take precedence over environment-provided values.
    • Configuration can be omitted when all required credentials are available through the environment.
    • Improved validation identifies missing credential fields and provides clearer guidance.
  • Documentation

    • Updated setup examples and added runtime guidance for Deno, Node, Bun, Cloudflare Workers, and browser builds.

The native entry resolves an omitted credential from `CS_*` env vars or
`~/.cipherstash`, inside protect-ffi. The WASM entry had no fallback, so
every edge caller plumbed four values by hand even in Deno, Node, and Bun
where the environment is right there.

`config` is now optional and each field falls back individually:
`clientId`/`CS_CLIENT_ID`, `clientKey`/`CS_CLIENT_KEY`,
`workspaceCrn`/`CS_WORKSPACE_CRN`, `accessKey`/`CS_CLIENT_ACCESS_KEY`.
An explicit value always wins, so nothing changes for callers passing a
full config.

This lives here rather than in protect-ffi deliberately. The stack already
depends on `@cipherstash/auth`, already builds `AccessKeyStrategy` for the
access-key path, and already owns the `config.authStrategy` concept.
protect-ffi's wasm entry is raw wasm-bindgen output with no JS layer, and
its auth seam is duck-typed (`{ getToken }`) — defaulting there would have
meant a new JS wrapper module plus a hard dependency on `@cipherstash/auth`,
inverting that seam for a fallback only this layer needs.

Three details that are the substance of the change:

- **`typeof process`, not `process?.env`.** This module is the edge entry.
  In a Worker or browser `process` is UNDECLARED, so a bare read is a
  `ReferenceError` on client construction, and optional chaining does not
  help — it still evaluates the identifier. A test deletes the global to
  reproduce that runtime honestly rather than asserting against an empty
  `process.env`. This is the same trap that broke the wasm entry on import
  in #798, so it is guarded from the start here.
- **`clientId`/`clientKey` are a keypair**, filled only when the environment
  supplies both — matching protect-ffi's native reader. Mixing a stale env
  `CS_CLIENT_ID` with a config `clientKey` would produce a mismatched client
  that fails inside ZeroKMS, well away from the cause.
- **`accessKey` is not filled on the strategy path.** It is `never` on that
  arm and mutually exclusive with a strategy, so filling it from a stray env
  var would make `resolveStrategy` reject a config the caller wrote
  correctly.

`CS_CLIENT_ACCESS_KEY` is read first because that is the name this repo
documents and the one the entry's own example uses; `CS_ACCESS_KEY` is
accepted after it because protect-ffi's native JS credential reader uses
that spelling, so both are in circulation.

Missing-credential errors now name only what is actually missing, the env
var that would have supplied it, and — when there is no `process` — say
that the fallback is unavailable in this runtime and point at the Worker
`env` argument. The existing strategy test asserted the old both-fields
message and is updated to the narrower one.

Verified: 1059 tests pass; `code:check` clean; both `process.env` reads in
the built `dist/wasm-inline.js` are `typeof`-guarded.

Skill and changeset updated — the skill now shows the no-config form, the
Worker `env` form, and warns against relying on the fallback in browser
builds, where bundlers inline `process.env` at build time and would ship
the access key to every visitor.
@coderdan
coderdan requested a review from a team as a code owner July 26, 2026 06:11
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e4013c5

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

This PR includes changesets to release 11 packages
Name Type
@cipherstash/stack Minor
@cipherstash/bench Patch
stash Minor
@cipherstash/prisma-next Minor
@cipherstash/stack-drizzle Minor
@cipherstash/stack-supabase Minor
@cipherstash/test-kit Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-next-example Patch
@cipherstash/e2e Patch
@cipherstash/wizard Minor

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

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The WASM inline client now fills missing credentials from process.env, supports optional configuration, validates unresolved fields, and preserves strategy-specific access-key behavior. Tests cover precedence and runtime cases, while documentation describes supported environments and browser or Workers usage.

Changes

WASM environment credential fallback

Layer / File(s) Summary
Credential contract and resolution
packages/stack/src/wasm-inline.ts
Credential fields and config become optional; withEnvCredentials resolves supported environment variables before strategy creation and client construction, with detailed missing-field errors.
Credential resolution validation
packages/stack/__tests__/wasm-inline-env-credentials.test.ts, packages/stack/__tests__/wasm-inline-strategy.test.ts
Tests verify precedence, credential-pair handling, access-key aliases, strategy behavior, missing-process compatibility, and granular errors.
Runtime guidance and release documentation
.changeset/wasm-env-credentials.md, skills/stash-encryption/SKILL.md
Documentation describes automatic environment loading, runtime constraints, and configuration guidance for Workers and browsers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: tobyhede

Sequence Diagram(s)

sequenceDiagram
  participant Encryption
  participant withEnvCredentials
  participant resolveStrategy
  participant wasmNewClient
  Encryption->>withEnvCredentials: Resolve missing credentials from process.env
  withEnvCredentials-->>Encryption: Return resolved configuration
  Encryption->>resolveStrategy: Resolve authentication strategy
  resolveStrategy-->>Encryption: Return strategy
  Encryption->>wasmNewClient: Create client with resolved clientId and clientKey
Loading
🚥 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 matches the main change: adding environment-variable credential fallback in the wasm-inline stack entry.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wasm-env-credentials

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.

@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

🧹 Nitpick comments (1)
packages/stack/src/wasm-inline.ts (1)

1411-1411: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unnecessary as never.

{} now satisfies WasmClientConfig, so this can retain type checking.

Proposed fix
-  const resolvedConfig = withEnvCredentials(clientConfig ?? ({} as never))
+  const resolvedConfig = withEnvCredentials(clientConfig ?? {})

As per coding guidelines, avoid type-erasing assertions in source unless deliberately suppressed with a reason.

🤖 Prompt for 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.

In `@packages/stack/src/wasm-inline.ts` at line 1411, Remove the unnecessary `as
never` assertion from the `clientConfig` fallback in the `resolvedConfig`
initialization, passing the empty object directly to `withEnvCredentials` so
normal type checking is preserved.

Source: Coding guidelines

🤖 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/wasm-inline.ts`:
- Around line 1710-1717: Update the missing accessKey diagnostic in
packages/stack/src/wasm-inline.ts lines 1710-1717 to mention both
CS_CLIENT_ACCESS_KEY and CS_ACCESS_KEY, matching the fallbacks accepted by
withEnvCredentials. Update the related regex assertions in
packages/stack/__tests__/wasm-inline-strategy.test.ts lines 156-173 to require
both supported environment variable names.

---

Nitpick comments:
In `@packages/stack/src/wasm-inline.ts`:
- Line 1411: Remove the unnecessary `as never` assertion from the `clientConfig`
fallback in the `resolvedConfig` initialization, passing the empty object
directly to `withEnvCredentials` so normal type checking is preserved.
🪄 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 Plus

Run ID: d57e7fc9-f2ff-423e-a84f-9c92e2401c75

📥 Commits

Reviewing files that changed from the base of the PR and between b48494c and e4013c5.

📒 Files selected for processing (5)
  • .changeset/wasm-env-credentials.md
  • packages/stack/__tests__/wasm-inline-env-credentials.test.ts
  • packages/stack/__tests__/wasm-inline-strategy.test.ts
  • packages/stack/src/wasm-inline.ts
  • skills/stash-encryption/SKILL.md

Comment on lines +1710 to +1717
const missing = [
!cfg.workspaceCrn && '`config.workspaceCrn` (or `CS_WORKSPACE_CRN`)',
!cfg.accessKey && '`config.accessKey` (or `CS_CLIENT_ACCESS_KEY`)',
].filter(Boolean)
throw new Error(
'[encryption]: `config.workspaceCrn` and `config.accessKey` are required when no auth strategy is provided.',
`[encryption]: ${missing.join(' and ')} ${
missing.length > 1 ? 'are' : 'is'
} required when no auth strategy is provided.${envHint()}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Mention the supported CS_ACCESS_KEY fallback in the error.

withEnvCredentials accepts both access-key variables, but this error only suggests CS_CLIENT_ACCESS_KEY, contrary to the supported fallback contract.

  • packages/stack/src/wasm-inline.ts#L1710-L1717: include CS_ACCESS_KEY alongside CS_CLIENT_ACCESS_KEY in the missing accessKey diagnostic.
  • packages/stack/__tests__/wasm-inline-strategy.test.ts#L156-L173: update the regexes to assert both accepted access-key environment variables.
📍 Affects 2 files
  • packages/stack/src/wasm-inline.ts#L1710-L1717 (this comment)
  • packages/stack/__tests__/wasm-inline-strategy.test.ts#L156-L173
🤖 Prompt for 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.

In `@packages/stack/src/wasm-inline.ts` around lines 1710 - 1717, Update the
missing accessKey diagnostic in packages/stack/src/wasm-inline.ts lines
1710-1717 to mention both CS_CLIENT_ACCESS_KEY and CS_ACCESS_KEY, matching the
fallbacks accepted by withEnvCredentials. Update the related regex assertions in
packages/stack/__tests__/wasm-inline-strategy.test.ts lines 156-173 to require
both supported environment variable names.

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.

1 participant