diff --git a/.changeset/pre.json b/.changeset/pre.json index cdaa6fd7..57c9a142 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,33 @@ "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", + "silent-telemetry-flush", "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 +99,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..0cdf4ade 100644 --- a/e2e/CHANGELOG.md +++ b/e2e/CHANGELOG.md @@ -1,5 +1,22 @@ # @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 [8d32ba6] +- 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..56f49cc8 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,179 @@ # @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). +- 8d32ba6: Fix telemetry stack traces printing to the terminal when the telemetry endpoint is unreachable or returns an error. The PostHog SDK logs flush failures to the console internally (bypassing the CLI's own error swallowing), so a machine with telemetry enabled and a failing network printed two full stack-trace blocks per command. The CLI now supplies the SDK with a fetch wrapper that absorbs network and HTTP errors, so a failed send is silently dropped — matching the documented fire-and-forget behaviour. Command output, including `stash manifest --json` stdout, was never corrupted; the noise went to stderr. +- 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",