Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ exclude_re = [
'stack-encrypt/src/sem/mod\.rs:\d+:\d+: replace <impl MatchConfig for DefaultMatch>::options -> MatchOptions with Default::default\(\)$',
# Both unit-context conversions explicitly return Self::default().
'stack-encrypt/src/target/context\.rs:\d+:\d+: replace <impl From<\(\)> for (DeclaredContext|ExpectedContext<T>)>::from -> Self with Default::default\(\)$',
# Omitting an expectation is expecting nothing: `ExpectedContext::omitted`
# returns Self::default(). That an omitted expectation opens under the
# stored context without checking it is pinned by
# `a_stored_context_opens_as_stored_and_checks_an_expectation`.
'stack-encrypt/src/target/input\.rs:\d+:\d+: replace <impl AllowsNoContext for ExpectedContext<T>>::omitted -> Self with Default::default\(\)$',
# A caller-free declaration adds nothing to the declared context: the
# conversion returns Self::default().
'stack-encrypt/src/target/input\.rs:\d+:\d+: replace <impl From<NoCallerContext> for super::DeclaredContext>::from -> Self with Default::default\(\)$',
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions conformance/stack-encrypt/coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Paths are relative to the repository root. New profile cases run from `v1/`.
| Requirement | Existing evidence | Remaining work |
|---|---|---|
| Scalar value widths and lossless transport | Initial scalar cases and [value-kind requirements](fixture-contract.md#value-kind-requirements), executed by the existing Rust and generated Go runners | Complete per-kind boundary matrices, bytes and broader floats; Python/TS mappings |
| Owned opaque ciphertext and storage envelope | `packages/stack-encrypt/tests/scalar.rs`, `frozen_bytes.rs`; initial cross-opening cases | Blob profile; encoded targets; supported version matrix |
| Record declaration and lowering | [Declaration grammar](declaration-grammar.md); `v1/record-declarations-v1.cases.json` shared by native Rust and Go; native normalization observations; existing record lowering suites | EQL-resolver profile; SDK-to-declaration mappings; runtime source/read shapes |
| Owned opaque ciphertext and storage envelope | `packages/stack-encrypt/tests/scalar.rs`, `frozen_bytes.rs`; initial cross-opening cases; `owned_records.rs`, which tests the owned record derives: passthrough fields are moved, sealed fields are wiped, nested and renamed fields work, a record can store its context, and a column makes one batch | Blob profile; encoded targets; supported version matrix |
| Record declaration and lowering | [Declaration grammar](declaration-grammar.md); `v1/record-declarations-v1.cases.json` shared by native Rust and Go; native normalization observations; existing record lowering suites; `packages/stack-encrypt/tests/owned_equivalence.rs`, which seals one mixed, searchable record three ways: through a hand-written owned target, the owned derive, and the data plan. All three use one provider that records its requests. The test compares the descriptors and the term bytes, and checks that each way decrypts the records of the others | EQL-resolver profile; SDK-to-declaration mappings; runtime source/read shapes |
| Context binding and composition | `packages/stack-encrypt/tests/target.rs`, `descriptor.rs`, `record_lowering.rs`; `languages/golang/encrypt/context_field_test.go`; initial wrong-context cases | Shared composition vectors; expected-context and required-input matrix |
| Query terms and diagnostics | [Query-operator requirements](fixture-contract.md#query-operator-requirements), existing Rust/Go runners and initial equality/query-error cases | Common rejection corpus for every operator, capability and input shape; wider kind/index combinations |
| Missing, SQL NULL and sealed null | `packages/stack-encrypt/src/dynamic/record.rs` null tests; `languages/golang/encrypt/null_test.go` | Explicit language mappings and common missing/null fixtures |
Expand Down
7 changes: 4 additions & 3 deletions conformance/stack-encrypt/lifecycle-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ process. Python and TypeScript are explicitly not enrolled.
|---|---|---|
| `lifecycle-ownership-v1` | Actual record operations and owned `Vec<S>` to `Vec<T>` conversions release fulfilment captures exactly once on success, provider failure and unpolled drop; an unpolled encrypt or decrypt makes no key request. Zipped fulfilments do not share key material. | Client keys are consumed/redacted; explicit close and unreachable-client cleanup release guest memory; calls on a closed client fail. GC cleanup is eventual, so callers should close explicitly. |
| `lifecycle-cancellation-v1` | Dropping an encrypt future paused in key generation, or a decrypt future paused in key retrieval, drops the provider future and operation captures, for a record operation or an owned collection; subsequent work on the cipher succeeds. Guest ABI abort spends the handle and drops the operation. | Cancellation before a call sends no request; cancellation between steps or during token fetch leaves unrelated operations usable. A guest step that overruns cancellation closes the client. Discarded late output is wiped. |
| `lifecycle-partial-output-v1` | A batch with a valid first row and a later authenticated type mismatch releases its decoded prefix without marked heap residue. Tests check this for the dynamic record path and for the owned `Vec<Ciphertext>` to `Vec<String>` conversion. For owned collections, drop witnesses are test values that record whether they were wiped. They show that the prefix is wiped, not leaked, after a late failure or an element's panic. Existing context cases reject late authentication failures without returning partial output. | Generated and raw batch APIs return no partial rows on failure; interrupted HTTP bodies fail; cancelled output is wiped. SDK conversion diagnostics contain no decrypted values. This does not assert erasure of partially populated caller-owned Go objects. |
| `lifecycle-buffer-cleanup-v1` | The existing allocator observer checks selected write/read/JSON parse paths and has a negative control. One of those paths encrypts an owned `Vec<String>` collection through its guard. Drop witnesses show that an owned collection's source is wiped before its pending request is returned. That includes a refused context, and a context conversion that panics. Guest ABI tests check buffer-registry cleanup, error-buffer release and protected text drop. | Request bodies survive until their owner closes them, then wipe; refused requests wipe; heap growth wipes abandoned backing arrays; guest runtime close releases memory. Post-encryption memory scans are supplementary observations. |
| `lifecycle-partial-output-v1` | A batch with a valid first row and a later authenticated type mismatch releases its decoded prefix without marked heap residue. Tests check this for the dynamic record path and for the owned `Vec<Ciphertext>` to `Vec<String>` conversion. For owned collections, drop witnesses are test values that record whether they were wiped. They show that the prefix is wiped, not leaked, after a late failure or an element's panic. The same holds for a column field of a structural record. For an owned record, a drop witness shows that a field opened before a later field is refused is wiped. Existing context cases reject late authentication failures without returning partial output. | Generated and raw batch APIs return no partial rows on failure; interrupted HTTP bodies fail; cancelled output is wiped. SDK conversion diagnostics contain no decrypted values. This does not assert erasure of partially populated caller-owned Go objects. |
| `lifecycle-buffer-cleanup-v1` | The existing allocator observer checks selected write/read/JSON parse paths and has a negative control. One of those paths encrypts an owned `Vec<String>` collection through its guard. Drop witnesses show that an owned collection's source is wiped before its pending request is returned. That includes a refused context, and a context conversion that panics. It also includes the elements after an element whose declaration panics. For an owned record, drop witnesses show that the record wipes the fields it seals, also when the context is refused. They also show that the record moves its passthrough fields without copying them. Guest ABI tests check buffer-registry cleanup, error-buffer release and protected text drop. | Request bodies survive until their owner closes them, then wipe; refused requests wipe; heap growth wipes abandoned backing arrays; guest runtime close releases memory. Post-encryption memory scans are supplementary observations. |
| `lifecycle-diagnostics-v1` | Guest errors discard untrusted foreign cause messages; failed/successful exports do not hand over stale diagnostics. | Structured errors remain available after failures; SDK errors and redacted display/logging expose types or fields rather than plaintext values. |
| `lifecycle-validation-before-io-v1` | Existing runtime/read/context cases count provider calls. Wrong shape, independently detectable context mismatch and oversized descriptors fail before key I/O; lazy assembly stops after local failure. | Host refusals happen before entering the guest, and shared runtime/read cases exercise the uninitialised guest's preflight boundary. Go does not yet expose native provider counters. |
| `lifecycle-logical-batching-v1` | Instrumented `DataKeySource` calls establish one logical generation batch, retrieval grouping by keyset, descriptor ordering and no work for empty batches, for record operations and for an owned `Vec<S>` collection of any length, including an empty one. | Go uses the same engine, but this profile does not claim independently instrumented Go key-request counts. |
Expand Down Expand Up @@ -62,7 +62,8 @@ These entries in the index are limitations, not successful requirements:
cleanup limitations. The observer only scans blocks that are freed, so it
misses plaintext that is leaked and never freed. For the owned `Vec<S>`
conversion, the drop witnesses in `packages/stack-encrypt/tests/collections.rs`
record the wipe itself. A test there fails if a source or a decrypted prefix
record the wipe itself. For owned records, the drop witnesses in
`packages/stack-encrypt/tests/owned_records.rs` do the same. A test there fails if a source or a decrypted prefix
is leaked. The lifecycle
runner preserves the observer's output.
- **`caller-owned-host-values` — outside the guarantee.** Caller-owned strings,
Expand Down
23 changes: 21 additions & 2 deletions conformance/stack-encrypt/v1/lifecycle-v1.enrollment.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
"features": ["--all-features"],
"binary": "collections"
},
"owned-records": {
"language": "rust",
"lane": "fast",
"workspace": ".",
"package": "stack-encrypt",
"features": ["--all-features"],
"binary": "owned_records"
},
"runtime": {
"language": "rust",
"lane": "fast",
Expand Down Expand Up @@ -144,7 +152,11 @@
],
"collections": [
"a_late_element_failure_wipes_the_plaintext_recovered_before_it",
"a_panicking_element_conversion_wipes_the_plaintext_recovered_before_it"
"a_panicking_element_conversion_wipes_the_plaintext_recovered_before_it",
"a_late_element_failure_in_a_column_field_wipes_the_recovered_prefix"
],
"owned-records": [
"a_field_opened_before_a_later_field_is_refused_is_wiped"
],
"contexts": ["shared_context_boundaries"],
"go": [
Expand All @@ -168,7 +180,14 @@
"collections": [
"owned_elements_are_wiped_before_the_pending_is_returned",
"owned_elements_are_wiped_when_the_context_is_refused",
"owned_elements_are_guarded_during_context_conversion"
"owned_elements_are_guarded_during_context_conversion",
"a_panicking_element_declaration_wipes_the_elements_after_it"
],
"owned-records": [
"the_owned_record_wipes_sealed_fields_and_moves_passthrough_fields",
"the_owned_record_wipes_sealed_fields_when_the_context_is_refused",
"a_panicking_field_declaration_wipes_the_sealed_fields_after_it",
"a_declaration_that_panics_while_constructed_wipes_the_plaintext"
],
"guest-abi": [
"buffers::tests::wipe_all_leaves_no_live_buffer",
Expand Down
4 changes: 3 additions & 1 deletion packages/stack-encrypt-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ syn = { version = "3", features = ["full", "extra-traits"] }
# The dev-dependencies below are used only by the crate-level doctests.
# `cargo-udeps` cannot see into rustdoc, so it reports them as unused.
[package.metadata.cargo-udeps.ignore]
development = ["stack-encrypt", "tokio"]
development = ["stack-encrypt", "tokio", "zeroize"]

[dev-dependencies]
# The plain-segment rule is copied from stack-encrypt (a proc-macro crate cannot
Expand All @@ -37,3 +37,5 @@ serde_json = { workspace = true }
# these doctests are what exercises that forwarding.
stack-encrypt = { path = "../stack-encrypt", features = ["test-support"] }
tokio = { workspace = true, features = ["rt", "macros"] }
# The owned-record doctest derives `Zeroize` on its plaintext, as a user does.
zeroize = { workspace = true }
Loading
Loading