Repository navigation
Conversation
Add a deterministic-kms test feature to the extension. In that build, _engine._use_deterministic_keys(seed) makes the process take its keys from stack-kms's DeterministicSource, as the Go SDK's deterministic guest does. A normal build has neither the feature nor the function. tests_deterministic/ runs against the EQL rows the Go SDK stored under that seed: the frozen stored_v4_rows_v1.json, and the ordered, smallint and ledger fixtures. Python opens Go's rows to the same values, and seals the same values to byte-identical equality, order and match terms. Its query terms equal Go's stored terms. Two ledger rows Python cannot hold are refused with decrypt_not_representable. mise run test:deterministic runs them in their own pytest process, and tests-python.yml runs that task with no credentials. Add a heap-hygiene test to the extension: a spy allocator fails when a block freed during encrypt or query still contains the plaintext. It found that the owned copy of a str, which pyo3 makes under abi3-py39, was freed unwiped. Read now wipes it with zeroize. The four native-plan fixtures and the shared conformance corpus are not part of this commit. The fixtures declare plans the Python SDK cannot express. The corpus needs the stack rebased onto next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refuse an opened timestamp with nanoseconds as decrypt_not_representable. to_python rounded it to microseconds, so a Go-written value opened changed, and resealed to another equality term. The ledger test now checks nanoseconds and years on their own: the old test passed only because those rows also had a year out of range. Wipe decrypted plaintext. A Scalar now wipes its text and bytes when dropped, and to_python borrows it. Decimal digits are wiped on read and on to_python. The hygiene test now watches decrypt too, and a decimal by its digits. The hygiene test now checks that the spy fails on a leak of each needle. Each test takes one lock before it attaches to Python: one test could hold the watch lock and wait for the GIL while another held the GIL and waited for the watch lock. In the test build, client::shared() refuses after the switch to the deterministic client, so an operation that skips the check cannot reach ZeroKMS. use_deterministic takes the build lock. CI's clippy step lints every feature. test_go_rows.py no longer imports from conftest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Scalar now wipes every variant when dropped, not only text and bytes: integers, floats, dates, timestamps and decimal mantissas sit inline in a row's buffer. decrypt also stops moving each value out of its row. A moved value left its bytes in the old slot, and the buffer was freed with them. to_python now borrows the value, so each one is wiped where it lies. The hygiene test now watches a decimal's mantissa as the engine's i128, and fails without either fix. decrypt_not_representable now names the one cause that applies: a year outside 1 to 9999, or a timestamp with nanoseconds. In the test build, shared() checks for the deterministic client again after it takes the build lock. A thread that waited for the lock during the switch built a live client. A --features flag on maturin's command line replaces the features in pyproject.toml. test:deterministic now names pyo3/extension-module again. Maturin 1.10 and later set PYO3_BUILD_EXTENSION_MODULE, but pyproject.toml allows 1.9, where the test build linked libpython. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1393) encrypt, decrypt and query each wrote their call twice: once for the deterministic test client, once for the live one. client::run! now takes the call once and compiles it for both, so the two cannot drift. The clients have different key source types, so a function that returned the current client would still need two calls. encrypt now borrows each column, as decrypt does. A moved passthrough value left its bytes in the row's buffer when the buffer was freed. A decrypted row is now written into one buffer and moved whole. It was a Vec<Option<Scalar>> collected into a Vec<Scalar>, which left no plaintext only because rustc reused the buffer in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decrypt chose the cause of decrypt_not_representable from the value before to_python ran, with its own copy of the nanosecond rule. Any other Python error on a date or timestamp was reported as a year out of range, and the real error was lost. to_python now returns the cause itself, as Unfit::Nanoseconds or Unfit::Year. A year is reported only for datetime's own range errors: ValueError from fromordinal, OverflowError from timedelta or the addition. Every other error is Python's own and is raised as it is. The comment in crates/engine/Cargo.toml now names both places that turn on extension-module: pyproject.toml, and the test:deterministic task. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueTip 🎯 Customize your risk assessments
✅ Automatic approvals, with your rules
CodeRabbit Verify
|
|
Found 2 test failures on Blacksmith runners: Failures
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
The Python SDK now opens the EQL rows that the Go SDK stored, and seals the same values to the same search terms. EQL (Encrypt Query Language) is CipherStash's set of PostgreSQL types for encrypted values. A new test also checks that the extension wipes its own copies of the plaintext before it frees them.
These tests need no credentials. A test build of the extension takes its keys from a fixed seed, in the same way as the Go SDK's test build.
Changes
deterministic-kmsfeature._engine._use_deterministic_keys(seed)switches the process to stack-kms'sDeterministicSource. The Go SDK's test build uses the same key source.client::run!runs each operation on whichever client is in place.encrypt,decrypt, andqueryeach write their call once.tests_deterministic/test_go_rows.pyreads four Go fixtures inpackages/eql/tests/encryption/fixtures/. For each one, Python does three things:Decimal's scale count.c.stored_v4_rows_v1.json. Nothing writes it again. The Go and Rust tests already check it, and now Python does too.datetimesupports neither.decryptnow refuses both withdecrypt_not_representable. The message names the one cause that applies.decryptandencryptnow borrow each value, and no longer move it out of its row. A moved value left its bytes in the row's buffer, and the buffer was freed with them.Decimal.crates/engine/src/heap_hygiene.rsinstalls a spy allocator.encrypt,query, ordecryptstill contains the plaintext.mise run test:deterministicbuilds the test extension, then runstests_deterministic/in its own pytest process.tests-python.ymlruns it in a new step, "Python against the Go SDK's rows". The workflow also runs when the Go fixtures change.--featuresflag replaces the features inpyproject.toml. So the test task namespyo3/extension-moduleagain. Without it, maturin 1.9 links the test build to libpython.Verification
mise run test:cargo: all core and extension tests pass, with the 4 hygiene tests.mise run test:deterministic: 18 passed.gate-probetests.cargo fmt --check: no findings, with and without all features.pnpm run test:scripts: 84 of 84 files pass.assertlines in Python.Related
Refs #1393. Refs #1341.
This PR does not close #1393. These parts of the issue are not in it:
next.Review notes
client.rsfor the switch, thenheap_hygiene.rs.to_pythonpasses such an error on unchanged.🤖 Generated with Claude Code