diff --git a/.changeset/pre.json b/.changeset/pre.json index cdaa6fd7..99333559 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -27,7 +27,10 @@ "cli-anonymous-telemetry", "cli-eql-v3-single-bundle", "codex-skills-eperm", + "drizzle-encrypted-indexes", "drizzle-kit-eql-v3-ddl", + "dynamodb-eql-v3", + "eql-baked-digest-verified", "eql-migration-command", "eql-v3-adapter-type-robustness", "eql-v3-bigint-domains", @@ -59,26 +62,32 @@ "khaki-pugs-play", "migrate-eql-v3", "plan-complete-rollout-yes", + "plan-honest-outcome", "prisma-example-eql-v3", "prisma-next-0-14", + "prisma-next-0-16", "prisma-next-drop-encrypted-prefix", "prisma-next-eql-runtime-source", "prisma-next-joins-release-train", + "prisma-next-migrate-command", "prisma-next-v3-only-install", "protect-ffi-030-json-selectors", "release-train-coupling", "remove-legacy-drizzle-package", "remove-secrets-leftovers", "rename-db-install-to-eql-install", + "schema-build-v3-domain-picker", "schema-stevec-standard-pin", "skills-eql-v3-accuracy", "skills-identity-docs-refresh", "stack-1-0-0-rc", "stack-adapter-kit", + "stack-drizzle-readme", "stash-cli-eql-v3-default", "stash-cli-skill-refresh", "stash-drizzle-skill-encrypt-query", "stash-env-mint-credentials", + "stash-indexing-skill", "stash-prisma-next-skill-v3-only", "stash-prisma-next-skill", "stash-skills-contains-to-matches", @@ -89,9 +98,12 @@ "supabase-or-string-parser", "supabase-v3-json-querying", "supabase-v3-order-by-ope-term", + "v3-payload-routing", "v3-supabase-needle-lockcontext-errors", "wasm-encrypt-query", "wasm-inline-bulk-ops", + "wasm-inline-model-helpers", + "wild-donkeys-shave", "wizard-allow-env-templates", "wizard-analytics-privacy", "wizard-joins-release-train" diff --git a/e2e/CHANGELOG.md b/e2e/CHANGELOG.md index fbe4d300..025341b4 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,21 @@ # @cipherstash/e2e +## 0.0.3-rc.5 + +### Patch Changes + +- Updated dependencies [d26950d] +- Updated dependencies [d6bc9e9] +- Updated dependencies [b7fa61f] +- Updated dependencies [b7fa61f] +- Updated dependencies [f78fd7a] +- Updated dependencies [239f79b] +- Updated dependencies [d25d100] +- Updated dependencies [f628463] + - @cipherstash/stack@1.0.0-rc.5 + - stash@1.0.0-rc.5 + - @cipherstash/wizard@1.0.0-rc.5 + ## 0.0.3-rc.4 ### Patch Changes diff --git a/e2e/package.json b/e2e/package.json index 7fd7205e..e95ba8bc 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/e2e", - "version": "0.0.3-rc.4", + "version": "0.0.3-rc.5", "private": true, "description": "End-to-end tests that exercise built CipherStash binaries and cross-package behaviour.", "type": "module", diff --git a/examples/basic/CHANGELOG.md b/examples/basic/CHANGELOG.md index 8b590e67..74b19d1b 100644 --- a/examples/basic/CHANGELOG.md +++ b/examples/basic/CHANGELOG.md @@ -1,5 +1,17 @@ # @cipherstash/basic-example +## 1.2.14-rc.5 + +### Patch Changes + +- Updated dependencies [239f79b] +- Updated dependencies [d26950d] +- Updated dependencies [ba706cb] +- Updated dependencies [d25d100] + - @cipherstash/stack-drizzle@1.0.0-rc.5 + - @cipherstash/stack@1.0.0-rc.5 + - @cipherstash/stack-supabase@1.0.0-rc.5 + ## 1.2.14-rc.4 ### Patch Changes diff --git a/examples/basic/package.json b/examples/basic/package.json index 1c1de60b..0f8fccb4 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/basic-example", "private": true, - "version": "1.2.14-rc.4", + "version": "1.2.14-rc.5", "type": "module", "scripts": { "start": "tsx index.ts" diff --git a/examples/prisma/CHANGELOG.md b/examples/prisma/CHANGELOG.md index 54f5e51b..6418f9ae 100644 --- a/examples/prisma/CHANGELOG.md +++ b/examples/prisma/CHANGELOG.md @@ -1,5 +1,18 @@ # @cipherstash/prisma-next-example +## 0.1.0-rc.5 + +### Patch Changes + +- Updated dependencies [d26950d] +- Updated dependencies [b7fa61f] +- Updated dependencies [b7fa61f] +- Updated dependencies [b7fa61f] +- Updated dependencies [b7fa61f] +- Updated dependencies [d25d100] + - @cipherstash/stack@1.0.0-rc.5 + - @cipherstash/prisma-next@1.0.0-rc.5 + ## 0.1.0-rc.4 ### Patch Changes diff --git a/examples/prisma/package.json b/examples/prisma/package.json index cab49036..b85a7422 100644 --- a/examples/prisma/package.json +++ b/examples/prisma/package.json @@ -1,7 +1,7 @@ { "name": "@cipherstash/prisma-next-example", "private": true, - "version": "0.1.0-rc.4", + "version": "0.1.0-rc.5", "description": "End-to-end example of @cipherstash/prisma-next: searchable application-layer encryption for Postgres with Prisma Next, using @cipherstash/stack as the SDK.", "type": "module", "scripts": { diff --git a/packages/bench/CHANGELOG.md b/packages/bench/CHANGELOG.md index 7ebbbed0..327e0aa9 100644 --- a/packages/bench/CHANGELOG.md +++ b/packages/bench/CHANGELOG.md @@ -1,5 +1,16 @@ # @cipherstash/bench +## 0.0.5-rc.5 + +### Patch Changes + +- Updated dependencies [239f79b] +- Updated dependencies [d26950d] +- Updated dependencies [ba706cb] +- Updated dependencies [d25d100] + - @cipherstash/stack-drizzle@1.0.0-rc.5 + - @cipherstash/stack@1.0.0-rc.5 + ## 0.0.5-rc.4 ### Patch Changes diff --git a/packages/bench/package.json b/packages/bench/package.json index 1aa5b847..12e40ec4 100644 --- a/packages/bench/package.json +++ b/packages/bench/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/bench", - "version": "0.0.5-rc.4", + "version": "0.0.5-rc.5", "private": true, "description": "Performance / index-engagement benchmarks for stack integrations (Drizzle, encryptedSupabase, Prisma).", "type": "module", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0df96276..395ab2d5 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,178 @@ # @cipherstash/cli +## 1.0.0-rc.5 + +### Minor Changes + +- 239f79b: New bundled agent skill: `stash-indexing` — how to index EQL v3 encrypted + columns. Integrations that were otherwise correct shipped with no index on any + encrypted predicate because nothing in the installed skills said encrypted + columns _can_ be indexed (#753). The skill covers the functional-index recipes + over the term extractors (`eql_v3.eq_term` / `ord_term` / `ord_term_ore` / + `match_term` / `to_ste_vec_query`) mapped to the `types.*` domains, what works + without superuser on Supabase and managed Postgres versus the ORE opclass + restriction, which domains are storage-only by design, the query shapes that + engage an index (`ORDER BY` sort-key and `GROUP BY` traps), building indexes on + large tables, an `EXPLAIN` verification checklist, and when to create indexes + during an encryption rollout (after backfill, before switching reads). + + `stash init` / `stash impl` handoffs — and the `@cipherstash/wizard` skills + prompt — now install it for **every** integration (Drizzle, Supabase, Prisma + Next, plain PostgreSQL) — the gap is cross-cutting. + The existing per-integration skills gained pointers to it (including the + missing `stash-prisma-next` one-line purpose in the setup prompt, which + previously rendered "(no description)"). + +### Patch Changes + +- d26950d: `encryptedDynamoDB` now accepts EQL v3 tables. + + Pass a table built with `encryptedTable` + the `types.*` domains from + `@cipherstash/stack/v3` (or `@cipherstash/stack/eql/v3`) to any of + `encryptModel`, `bulkEncryptModels`, `decryptModel`, `bulkDecryptModels`. Both + the typed client from `EncryptionV3` and the nominal client from + `Encryption({ config: { eqlVersion: 3 } })` are accepted. + + EQL v2 tables continue to work unchanged — this is additive, and no existing + caller needs to change. The table decides which wire format is used, so a + DynamoDB table populated under one version must keep being read with that + version. + + This fixes a latent bug that made v3 unusable: the write path detected an + encrypted value by its `k: 'ct'` tag, but EQL v3 scalars carry no `k` + discriminator at all. Every v3 scalar fell through to the nested-object branch + and was written as a raw map instead of being split into `__source` and + `__hmac`. + + Notes on capability: + + - Only equality is usable on DynamoDB. `__hmac` is written for domains + that mint an `hm` term — the `*Eq` family, plus `TextOrd`/`TextOrdOre`/ + `TextSearch`. Ordering and bloom-filter terms have no DynamoDB query surface + and are not stored, so those columns remain decryptable but not queryable. + - Nested attributes are supported in v3. There is no nested-group authoring + form (that is a compile error), so declare the column flat with a dotted + path — `{ 'profile.ssn': types.TextEq('profile.ssn') }`. The model is + matched by dotted path, so `{ profile: { ssn } }` resolves, and the nested + attribute keeps its `__hmac` for key conditions. + - Audit metadata on `decryptModel` / `bulkDecryptModels` requires the nominal + client; the `EncryptionV3` client has no audit surface on decrypt. + + The DynamoDB adapter also gains its first test coverage — across the v2 and v3 + paths, where it previously had none. + + Robustness, from review: + + - Passing a v3 table to a client that never registered it (one built for a + different schema set, so it is not in v3 mode for that table) now throws a + clear, actionable error naming the table, instead of failing opaquely deep in + the FFI. + - A malformed decrypt result from a non-conforming client is surfaced as a + failure rather than resolving as a silent `undefined` success. + - Reading back a `__source` attribute that matches no declared column now + logs a debug diagnostic instead of silently returning the raw ciphertext. + - Caller input that cannot be structurally cloned no longer reaches the FFI by + reference — the "encryption never mutates a caller's object" guarantee holds + on that path too. + - The write path now splits only declared columns, matched on the same property + path the read path rebuilds from. A pre-encrypted payload placed under an + undeclared nested name is stored whole (and round-trips) instead of being + split into a `__source` the read path could never reassemble. + - A degenerate payload with an empty-string ciphertext is split like any other + ciphertext rather than falling through and being written as a raw map, which + had leaked its `v`/`i` envelope metadata into storage. + - Arrays are documented as a deliberate carve-out: the mapping does not descend + into them, so a payload inside a list is stored whole (still decryptable, but + not queryable and not part of the `__source`/`__hmac` layout). + + The v3 overloads are strongly typed. `encryptModel` / `bulkEncryptModels` check + the input model against the table's column domains, and return the DynamoDB + attribute map that is actually written — the new exported `EncryptedAttributes` + type, where a declared column `email` becomes `email__source` (plus + `email__hmac` for the equality domains that mint one) rather than surviving as + `email`. `decryptModel` / `bulkDecryptModels` invert it via `DecryptedAttributes`. + `AnyEncryptedTable`, `DynamoDBEncryptionClient` and `AuditConfig` are now + exported from `@cipherstash/stack/dynamodb` so these signatures can be named. + The EQL v2 overloads are unchanged. + +- d6bc9e9: `stash plan` now reports the outcome that actually occurred instead of unconditionally printing `Plan drafted at .cipherstash/plan.md` and exiting 0 (#738). The plan file is written by the handed-off agent, so the command verifies it on disk after the handoff: "Plan drafted" appears only when the file exists; if a launched agent (Claude Code, Codex, or the wizard) exits without writing it, `plan` errors and exits non-zero so automation never proceeds against a plan that was never created; deferred handoffs (`--target agents-md`, or a CLI target that isn't installed) end with an honest "No plan drafted yet" hint; and a pre-existing plan the run didn't modify is reported as left unchanged rather than drafted. An unexpected filesystem error while reading the plan path (a locked or malformed `.cipherstash/`) now exits non-zero with a clear message rather than an opaque crash. +- b7fa61f: Upgrade the Prisma Next integration to Prisma Next 0.16. + + All `@prisma-next/*` dependencies move from `0.14.0` to `0.16.0`, in lockstep. The + CipherStash encryption surface is unchanged — column types, envelopes, the `eql*` + operators, `cipherstashFromStack`, and every subpath export behave exactly as before. + + **Action required in your `prisma-next.config.ts`:** Prisma Next 0.15 stopped + materialising a placeholder namespace, so authoring a SQL contract now requires the + target's namespace factory. Add `createNamespace` to your `prismaContract(...)` call: + + ```typescript + import { postgresCreateNamespace } from '@prisma-next/target-postgres/types' + + contract: prismaContract('./prisma/schema.prisma', { + output: 'src/prisma/contract.json', + target: postgresPack, + createNamespace: postgresCreateNamespace, + }), + ``` + + Without it, `prisma-next contract emit` fails at runtime with `createNamespace is +not a function`. The bundled `stash-prisma-next` skill documents this too. + + The bundled EQL v3 baseline migration is re-emitted so its label and hash reflect + the pinned `@cipherstash/eql` 3.0.2 (the committed artifact still said 3.0.0). + + Re-run `prisma-next contract emit` after upgrading. The regenerated + `contract.{json,d.ts}` picks up the 0.15/0.16 shape changes — the namespace + discriminator becomes the target-specific `'postgres-schema'` (was + `'sql-namespace'`), emit adds the `StorageColumnTypes` / `StorageColumnInputTypes` + maps and the `scalarList` capability marker, and foreign keys and their backing + indexes become discrete contract entities. Your contract's `storageHash` is + unaffected by the upgrade itself. + +- b7fa61f: Fix the wrong `prisma-next migration apply` command name in the Prisma Next + guidance. Prisma Next has no `migration apply` subcommand — the apply verb is the + top-level `prisma-next migrate` (`migration` only has `plan`/`new`/`show`/ + `status`/`log`/`list`/`graph`/`check`). The stale name appeared in the + `stash-prisma-next` and `stash-cli` skills, the `@cipherstash/prisma-next` + README, and — user-visibly — in `stash init --prisma-next`'s printed next-steps, + the `stash init` flag help, and the `stash eql install` Prisma-Next refusal + message, all of which now say `prisma-next migrate`. Surfaced by the rc.4 + skilltester run (found independently at Prisma Next 0.14.0, confirmed at 0.16.0). +- f78fd7a: `stash schema build` now picks a concrete EQL v3 domain per column + (`TextSearch`, `IntegerOrd`, `TextEq`, …) instead of the legacy v2 + "searchable capabilities" toggle. Boolean columns are assigned the + storage-only `types.Boolean` domain automatically, while JSON columns are + assigned the queryable `types.Json` domain, with encrypted containment and + selector queries. Other columns default to the widest searchable domain, + matching the previous behaviour. The internal `SearchOp` capability tuple + and the `v3DomainFactory` translation shim are removed, unblocking EQL v2 + removal (#707, #751). +- d25d100: `@cipherstash/stack/wasm-inline` now has the model helpers: `encryptModel` / `decryptModel` and `bulkEncryptModels` / `bulkDecryptModels` (#742). They run the same schema traversal as the native entry (shared code, so the two entries cannot drift on which fields get encrypted): declared columns are encrypted — matched by JS property name, nested fields via the column's dotted path — everything else passes through, and `null`/`undefined` fields are preserved without reaching ZeroKMS. A call that encrypts (or decrypts) at least one field is one ZeroKMS round trip regardless of how many fields or models it covers; a `null`/empty batch, or one whose models carry no schema fields, returns without contacting ZeroKMS at all. `types.Date`/`types.Timestamp` columns round-trip `Date` → `Date` (ISO strings on the wire), and failures follow this entry's `{ data } | { failure }` Result contract, with decrypt failures naming every failing field by its model path. Edge code no longer needs the hand-written `bulkEncrypt` field mapping whose failure mode was a schema column silently persisted in plaintext. + + The shared model traversal is also hardened: it no longer mutates the caller's model (previously a nested-column decrypt wrote decrypted plaintext back into the caller's input, and encrypt overwrote it with ciphertext); a literal flat dotted key, a `__proto__`-shaped key, or a non-object model element is handled safely instead of crashing, leaking plaintext, or reaching `Object.prototype`; an already-encrypted field is passed through rather than re-encrypted; and an invalid `Date` is rejected per field. On the WASM entry, model ops now validate the table against the client's schemas, `Date` values are normalized at every encrypt/query crossing (not just the model path), and a `null`/empty model batch returns `{ data: [] }`. The skills update ships in the `stash` tarball, hence the `stash` patch. + +- f628463: Fix invalid DDL when a Drizzle column changes to an EQL v3 domain. + + `drizzle-kit generate` emits an in-place `ALTER TABLE … ALTER COLUMN … SET DATA TYPE` + when a plaintext column is changed to an encrypted one, which Postgres rejects — there + is no cast from `text`/`numeric` to an EQL type, and on drizzle-kit 0.31.0+ the emitted + type name is additionally mangled to `"undefined"."eql_v3_"`. The migration + rewriter only recognised the EQL v2 type, so a v3 user was left with an un-runnable + migration and nothing to repair it. + + The rewriter now matches the whole `eql_v3_*` domain family alongside `eql_v2_encrypted`, + across every mangled form observed from drizzle-kit 0.24 through 0.31, and emits the + matched domain in the replacement instead of a hardcoded v2 type. `stash eql migration +--drizzle` — the EQL v3 migration-first path — now runs the same sweep that `eql install +--drizzle` has always run, so the repair actually reaches v3 projects. + + The rewrite's guidance comment now also warns that it drops the plaintext column in the + same migration, and points at the staged `stash encrypt` path (add → backfill → cutover → + drop) for populated production tables. + + - @cipherstash/migrate@1.0.0-rc.1 + ## 1.0.0-rc.4 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 8f1c36b9..642bd30b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "stash", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "CipherStash CLI — the one stash command for auth, init, encryption schema, database setup, and secrets.", "repository": { "type": "git", diff --git a/packages/prisma-next/CHANGELOG.md b/packages/prisma-next/CHANGELOG.md index b2163725..dc7a8575 100644 --- a/packages/prisma-next/CHANGELOG.md +++ b/packages/prisma-next/CHANGELOG.md @@ -1,5 +1,126 @@ # @cipherstash/prisma-next +## 1.0.0-rc.5 + +### Minor Changes + +- b7fa61f: Bake the EQL v3 install SQL into the migration artefacts at emit time, + digest-verified against the `@cipherstash/eql` release manifest — replacing + runtime injection, whose environment-dependent migration hashes broke consumer + repos on every EQL version bump. + + **Why.** Prisma Next's migration framework treats migration packages as + immutable, content-addressed artefacts: the CLI seed phase copies an + extension's packages into the app's `migrations/cipherstash/` only when + missing, and `db init` / `db update` apply from those on-disk files without + loading the extension descriptor. The previous design injected the installed + `@cipherstash/eql`'s SQL at descriptor build and recomputed the migration hash + from it, so the migration's identity changed with the installed EQL version — + every bump orphaned the vendored copies (`PN-MIG-5002`), and the hash could + never detect SQL tampering because it was recomputed from whatever content was + present. + + **Now.** Each migration's self-emit embeds the SQL read from the pinned + `@cipherstash/eql`, refusing to emit bytes whose sha256 does not match the + release manifest's `installSqlSha256`. One content-addressed identity flows + from this repo's git history through the npm tarball into the consumer's + vendored copy and the database ledger; `verifyMigrationHash` re-checks the + bytes on every read, so editing the vendored SQL fails loudly before anything + executes. A repo test pins the committed artefact's SQL digest to the installed + manifest, so an EQL bump without a matching new upgrade migration fails CI. + EQL upgrades ship as new append-only migration directories carrying fresh + invariants (the existing `upgrade_eql_v3_3_0_2` pattern); published directories + are never rewritten. + + **One-time exception.** This PR itself re-emits the baseline migration's bytes + and hash (`35fc9000…` → `2c873907…`) as it moves off the runtime-injection + scheme. That is a deliberate, one-time transition — the "never rewrite a + published directory" invariant it introduces holds strictly for every release + after this one, enforced by the frozen-hash CI pins. + + **Action required (rc consumers only).** If your project's + `migrations/cipherstash/` was generated by an earlier rc, delete that directory + and re-run `prisma-next migration plan` (or `migrate`) — the seed phase + regenerates it byte-identical to the shipped artefacts. Databases keep their + markers; already-applied invariants are not re-run. + +- b7fa61f: Upgrade the Prisma Next integration to Prisma Next 0.16. + + All `@prisma-next/*` dependencies move from `0.14.0` to `0.16.0`, in lockstep. The + CipherStash encryption surface is unchanged — column types, envelopes, the `eql*` + operators, `cipherstashFromStack`, and every subpath export behave exactly as before. + + **Action required in your `prisma-next.config.ts`:** Prisma Next 0.15 stopped + materialising a placeholder namespace, so authoring a SQL contract now requires the + target's namespace factory. Add `createNamespace` to your `prismaContract(...)` call: + + ```typescript + import { postgresCreateNamespace } from '@prisma-next/target-postgres/types' + + contract: prismaContract('./prisma/schema.prisma', { + output: 'src/prisma/contract.json', + target: postgresPack, + createNamespace: postgresCreateNamespace, + }), + ``` + + Without it, `prisma-next contract emit` fails at runtime with `createNamespace is +not a function`. The bundled `stash-prisma-next` skill documents this too. + + The bundled EQL v3 baseline migration is re-emitted so its label and hash reflect + the pinned `@cipherstash/eql` 3.0.2 (the committed artifact still said 3.0.0). + + Re-run `prisma-next contract emit` after upgrading. The regenerated + `contract.{json,d.ts}` picks up the 0.15/0.16 shape changes — the namespace + discriminator becomes the target-specific `'postgres-schema'` (was + `'sql-namespace'`), emit adds the `StorageColumnTypes` / `StorageColumnInputTypes` + maps and the `scalarList` capability marker, and foreign keys and their backing + indexes become discrete contract entities. Your contract's `storageHash` is + unaffected by the upgrade itself. + +- b7fa61f: Route v3 decoding from the EQL payload's own identifier, so encrypted columns + decode on paths that carry no column context. + + Every EQL v3 payload carries a required `i` identifier (`{"t": "", "c": +""}`), and ZeroKMS commits the cell's key to it — a payload relocated to + a different column cannot decrypt. That makes the identifier the authoritative + routing source, and it travels with the value rather than with the query. + + The v3 cell codec previously took its `(table, column)` routing key only from the + SQL runtime's projected-column context, which meant two paths failed even though + the value knew exactly where it came from: + + - **Relation `include()`** — the ORM decodes cells nested in a `json_agg` / + `json_build_object` document through `decodeJson`, whose framework signature + passes only the JSON value, with no column ref. This previously threw + `decodeJson is not supported; envelopes do not round-trip through JSON`. + Included encrypted columns now decode into ordinary envelopes that + `decryptAll` batches alongside top-level ones. + - **Aggregates and computed projections** — the runtime resolves no column ref + and deliberately passes `column: undefined`, so `decode` threw. It now routes + from the payload. + + `decode` reads the payload identifier first and falls back to the projected-column + context for a value carrying no usable identifier (a non-v3 or malformed + document), so existing well-routed reads are unaffected. `encodeJson` is + unchanged: it still renders the opaque `$encrypted*` marker, and is deliberately + not the inverse of `decodeJson` — the two serve different planes. + +### Patch Changes + +- b7fa61f: Fix the wrong `prisma-next migration apply` command name in the Prisma Next + guidance. Prisma Next has no `migration apply` subcommand — the apply verb is the + top-level `prisma-next migrate` (`migration` only has `plan`/`new`/`show`/ + `status`/`log`/`list`/`graph`/`check`). The stale name appeared in the + `stash-prisma-next` and `stash-cli` skills, the `@cipherstash/prisma-next` + README, and — user-visibly — in `stash init --prisma-next`'s printed next-steps, + the `stash init` flag help, and the `stash eql install` Prisma-Next refusal + message, all of which now say `prisma-next migrate`. Surfaced by the rc.4 + skilltester run (found independently at Prisma Next 0.14.0, confirmed at 0.16.0). +- Updated dependencies [d26950d] +- Updated dependencies [d25d100] + - @cipherstash/stack@1.0.0-rc.5 + ## 1.0.0-rc.4 ### Minor Changes diff --git a/packages/prisma-next/package.json b/packages/prisma-next/package.json index 9c335aa2..af789ad3 100644 --- a/packages/prisma-next/package.json +++ b/packages/prisma-next/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/prisma-next", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "license": "MIT", "author": "CipherStash ", "description": "CipherStash extension for Prisma Next: searchable application-layer field-level encryption for Postgres (EQL v3), with domain-typed encrypted columns, the eql* query operators, bulk encrypt/decrypt middleware, and a baseline migration that installs the EQL v3 bundle.", diff --git a/packages/stack-drizzle/CHANGELOG.md b/packages/stack-drizzle/CHANGELOG.md index 6a2662e5..cc79e49d 100644 --- a/packages/stack-drizzle/CHANGELOG.md +++ b/packages/stack-drizzle/CHANGELOG.md @@ -1,5 +1,43 @@ # @cipherstash/stack-drizzle +## 1.0.0-rc.5 + +### Minor Changes + +- 239f79b: New `encryptedIndexes` helper on the `/v3` entry: spread + `...encryptedIndexes(t)` in `pgTable`'s third-argument callback and it derives + the recommended functional indexes for every encrypted column in the table — + named `
__`, tracked by `drizzle-kit generate` like + any other index. The mapping comes from the same per-domain capability record + the operator layer gates on, so the emitted indexes and the operators that + engage them cannot drift: equality → btree on `eql_v3.eq_term`, ordering → + btree on `eql_v3.ord_term` (on the numeric/date/timestamp `_ord` domains one + index serves `=` and range — their injective ordering term answers equality + and no `eq_term` overload exists; the non-injective `text_ord` / `text_ord_ore` + also carry `hm` and get an `eq_term` index alongside), ORE ordering → + `eql_v3.ord_term_ore`, free-text → + GIN on `eql_v3.match_term`, encrypted JSON → GIN on + `(eql_v3.to_ste_vec_query(col)::jsonb) jsonb_path_ops`. Storage-only and + non-encrypted columns emit nothing. Closes the #753 gap where integrations + emitted query operators but no index DDL, so encrypted predicates + sequential-scanned by default. + + Also fixed: `isEqlV3Column` / `getEqlV3Column` no longer blow the stack when + handed a column from `pgTable`'s extras callback — drizzle-orm ≤0.45's + `ExtraConfigColumn.getSQLType()` recurses into itself, so the domain is now + recovered from the column's custom-type params instead of calling it. + +### Patch Changes + +- ba706cb: README overhaul: lead with what the package does and why — application-side + encryption with per-value keys, queryable ciphertext (equality, range, + ORDER BY, fuzzy text, encrypted JSON), drizzle-kit-native types and index + derivation — plus a How-it-works section on EQL payloads and searchable + terms. No code changes. +- Updated dependencies [d26950d] +- Updated dependencies [d25d100] + - @cipherstash/stack@1.0.0-rc.5 + ## 1.0.0-rc.4 ### Minor Changes diff --git a/packages/stack-drizzle/package.json b/packages/stack-drizzle/package.json index dd004ad6..1b7489b8 100644 --- a/packages/stack-drizzle/package.json +++ b/packages/stack-drizzle/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-drizzle", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "CipherStash Stack Drizzle ORM integration: searchable, application-layer field-level encryption for PostgreSQL.", "keywords": [ "encrypted", diff --git a/packages/stack-supabase/CHANGELOG.md b/packages/stack-supabase/CHANGELOG.md index 034c19e9..652e57a0 100644 --- a/packages/stack-supabase/CHANGELOG.md +++ b/packages/stack-supabase/CHANGELOG.md @@ -1,5 +1,13 @@ # @cipherstash/stack-supabase +## 1.0.0-rc.5 + +### Patch Changes + +- Updated dependencies [d26950d] +- Updated dependencies [d25d100] + - @cipherstash/stack@1.0.0-rc.5 + ## 1.0.0-rc.4 ### Patch Changes diff --git a/packages/stack-supabase/package.json b/packages/stack-supabase/package.json index 4b0a5bc4..70074531 100644 --- a/packages/stack-supabase/package.json +++ b/packages/stack-supabase/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack-supabase", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "CipherStash Stack Supabase integration: transparent, searchable field-level encryption for Supabase.", "keywords": [ "encrypted", diff --git a/packages/stack/CHANGELOG.md b/packages/stack/CHANGELOG.md index a403f6dc..5e611a12 100644 --- a/packages/stack/CHANGELOG.md +++ b/packages/stack/CHANGELOG.md @@ -1,5 +1,83 @@ # @cipherstash/stack +## 1.0.0-rc.5 + +### Minor Changes + +- d26950d: `encryptedDynamoDB` now accepts EQL v3 tables. + + Pass a table built with `encryptedTable` + the `types.*` domains from + `@cipherstash/stack/v3` (or `@cipherstash/stack/eql/v3`) to any of + `encryptModel`, `bulkEncryptModels`, `decryptModel`, `bulkDecryptModels`. Both + the typed client from `EncryptionV3` and the nominal client from + `Encryption({ config: { eqlVersion: 3 } })` are accepted. + + EQL v2 tables continue to work unchanged — this is additive, and no existing + caller needs to change. The table decides which wire format is used, so a + DynamoDB table populated under one version must keep being read with that + version. + + This fixes a latent bug that made v3 unusable: the write path detected an + encrypted value by its `k: 'ct'` tag, but EQL v3 scalars carry no `k` + discriminator at all. Every v3 scalar fell through to the nested-object branch + and was written as a raw map instead of being split into `__source` and + `__hmac`. + + Notes on capability: + + - Only equality is usable on DynamoDB. `__hmac` is written for domains + that mint an `hm` term — the `*Eq` family, plus `TextOrd`/`TextOrdOre`/ + `TextSearch`. Ordering and bloom-filter terms have no DynamoDB query surface + and are not stored, so those columns remain decryptable but not queryable. + - Nested attributes are supported in v3. There is no nested-group authoring + form (that is a compile error), so declare the column flat with a dotted + path — `{ 'profile.ssn': types.TextEq('profile.ssn') }`. The model is + matched by dotted path, so `{ profile: { ssn } }` resolves, and the nested + attribute keeps its `__hmac` for key conditions. + - Audit metadata on `decryptModel` / `bulkDecryptModels` requires the nominal + client; the `EncryptionV3` client has no audit surface on decrypt. + + The DynamoDB adapter also gains its first test coverage — across the v2 and v3 + paths, where it previously had none. + + Robustness, from review: + + - Passing a v3 table to a client that never registered it (one built for a + different schema set, so it is not in v3 mode for that table) now throws a + clear, actionable error naming the table, instead of failing opaquely deep in + the FFI. + - A malformed decrypt result from a non-conforming client is surfaced as a + failure rather than resolving as a silent `undefined` success. + - Reading back a `__source` attribute that matches no declared column now + logs a debug diagnostic instead of silently returning the raw ciphertext. + - Caller input that cannot be structurally cloned no longer reaches the FFI by + reference — the "encryption never mutates a caller's object" guarantee holds + on that path too. + - The write path now splits only declared columns, matched on the same property + path the read path rebuilds from. A pre-encrypted payload placed under an + undeclared nested name is stored whole (and round-trips) instead of being + split into a `__source` the read path could never reassemble. + - A degenerate payload with an empty-string ciphertext is split like any other + ciphertext rather than falling through and being written as a raw map, which + had leaked its `v`/`i` envelope metadata into storage. + - Arrays are documented as a deliberate carve-out: the mapping does not descend + into them, so a payload inside a list is stored whole (still decryptable, but + not queryable and not part of the `__source`/`__hmac` layout). + + The v3 overloads are strongly typed. `encryptModel` / `bulkEncryptModels` check + the input model against the table's column domains, and return the DynamoDB + attribute map that is actually written — the new exported `EncryptedAttributes` + type, where a declared column `email` becomes `email__source` (plus + `email__hmac` for the equality domains that mint one) rather than surviving as + `email`. `decryptModel` / `bulkDecryptModels` invert it via `DecryptedAttributes`. + `AnyEncryptedTable`, `DynamoDBEncryptionClient` and `AuditConfig` are now + exported from `@cipherstash/stack/dynamodb` so these signatures can be named. + The EQL v2 overloads are unchanged. + +- d25d100: `@cipherstash/stack/wasm-inline` now has the model helpers: `encryptModel` / `decryptModel` and `bulkEncryptModels` / `bulkDecryptModels` (#742). They run the same schema traversal as the native entry (shared code, so the two entries cannot drift on which fields get encrypted): declared columns are encrypted — matched by JS property name, nested fields via the column's dotted path — everything else passes through, and `null`/`undefined` fields are preserved without reaching ZeroKMS. A call that encrypts (or decrypts) at least one field is one ZeroKMS round trip regardless of how many fields or models it covers; a `null`/empty batch, or one whose models carry no schema fields, returns without contacting ZeroKMS at all. `types.Date`/`types.Timestamp` columns round-trip `Date` → `Date` (ISO strings on the wire), and failures follow this entry's `{ data } | { failure }` Result contract, with decrypt failures naming every failing field by its model path. Edge code no longer needs the hand-written `bulkEncrypt` field mapping whose failure mode was a schema column silently persisted in plaintext. + + The shared model traversal is also hardened: it no longer mutates the caller's model (previously a nested-column decrypt wrote decrypted plaintext back into the caller's input, and encrypt overwrote it with ciphertext); a literal flat dotted key, a `__proto__`-shaped key, or a non-object model element is handled safely instead of crashing, leaking plaintext, or reaching `Object.prototype`; an already-encrypted field is passed through rather than re-encrypted; and an invalid `Date` is rejected per field. On the WASM entry, model ops now validate the table against the client's schemas, `Date` values are normalized at every encrypt/query crossing (not just the model path), and a `null`/empty model batch returns `{ data: [] }`. The skills update ships in the `stash` tarball, hence the `stash` patch. + ## 1.0.0-rc.4 ### Minor Changes diff --git a/packages/stack/package.json b/packages/stack/package.json index 32a167b6..126c6e20 100644 --- a/packages/stack/package.json +++ b/packages/stack/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/stack", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "CipherStash Stack for TypeScript and JavaScript", "keywords": [ "encrypted", diff --git a/packages/test-kit/CHANGELOG.md b/packages/test-kit/CHANGELOG.md index 22d82c4a..11c7589c 100644 --- a/packages/test-kit/CHANGELOG.md +++ b/packages/test-kit/CHANGELOG.md @@ -1,5 +1,13 @@ # @cipherstash/test-kit +## 0.0.1-rc.5 + +### Patch Changes + +- Updated dependencies [d26950d] +- Updated dependencies [d25d100] + - @cipherstash/stack@1.0.0-rc.5 + ## 0.0.1-rc.4 ### Patch Changes diff --git a/packages/test-kit/package.json b/packages/test-kit/package.json index 656ffc96..0e603680 100644 --- a/packages/test-kit/package.json +++ b/packages/test-kit/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/test-kit", - "version": "0.0.1-rc.4", + "version": "0.0.1-rc.5", "private": true, "description": "Shared EQL v3 test harness: the domain catalog, the plaintext oracle, and the integration-suite driver. Consumed as TypeScript source — no build step.", "type": "module", diff --git a/packages/wizard/CHANGELOG.md b/packages/wizard/CHANGELOG.md index 0c25c6ce..287ac80d 100644 --- a/packages/wizard/CHANGELOG.md +++ b/packages/wizard/CHANGELOG.md @@ -1,5 +1,28 @@ # @cipherstash/wizard +## 1.0.0-rc.5 + +### Minor Changes + +- 239f79b: New bundled agent skill: `stash-indexing` — how to index EQL v3 encrypted + columns. Integrations that were otherwise correct shipped with no index on any + encrypted predicate because nothing in the installed skills said encrypted + columns _can_ be indexed (#753). The skill covers the functional-index recipes + over the term extractors (`eql_v3.eq_term` / `ord_term` / `ord_term_ore` / + `match_term` / `to_ste_vec_query`) mapped to the `types.*` domains, what works + without superuser on Supabase and managed Postgres versus the ORE opclass + restriction, which domains are storage-only by design, the query shapes that + engage an index (`ORDER BY` sort-key and `GROUP BY` traps), building indexes on + large tables, an `EXPLAIN` verification checklist, and when to create indexes + during an encryption rollout (after backfill, before switching reads). + + `stash init` / `stash impl` handoffs — and the `@cipherstash/wizard` skills + prompt — now install it for **every** integration (Drizzle, Supabase, Prisma + Next, plain PostgreSQL) — the gap is cross-cutting. + The existing per-integration skills gained pointers to it (including the + missing `stash-prisma-next` one-line purpose in the setup prompt, which + previously rendered "(no description)"). + ## 1.0.0-rc.4 ### Patch Changes diff --git a/packages/wizard/package.json b/packages/wizard/package.json index 467135fd..4eb99006 100644 --- a/packages/wizard/package.json +++ b/packages/wizard/package.json @@ -1,6 +1,6 @@ { "name": "@cipherstash/wizard", - "version": "1.0.0-rc.4", + "version": "1.0.0-rc.5", "description": "AI-powered encryption setup for CipherStash. Reads your codebase, picks columns to encrypt, and wires everything up.", "repository": { "type": "git",