Version Packages (rc)#750
Open
github-actions[bot] wants to merge 1 commit into
Open
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
3 times, most recently
from
July 23, 2026 05:10
aa6b844 to
1f5c27e
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
July 23, 2026 05:18
1f5c27e to
d01cc69
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
mainis currently in pre mode so this branch has prereleases rather than normal releases. If you want to exit prereleases, runchangeset pre exitonmain.Releases
@cipherstash/stack@1.0.0-rc.5
Minor Changes
d26950d:
encryptedDynamoDBnow accepts EQL v3 tables.Pass a table built with
encryptedTable+ thetypes.*domains from@cipherstash/stack/v3(or@cipherstash/stack/eql/v3) to any ofencryptModel,bulkEncryptModels,decryptModel,bulkDecryptModels. Boththe typed client from
EncryptionV3and the nominal client fromEncryption({ 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 nokdiscriminator at all. Every v3 scalar fell through to the nested-object branch
and was written as a raw map instead of being split into
<attr>__sourceand<attr>__hmac.Notes on capability:
<attr>__hmacis written for domainsthat mint an
hmterm — the*Eqfamily, plusTextOrd/TextOrdOre/TextSearch. Ordering and bloom-filter terms have no DynamoDB query surfaceand are not stored, so those columns remain decryptable but not queryable.
form (that is a compile error), so declare the column flat with a dotted
path —
{ 'profile.ssn': types.TextEq('profile.ssn') }. The model ismatched by dotted path, so
{ profile: { ssn } }resolves, and the nestedattribute keeps its
__hmacfor key conditions.decryptModel/bulkDecryptModelsrequires the nominalclient; the
EncryptionV3client 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:
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.
failure rather than resolving as a silent
undefinedsuccess.<attr>__sourceattribute that matches no declared column nowlogs a debug diagnostic instead of silently returning the raw ciphertext.
reference — the "encryption never mutates a caller's object" guarantee holds
on that path too.
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
<attr>__sourcethe read path could never reassemble.ciphertext rather than falling through and being written as a raw map, which
had leaked its
v/ienvelope metadata into storage.into them, so a payload inside a list is stored whole (still decryptable, but
not queryable and not part of the
__source/__hmaclayout).The v3 overloads are strongly typed.
encryptModel/bulkEncryptModelscheckthe input model against the table's column domains, and return the DynamoDB
attribute map that is actually written — the new exported
EncryptedAttributestype, where a declared column
emailbecomesemail__source(plusemail__hmacfor the equality domains that mint one) rather than surviving asemail.decryptModel/bulkDecryptModelsinvert it viaDecryptedAttributes.AnyEncryptedTable,DynamoDBEncryptionClientandAuditConfigare nowexported from
@cipherstash/stack/dynamodbso these signatures can be named.The EQL v2 overloads are unchanged.
d25d100:
@cipherstash/stack/wasm-inlinenow has the model helpers:encryptModel/decryptModelandbulkEncryptModels/bulkDecryptModels(wasm-inline has no model helpers: encryptModel/decryptModel (and their bulk forms) are Node-only #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, andnull/undefinedfields 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; anull/empty batch, or one whose models carry no schema fields, returns without contacting ZeroKMS at all.types.Date/types.Timestampcolumns round-tripDate→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-writtenbulkEncryptfield 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 reachingObject.prototype; an already-encrypted field is passed through rather than re-encrypted; and an invalidDateis rejected per field. On the WASM entry, model ops now validate the table against the client's schemas,Datevalues are normalized at every encrypt/query crossing (not just the model path), and anull/empty model batch returns{ data: [] }. The skills update ships in thestashtarball, hence thestashpatch.stash@1.0.0-rc.5
Patch Changes
d26950d:
encryptedDynamoDBnow accepts EQL v3 tables.Pass a table built with
encryptedTable+ thetypes.*domains from@cipherstash/stack/v3(or@cipherstash/stack/eql/v3) to any ofencryptModel,bulkEncryptModels,decryptModel,bulkDecryptModels. Boththe typed client from
EncryptionV3and the nominal client fromEncryption({ 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 nokdiscriminator at all. Every v3 scalar fell through to the nested-object branch
and was written as a raw map instead of being split into
<attr>__sourceand<attr>__hmac.Notes on capability:
<attr>__hmacis written for domainsthat mint an
hmterm — the*Eqfamily, plusTextOrd/TextOrdOre/TextSearch. Ordering and bloom-filter terms have no DynamoDB query surfaceand are not stored, so those columns remain decryptable but not queryable.
form (that is a compile error), so declare the column flat with a dotted
path —
{ 'profile.ssn': types.TextEq('profile.ssn') }. The model ismatched by dotted path, so
{ profile: { ssn } }resolves, and the nestedattribute keeps its
__hmacfor key conditions.decryptModel/bulkDecryptModelsrequires the nominalclient; the
EncryptionV3client 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:
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.
failure rather than resolving as a silent
undefinedsuccess.<attr>__sourceattribute that matches no declared column nowlogs a debug diagnostic instead of silently returning the raw ciphertext.
reference — the "encryption never mutates a caller's object" guarantee holds
on that path too.
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
<attr>__sourcethe read path could never reassemble.ciphertext rather than falling through and being written as a raw map, which
had leaked its
v/ienvelope metadata into storage.into them, so a payload inside a list is stored whole (still decryptable, but
not queryable and not part of the
__source/__hmaclayout).The v3 overloads are strongly typed.
encryptModel/bulkEncryptModelscheckthe input model against the table's column domains, and return the DynamoDB
attribute map that is actually written — the new exported
EncryptedAttributestype, where a declared column
emailbecomesemail__source(plusemail__hmacfor the equality domains that mint one) rather than surviving asemail.decryptModel/bulkDecryptModelsinvert it viaDecryptedAttributes.AnyEncryptedTable,DynamoDBEncryptionClientandAuditConfigare nowexported from
@cipherstash/stack/dynamodbso these signatures can be named.The EQL v2 overloads are unchanged.
d6bc9e9:
stash plannow reports the outcome that actually occurred instead of unconditionally printingPlan drafted at .cipherstash/plan.mdand exiting 0 (stash plan reports "Plan drafted at .cipherstash/plan.md" and exits 0 without writing the file (rc.3 M2) #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,planerrors 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.f78fd7a:
stash schema buildnow 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.Booleandomain automatically, while JSON columns areassigned the queryable
types.Jsondomain, with encrypted containment andselector queries. Other columns default to the widest searchable domain,
matching the previous behaviour. The internal
SearchOpcapability tupleand the
v3DomainFactorytranslation shim are removed, unblocking EQL v2removal (Remove EQL v2 repo-wide (umbrella) #707, stash schema build: port the v2 capability column picker to EQL v3 domains #751).
d25d100:
@cipherstash/stack/wasm-inlinenow has the model helpers:encryptModel/decryptModelandbulkEncryptModels/bulkDecryptModels(wasm-inline has no model helpers: encryptModel/decryptModel (and their bulk forms) are Node-only #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, andnull/undefinedfields 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; anull/empty batch, or one whose models carry no schema fields, returns without contacting ZeroKMS at all.types.Date/types.Timestampcolumns round-tripDate→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-writtenbulkEncryptfield 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 reachingObject.prototype; an already-encrypted field is passed through rather than re-encrypted; and an invalidDateis rejected per field. On the WASM entry, model ops now validate the table against the client's schemas,Datevalues are normalized at every encrypt/query crossing (not just the model path), and anull/empty model batch returns{ data: [] }. The skills update ships in thestashtarball, hence thestashpatch.f628463: Fix invalid DDL when a Drizzle column changes to an EQL v3 domain.
drizzle-kit generateemits an in-placeALTER TABLE … ALTER COLUMN … SET DATA TYPEwhen a plaintext column is changed to an encrypted one, which Postgres rejects — there
is no cast from
text/numericto an EQL type, and on drizzle-kit 0.31.0+ the emittedtype name is additionally mangled to
"undefined"."eql_v3_<name>". The migrationrewriter 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 alongsideeql_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 thateql install --drizzlehas 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 encryptpath (add → backfill → cutover →drop) for populated production tables.
@cipherstash/prisma-next@1.0.0-rc.5
Patch Changes
@cipherstash/stack-drizzle@1.0.0-rc.5
Patch Changes
@cipherstash/stack-supabase@1.0.0-rc.5
Patch Changes
@cipherstash/wizard@1.0.0-rc.5
@cipherstash/e2e@0.0.3-rc.5
Patch Changes
@cipherstash/basic-example@1.2.14-rc.5
Patch Changes
@cipherstash/prisma-next-example@0.1.0-rc.5
Patch Changes
@cipherstash/bench@0.0.5-rc.5
Patch Changes
@cipherstash/test-kit@0.0.1-rc.5
Patch Changes