Skip to content

feat(proxy): accept inbound EQL ciphertext payloads - #458

Open
freshtonic wants to merge 9 commits into
mainfrom
feat/cip-3822-inbound-eql-payloads
Open

feat(proxy): accept inbound EQL ciphertext payloads#458
freshtonic wants to merge 9 commits into
mainfrom
feat/cip-3822-inbound-eql-payloads

Conversation

@freshtonic

@freshtonic freshtonic commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • accept EQL v3 storage payloads and query-only operands in SQL literals and bind parameters before plaintext decoding
  • validate payload version, inferred destination, configured scalar and SteVec SEM terms, and authenticated descriptors
  • authenticate every stored ciphertext, including every SteVec entry, then independently rederive and compare its query projection
  • accept bare selector hashes using the fixed selector-hash shape; document the unavoidable plaintext/hash ambiguity
  • forward valid payloads without re-encryption and fail closed with a generic, documented transaction-aborting error
  • cover application-generated payloads, query-only operands, selector hashes, metadata and term tampering, non-root SteVec tampering, and transaction recovery in cipherstash-proxy-integration
  • demonstrate storage, query-only, and selector-hash inputs in the executable showcase

Compatibility

On encrypted columns, JSON values with top-level v, i, and at least one of c, h, or sv advertise themselves as EQL payloads and fail closed if invalid. There is no opt-out. The error documentation includes an upgrade audit query and guidance for text-backed source data.

Testing

  • mise run test:check
  • mise run test:unit (all 372 tests pass; the macOS configuration-store test required an unsandboxed isolated rerun)
  • mise run test:clippy
  • mise run test:format
  • cargo test -p cipherstash-proxy-integration --no-run
  • cargo test -p showcase --no-run

Runtime integration and showcase cases use the existing credentialed CI tasks; this checkout has no application/ZeroKMS credentials for starting an isolated current-branch Proxy locally.

Linear: CIP-3822

@freshtonic
freshtonic requested a review from tobyhede August 19, 2026 06:59

@tobyhede tobyhede left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the new inbound EQL ciphertext path. One security issue with two exploit paths, one design trade-off to confirm, and one operational gap. Each item was checked against the actual code on this branch and the cipherstash-client 0.42.2 dependency source before being posted here. Two items from an earlier automated pass did not hold up under verification and are not included: the "h" top-level key check is not dead code (it detects SteVec payloads, which carry no top-level c field), and the binary-parameter byte handling in bind.rs does not corrupt data (original bytes stay untouched, and a failed trial parse falls back safely).

See inline comments for details.

Comment thread packages/cipherstash-proxy/src/postgresql/inbound_eql.rs
Comment thread packages/cipherstash-proxy/src/postgresql/inbound_eql.rs Outdated
Comment thread packages/cipherstash-proxy/src/postgresql/inbound_eql.rs Outdated
Comment thread packages/cipherstash-proxy/src/postgresql/frontend.rs Outdated
Comment thread packages/cipherstash-proxy/src/postgresql/frontend.rs
@freshtonic
freshtonic force-pushed the feat/cip-3822-inbound-eql-payloads branch from aad1e0b to 4db9e51 Compare August 23, 2026 12:28
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Scope the ZeroKMS cipher to CS_DEFAULT_KEYSET_ID whenever a connection has not selected an override. Previously Proxy only checked that the setting existed, then passed no identifier to ScopedCipher and could silently use the client's account default instead.

Application-encrypted payloads use the configured keyset explicitly. When the account and configured defaults differ, Proxy derived searchable-encryption metadata with another index key and rejected valid inbound ciphertext during authentication. Preserve connection-level keyset precedence while making the configured fallback effective.

Signed-off-by: James Sadler <james@cipherstash.com>
Inbound EQL authentication independently re-encrypts plaintext and compares its searchable-encryption metadata with the supplied payload. Match-index generation does not guarantee a stable ordering for Bloom-filter bit positions, so comparing serialized query operands rejected valid ciphertext whenever equivalent positions were emitted in another order.

Compare scalar metadata field by field and normalize Bloom-filter positions before equality. Continue comparing identifiers, exact-match terms, ordered terms, versions, and structured SteVec operands exactly so altered metadata still fails closed. Add a regression test covering reordered equivalent Bloom-filter terms.

Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
Signed-off-by: James Sadler <james@cipherstash.com>
@freshtonic
freshtonic force-pushed the feat/cip-3822-inbound-eql-payloads branch from d99891b to dc7a97d Compare August 24, 2026 02:23
@freshtonic
freshtonic requested a review from tobyhede August 24, 2026 02:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants