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
9 changes: 9 additions & 0 deletions .changeset/prisma-next-upgrade-to-0-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@cipherstash/prisma-next": minor
---

Upgrade `@prisma-next/*` peer/runtime stack from `0.6.0-dev.8` to `0.8.0`.

`@prisma-next/sql-runtime@0.8` reordered the SQL execution pipeline so the `beforeExecute` middleware chain fires *before* `encodeParams`. `bulkEncryptMiddleware` now mutates params via `replaceValues(...)` ahead of encode, which means `CipherstashCellCodec.encode` is invoked with the wire-format string rather than the original `EncryptedEnvelopeBase`. The cell codec now short-circuits string values through unchanged; the envelope path is preserved for direct (non-runtime) callers such as the codec unit tests.

`SqlMiddlewareContext.scope` (`"runtime" | "connection" | "transaction"`) also became required in 0.8 (was optional in 0.7); test mocks now set `scope: 'runtime'` explicitly.
24 changes: 12 additions & 12 deletions examples/prisma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@
"dependencies": {
"@cipherstash/prisma-next": "workspace:*",
"@cipherstash/stack": "workspace:*",
"@prisma-next/adapter-postgres": "0.6.0-dev.8",
"@prisma-next/contract": "0.6.0-dev.8",
"@prisma-next/driver-postgres": "0.6.0-dev.8",
"@prisma-next/family-sql": "0.6.0-dev.8",
"@prisma-next/framework-components": "0.6.0-dev.8",
"@prisma-next/postgres": "0.6.0-dev.8",
"@prisma-next/sql-contract": "0.6.0-dev.8",
"@prisma-next/sql-contract-psl": "0.6.0-dev.8",
"@prisma-next/sql-orm-client": "0.6.0-dev.8",
"@prisma-next/sql-runtime": "0.6.0-dev.8",
"@prisma-next/target-postgres": "0.6.0-dev.8",
"@prisma-next/adapter-postgres": "0.8.0",
"@prisma-next/contract": "0.8.0",
"@prisma-next/driver-postgres": "0.8.0",
"@prisma-next/family-sql": "0.8.0",
"@prisma-next/framework-components": "0.8.0",
"@prisma-next/postgres": "0.8.0",
"@prisma-next/sql-contract": "0.8.0",
"@prisma-next/sql-contract-psl": "0.8.0",
"@prisma-next/sql-orm-client": "0.8.0",
"@prisma-next/sql-runtime": "0.8.0",
"@prisma-next/target-postgres": "0.8.0",
"dotenv": "^17.4.2"
},
"devDependencies": {
"@prisma-next/cli": "0.6.0-dev.8",
"@prisma-next/cli": "0.8.0",
"@types/node": "^22.15.12",
"pathe": "^2.0.3",
"tsx": "catalog:repo",
Expand Down
36 changes: 18 additions & 18 deletions packages/prisma-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,27 @@
},
"dependencies": {
"@cipherstash/stack": "workspace:*",
"@prisma-next/contract": "0.6.0-dev.8",
"@prisma-next/family-sql": "0.6.0-dev.8",
"@prisma-next/framework-components": "0.6.0-dev.8",
"@prisma-next/migration-tools": "0.6.0-dev.8",
"@prisma-next/sql-contract": "0.6.0-dev.8",
"@prisma-next/sql-operations": "0.6.0-dev.8",
"@prisma-next/sql-relational-core": "0.6.0-dev.8",
"@prisma-next/sql-runtime": "0.6.0-dev.8",
"@prisma-next/ts-render": "0.6.0-dev.8",
"@prisma-next/utils": "0.6.0-dev.8",
"@prisma-next/contract": "0.8.0",
"@prisma-next/family-sql": "0.8.0",
"@prisma-next/framework-components": "0.8.0",
"@prisma-next/migration-tools": "0.8.0",
"@prisma-next/sql-contract": "0.8.0",
"@prisma-next/sql-operations": "0.8.0",
"@prisma-next/sql-relational-core": "0.8.0",
"@prisma-next/sql-runtime": "0.8.0",
"@prisma-next/ts-render": "0.8.0",
"@prisma-next/utils": "0.8.0",
"arktype": "^2.1.29"
},
"devDependencies": {
"@prisma-next/adapter-postgres": "0.6.0-dev.8",
"@prisma-next/cli": "0.6.0-dev.8",
"@prisma-next/driver-postgres": "0.6.0-dev.8",
"@prisma-next/psl-parser": "0.6.0-dev.8",
"@prisma-next/sql-contract-psl": "0.6.0-dev.8",
"@prisma-next/sql-contract-ts": "0.6.0-dev.8",
"@prisma-next/sql-schema-ir": "0.6.0-dev.8",
"@prisma-next/target-postgres": "0.6.0-dev.8",
"@prisma-next/adapter-postgres": "0.8.0",
"@prisma-next/cli": "0.8.0",
"@prisma-next/driver-postgres": "0.8.0",
"@prisma-next/psl-parser": "0.8.0",
"@prisma-next/sql-contract-psl": "0.8.0",
"@prisma-next/sql-contract-ts": "0.8.0",
"@prisma-next/sql-schema-ir": "0.8.0",
"@prisma-next/target-postgres": "0.8.0",
"pathe": "^2.0.3",
"tsup": "catalog:repo",
"typescript": "catalog:repo",
Expand Down
22 changes: 13 additions & 9 deletions packages/prisma-next/src/execution/cell-codec-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,19 @@ export class CipherstashCellCodec<E extends EncryptedEnvelopeBase<unknown>> exte
}

async encode(value: E, _ctx: SqlCodecCallContext): Promise<unknown> {
// Two-pass write path: `lower`/`encodeParams` runs first and reaches
// this method with the envelope as the user authored it (plaintext
// set, ciphertext unset). We return the envelope as a sentinel; the
// bulk-encrypt middleware then runs in `beforeExecute`, stamps the
// ciphertext onto the envelope, and rewrites the param slot to the
// wire-format string via `params.replaceValues(...)` before the
// driver reads. See `../middleware/bulk-encrypt.ts` for the full
// flow. On the read side, `handle.ciphertext` is already set on
// arrival and encode short-circuits to the wire-format string.
// Two-pass write path. As of `@prisma-next/sql-runtime@0.8`, the
// `beforeExecute` middleware chain fires *before* `encodeParams`:
// `bulkEncryptMiddleware` runs, calls `params.replaceValues(...)` to
// swap each envelope for its wire-format composite-literal string,
// and the encoder then sees that string directly. Pass it through.
// (Prior to 0.8 the order was inverted: encode ran first, returned
// the envelope as a sentinel, then the middleware replaced the param
// before the driver read it. The branches below preserve that older
// contract for non-runtime callers — e.g. the codec unit tests that
// call `encode` directly with an envelope.)
if (typeof value === 'string') {
return value;
}
Comment on lines +144 to +146
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail closed on raw-string passthrough in encode.

Line 144 currently accepts any string unchanged. That should be constrained to the expected eql_v2_encrypted composite-literal shape; otherwise malformed/raw values can bypass the envelope-path safety checks.

Suggested fix
-    if (typeof value === 'string') {
-      return value;
-    }
+    if (typeof value === 'string') {
+      const trimmed = value.trim();
+      if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {
+        throw runtimeError(
+          'RUNTIME.ENCODE_FAILED',
+          `cipherstash ${this.descriptor.codecId}: expected middleware-produced eql_v2_encrypted composite literal, got raw string input.`,
+          {
+            codecId: this.descriptor.codecId,
+            reason: 'cipherstash-invalid-wire-literal',
+          },
+        );
+      }
+      return value;
+    }
🤖 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/prisma-next/src/execution/cell-codec-factory.ts` around lines 144 -
146, The encode branch in the encode function currently returns any string value
verbatim (checked via `if (typeof value === 'string') return value;`), which
allows raw/malformed strings to bypass envelope safety; change this to validate
that `value` matches the expected composite-literal shape
`eql_v2_encrypted(...)` (use a strict check/regex for the `eql_v2_encrypted`
envelope format) and only return the string if it passes that validation,
otherwise throw or return an error/encode-path fallback so raw strings cannot be
passed through unchecked; update the `encode` function's `value` handling
accordingly.

const handle = value.expose();
if (handle.ciphertext === undefined) {
// Misconfig diagnostic: when an SDK-bound codec sees a pre-encrypt
Expand Down
1 change: 1 addition & 0 deletions packages/prisma-next/test/abort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ function makeMiddlewareCtx(signal: AbortSignal | undefined): SqlMiddlewareContex
return {
contract: {} as Contract<SqlStorage>,
mode: 'strict',
scope: 'runtime',
now: () => Date.now(),
log: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
contentHash: async () => 'mock-hash',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ function createCtx(overrides?: Partial<SqlMiddlewareContext>): SqlMiddlewareCont
return {
contract: {} as Contract<SqlStorage>,
mode: 'strict' as const,
scope: 'runtime' as const,
now: () => Date.now(),
log: {
info: vi.fn(),
Expand Down
Loading
Loading