Skip to content

fix(stack,supabase,prisma): keep plaintext search terms out of errors and logs - #1554

Draft
cipherstash-agent wants to merge 2 commits into
mainfrom
claude/project-thread-uthgtw
Draft

cipherstash-agent wants to merge 2 commits into
mainfrom
claude/project-thread-uthgtw

Conversation

@cipherstash-agent

Copy link
Copy Markdown
Contributor

Requested by CipherStash · project thread

Summary

Search terms on encrypted columns are plaintext the caller chose to protect, but three error messages quoted them, and the Supabase adapter logged the full message at error level (the default STASH_STACK_LOG level). That broke the "plaintext is never logged" contract. Fixes CIP-5161.

Changes

  • matchNeedleError (shared by the Drizzle, Supabase, Prisma and core encryptQuery guards) reports the short needle's length, not the needle.
  • Wildcard errors in Supabase like/ilike and Prisma eqlMatch report the pattern's length, not the pattern. Supabase's was not in the issue; it had the same problem.
  • Error logs in the Supabase query catch and DynamoDB handleError now log the failure's kind instead of its message. Both catches see errors from every layer, and any of those messages can quote a value. The new shared describeFailure helper (exported from @cipherstash/stack/adapter-kit) produces the description: the error class, plus any type, code and authCode on the error or on a nested encryptionError. Each identifier must match ^[\w.-]{1,64}$, so a field that holds data is dropped. The full message is still returned to the caller.
  • Changeset: patch for @cipherstash/stack, @cipherstash/stack-supabase and @cipherstash/stack-prisma.

Verification

  • The regression tests were written first and failed on main for the expected reason (the term or the message appeared in the output):
    • stack/__tests__/match-needle-guard.test.ts: "never echoes the search term"
    • stack-prisma/test/v3/operator-lowering-v3-text-search.test.ts: "never echoes the rejected pattern"
    • stack-supabase/__tests__/no-plaintext-in-errors.test.ts: short needle, like pattern, and logged failure kind
    • stack/__tests__/dynamodb/resolve-decrypt.test.ts: "logs the failure kind and code, never the message"
  • stack/__tests__/describe-failure.test.ts covers the helper.
  • Unit suites:
    • stack-drizzle: all passing.
    • stack-prisma v3: all passing.
    • stack: everything passes except the tests that need the native protect-ffi binding, which isn't built in this container (cjs-require, match-bloom-live and the live-credential files).
    • stack-supabase: everything passes except supabase-declared-mode and supabase-v3-factory, which need the native @cipherstash/auth binding. Those same failures happen on unchanged main here.
  • Biome reports no errors on the changed files.

Review notes

  • I left the diagnostic trade-off in on purpose. A plain validation Error now logs as just Error, because there is no reliable way to tell a library message apart from one that quotes caller data. The caller still gets the full message on the returned error.
  • selector-path.ts's String(value) was raised in review and refuted. Only non-finite numbers (NaN, ±Infinity) reach that branch, and they carry no data.

Generated by Claude Code

… and logs

The free-text needle guard (matchNeedleError) quoted the rejected search
term, and the Supabase and Prisma wildcard errors quoted the rejected
pattern. Supabase's query-level catch then logged the full message at
error level, the default STASH_STACK_LOG level, so a short needle reached
production logs. Callers also commonly log thrown errors, and the Prisma
pattern can be arbitrarily long.

The three messages now report the term's length in codepoints instead of
the term. The Supabase catch logs only the failure's kind (error class,
plus the encryption error's type and code), since it sees errors from
every layer and any of their messages may quote a value. The full message
still reaches the caller in error.message.

Fixes CIP-5161.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Jmn7pT73uHWFP7RKEikuh
…DB too

Review follow-up. The DynamoDB adapter's handleError logged the full
error message at error level, the same leak the Supabase catch had: the
message can quote a plaintext value from any layer below.

The log-safe description moves to a shared describeFailure helper in
adapter-kit, used by both adapters. Besides the error class it now keeps
the fixed identifiers a failure carries (type, code, authCode, on the
error or a nested encryptionError), so a PostgREST code or a token
service refusal such as USAGE_LIMIT_EXCEEDED still reaches the log.
Each must look like an identifier, so a field holding data is dropped.

Also aligns the Supabase and Prisma wildcard error wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Jmn7pT73uHWFP7RKEikuh
@cipherstash-agent cipherstash-agent self-assigned this Nov 6, 2026
@coderabbitai

coderabbitai Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

@changeset-bot

changeset-bot Bot commented Nov 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ba272d9

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 Patch
@cipherstash/stack-supabase Patch
@cipherstash/stack-prisma Patch
@cipherstash/bench Patch
stash Patch
@cipherstash/stack-drizzle Patch
@cipherstash/test-kit Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-example Patch
@cipherstash/e2e Patch
@cipherstash/wizard 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

This branch has not been deployed

No deployments
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