Repository navigation
Conversation
…tack 1.x
The existing compat suite showed protect-dynamodb 6.0.1 items read and
query under Stack 1.x, but its acceptance run re-encrypted them as a
backfill step, so it never showed the backfill could be skipped. A new
block freezes a protect-dynamodb corpus (single and bulk writes), moves
the app to Stack 1.x alone, decrypts every item, runs every equality
query, writes new items beside the old ones, and then asserts the stored
items are byte-identical to the snapshot.
The stash-dynamodb skill told customers to re-encrypt every item to move
a table to v3. That is not required, so the skill now says re-encryption
is optional and lists the three differences to plan for: boolean
equality lookups, downcase filters, and legacy dataType('json')
attributes.
The legacy client builder and the result unwrapper move to a shared
helper so the cross-store suite can reuse them.
An encrypted scalar is the same two strings in both stores: DynamoDB's <attr>__source and <attr>__hmac are the c and hm fields of a Postgres EQL v3 value. The rest of the Postgres value is the version and the table and column names, none of it secret, so moving a value between stores is a copy with no key involved. The new suite pins that in both directions with real protect-dynamodb 6.0.1 and protect 10.2.1 payloads as well as Stack 1.x ones: the EQL v3 column types accept the copied values, the ciphertext arrives byte-identical, Stack decrypts it, SQL equality queries and DynamoDB search hashes still match, and protect-dynamodb 6.0.1 reads items copied from Postgres. It needs CS_* credentials and DATABASE_URL, so it runs from its own config as test:protect-crossover, and the e2e-tests job now starts a Postgres through integration-db for it.
🦋 Changeset detectedLatest commit: fdb5e0c The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
The boolean-equality test accepted any encryptQuery failure, so a service
outage passed it. It now asserts the "no indexes configured" refusal
Stack gives a storage-only column, after a control query on the same
client succeeds. The snapshot test claimed more than it could show:
nothing in its block writes to the table, so it is renamed and its
comments say it pins only that reads and queries left the stored items
unchanged.
The cross-store suite sorted results with ORDER BY, which follows the
database collation, so it now sorts in JavaScript. The protect-dynamodb
decrypt check uses toEqual like every other decrypt check, and the
raw-payload block goes through the same two copy functions as the rest
of the file instead of a third, slightly different one.
In CI the cross-store suite was skipped whenever the protect-dynamodb
step failed. Both new steps now run on !cancelled().
The stash-dynamodb skill said Stack reads protect-dynamodb items as they
are while the paragraph above still required { storedEqlVersion: 2 }.
It now says a top-level scalar item reads either way and the option is
needed for a grouped v2 field declared by its bare leaf name. The advice
to REMOVE a stale __hmac on in-place updates now covers every domain
that writes no __hmac, not only Boolean.
A v2 downcase column mapped to TextEq is hashed case-sensitively by Stack, so a mixed-case write gets an __hmac that a lowercase query never matches. The skill told customers to lowercase only the query value. It now says to lowercase every write too, notes that this drops the original casing from the ciphertext, and suggests a separate attribute for the original when the app needs it. The write-path behaviour is already pinned by the 'downcase on the write path' test.
cipherstash-agent
left a comment
There was a problem hiding this comment.
Review attestation
Reviewed fdb5e0c · posted from Claude Code · claude-sonnet-5-5
Sources: code-review (medium) · claude-sonnet-5-5; review-cross-check
Skills: code-review, review-cross-check, review-post
No findings.
Seven candidate observations were checked against fdb5e0c and none held up as a defect (stale-advice, test-strength, workflow-gating and dependency-pin concerns were refuted or non-defects). No human review feedback existed on the PR at the time.
Generated by Claude Code
Summary
Customers on
@cipherstash/protect@10.2.1+@cipherstash/protect-dynamodb@6.0.1want to move to Stack 1.x without re-encrypting their data. This PR adds tests that prove they can. Existing DynamoDB items keep decrypting and stay searchable by equality as stored, and an encrypted value can move between DynamoDB and Postgres without being re-encrypted. It also fixes thestash-dynamodbagent skill, which wrongly told customers to re-encrypt every item.Background for reviewers new to this: an encrypted value has a ciphertext (the encrypted data) and, for searchable columns, a search hash (a keyed hash used for equality lookups). DynamoDB stores them as
<attr>__sourceand<attr>__hmac. In Postgres, an EQL v3 column stores them as thecandhmfields of a JSON value. EQL is CipherStash's SQL extension for querying encrypted data.Changes
No-re-encryption proof (DynamoDB)
e2e/tests/protect-dynamodb-compat.e2e.test.ts. A set of items written by protect-dynamodb 6.0.1 is frozen and snapshotted. From then on only Stack 1.x runs: it decrypts every item (single and bulk), runs every equality query, and writes new items beside the old ones. At the end the stored items are checked byte-for-byte against the snapshot. It also confirms the one lookup a Stack app cannot make, equality on a boolean. Re-encrypting wouldn't help there, because Stack writes no boolean search hash either.Cross-store proof (DynamoDB ↔ Postgres)
e2e/tests/protect-crossover.e2e.test.ts. Moving a value between stores means copying those two strings; no key is used. Each test checks the ciphertext arrives unchanged, then that the receiving store can decrypt it and find it by equality:eql_v3.eqqueries find them, and Stack decrypts them. Copying DynamoDB → Postgres → DynamoDB returns the original item exactly.createSearchTerms.protect.encryptoutput (EQL v2) → both stores.legacyProtect/okmove toe2e/tests/helpers/legacy-protect.ts, shared by both suites.Wiring
e2e/vitest.protect-crossover.config.ts+test:protect-crossoverturbo task. The default e2e config excludes the new file.tests.ymle2e-testsjob: starts a Postgres with.github/actions/integration-db, runs the suite, tears it down. The suite installs EQL v3 through the realstash eql install.@cipherstash/e2e:@cipherstash/test-kit(workspace) andpostgres@^3.4.8, which is already in the lockfile (3.4.9) via@cipherstash/stack. No install scripts.Skill
skills/stash-dynamodb/SKILL.md: re-encryption is optional. Keep property keys and DB column names, and map each v2 equality column to anEqdomain. The skill now lists the three things to plan for: boolean equality lookups,downcasefilters, and legacydataType('json')attributes.stashpatch changeset included, since skills ship in thestashtarball.Verification
pnpm --filter @cipherstash/e2e typecheck: passes.biome checkone2e/andturbo.json: no errors.DATABASE_URLlocally, so both files stopped at their configuration guard, as designed: they fail rather than skip without config. The CI run on this PR is the first live run.pnpm run test:scripts(workflow meta-tests): 1402 passed, 2 failed. Both failures (cargo-lock-freshness,lint-no-eql-registry-pins) come from local.worktrees/checkouts being picked up by the file scan, not from this change. CI is the clean check.Related
Refs #1436 (the protect-dynamodb 6.0.1 compatibility suite this builds on).
Review notes
toEnvelope/toAttributesfunctions at the top ofprotect-crossover.e2e.test.ts. They are the whole migration surface.encryptQueryor one that returns no search hash, because I couldn't run it to see which. Tighten it after the first green run.protectinto EQL v2 columns is out of scope here. That path is covered by the EQL v2 → v4 migration work.Summary by CodeRabbit
downcasenormalization, JSON fields, and grouped EQL v2 fields.