Skip to content

Commit 4e2c723

Browse files
authored
Merge pull request #1202 from cipherstash/fix/eql-derive-stevec-selectors
test(eql): derive SteVec selectors from the fixtures, and pin their computation under a fixed key
2 parents 0d819ea + 0aa5ef5 commit 4e2c723

24 files changed

Lines changed: 1185 additions & 337 deletions

‎.github/workflows/test-eql.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ jobs:
536536
workspaces: packages/eql
537537
shared-key: sqlx-tests
538538
save-if: false
539-
- name: Validate v2.2 / v2.3 payload schemas
539+
- name: Validate v2.2 / v2.3 payload schemas, refuse pinned selectors
540540
run: |
541541
mise run test:schema
542542

‎AGENTS.md‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -562,21 +562,25 @@ monorepo, which is where the silent failures are.
562562
would pass if added — and this is the workspace whose `Cargo.lock` the
563563
lockstep bump actually moves. Left open only because it means editing the
564564
subtree's `mise.toml`.
565-
- **Two SQLx test constants are keyed to this repo's CI workspace.** SteVec
566-
selectors are MACs over (column context, JSONPath) under a *workspace keyset*,
567-
so `SELECTOR` in `tests/sqlx/src/fixtures/v3_doc_integer.rs` and `SEL_HELLO_OP`
568-
in `tests/sqlx/src/fixtures/v3_ste_vec.rs` changed value — with no change to
569-
Rust, SQL or fixture logic — the moment CI moved to this repo's
570-
`CS_WORKSPACE_CRN` (`9467cc5d`, `da141339`). Each has a drift guard that
571-
prints the candidate selectors and their discriminators rather than inferring
572-
a replacement, because guessing wrong re-pins to the wrong leaf silently —
573-
which had already happened once, `SEL_HELLO_OP` naming `$.number` while
574-
claiming `$.hello` and surviving because an equality-only assertion cannot
575-
separate them. **If you run the suite against your own CipherStash workspace
576-
the guards will fire: do not commit your local value.** Rotating
577-
`CS_WORKSPACE_CRN` re-pins both. `tests/sqlx/src/selectors.rs` holds five more
578-
workspace-keyed constants with no consumers and no guard — delete or guard
579-
them before using any of them.
565+
- **SteVec selectors in the SQLx suite are read from the fixtures, never
566+
pinned.** A selector is a MAC over (column context, JSONPath) under the
567+
*workspace keyset*, so the same path has a different selector in every
568+
CipherStash workspace. Two pinned constants broke the whole suite each time
569+
CI's `CS_WORKSPACE_CRN` changed (#1200). Tests now call
570+
`fixtures::v3_doc_integer::field_op_selector` and
571+
`fixtures::v3_ste_vec::hello_op_selector`, which apply a written rule to the
572+
loaded fixture and fail, listing every candidate, when zero or several
573+
selectors match. `tests/sqlx/tests/selector_literal_guard.rs` fails on a
574+
random-looking standalone 32-hex run anywhere in the crate's source, SQL and
575+
escaped JSON strings included, and runs DB-free in `mise run test:schema` so
576+
fork PRs get it too; a forged selector must be
577+
visibly fake (`"00000000000000000000000000000001"`). The suite passes against
578+
any workspace. That a keyset and path still produce the same selector bytes is
579+
pinned separately, in the encryption test crate
580+
(`packages/eql/tests/encryption/tests/ste_vec_selectors.rs`, regenerated only
581+
with `EQL_UPDATE_STE_VEC_SELECTORS=1`). It pins the cipherstash-client version
582+
the EQL workspace resolves; protect-ffi resolves its own from its own
583+
lockfile, so the pin covers protect-ffi's only while the two versions agree.
580584
- **`FROZEN_PUBLISHERS` is what stops this repo publishing a package whose
581585
publisher lives elsewhere, and it is a mechanism, not a status.** `scripts/release-gate.mjs` carries a map of every
582586
package that lives here but is published from another repository. For each,

‎biome.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"!languages/typescript/packages/protect-ffi/target",
1212
"!languages/typescript/packages/protect-ffi/src/eql-v3-types",
1313
"!packages/stack-encrypt/tests/fixtures/record_lowering.json",
14+
"!packages/eql/tests/encryption/fixtures/ste_vec_selectors.json",
1415
"!packages/eql/crates/eql-bindings/bindings",
1516
"!packages/eql/crates/eql-bindings/schema",
1617
"!packages/eql/packages/eql/src/generated",

‎packages/eql/Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/eql/mise.toml‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,14 @@ dir = "{{config_root}}"
210210
run = "bash tasks/test/sqlx-partition.sh"
211211

212212
[tasks."test:schema"]
213-
description = "Validate sample payloads against the v2.2 / v2.3 JSON Schemas (no database required)"
213+
description = "Validate sample payloads against the v2.2 / v2.3 JSON Schemas, and refuse pinned SteVec selectors (no database required)"
214214
dir = "{{config_root}}/tests/sqlx"
215+
# selector_literal_guard rides here because it needs no database or
216+
# credentials: the rest of the SQLx suite runs only from the credentialed
217+
# nextest archive, which fork PRs skip, so a pinned selector would otherwise
218+
# reach main unchecked (#1200).
215219
run = """
216-
cargo test --test payload_schema_tests
220+
cargo test --test payload_schema_tests --test selector_literal_guard
217221
"""
218222

219223
[tasks."codegen:parity"]

‎packages/eql/tests/encryption/Cargo.toml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ serde_json = "1"
2727
uuid = "1"
2828
tokio = { version = "1", features = ["rt", "macros"] }
2929
postgres = "0.19"
30+
# The SteVec selector pin (tests/ste_vec_selectors.rs). stack-encrypt has no
31+
# JSON index yet, so the selector computation customers' stored documents
32+
# depend on lives in cipherstash-client, which protect-ffi encrypts through.
33+
# Its `JsonIndexer` runs offline over a supplied index key. The same exact pin
34+
# tests/sqlx names, so this workspace's lockfile gains no second copy.
35+
# protect-ffi resolves cipherstash-client from its own workspace and lockfile,
36+
# so the pin covers the version named here, not necessarily the one protect-ffi
37+
# ships: a protect-ffi bump is covered once this line moves to match it.
38+
cipherstash-client = "=0.42.0"
3039
# The tampering test decodes a stored body, changes one byte and re-encodes
3140
# it. The same line eql-bindings uses, so the lockfile gains no second copy.
3241
base64 = "0.22"
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
{
2+
"_comment": "The SteVec selectors and op terms cipherstash-client's JsonIndexer derives for the document under FakeDataKeySource's index key (keyset nil, recorded in index_key), with the ste_vec index config @cipherstash/stack sends for an EQL v3 JSON column: prefix \"enabled\", the sentinel production sends unrewritten (issue #1203; a fix there changes every selector and is meant to fail this pin). A selector change under the same index_key breaks queries over SteVec documents customers have already stored: it is a breaking change, not a refresh. Regenerate with EQL_UPDATE_STE_VEC_SELECTORS=1 only alongside a changeset on @cipherstash/protect-ffi explaining the break, or, when only index_key changed, for the stack-kms test change that moved it; do not edit by hand.",
3+
"document": {
4+
"active": true,
5+
"field": 1,
6+
"hello": "world",
7+
"missing": null,
8+
"nested": {
9+
"deep": "value"
10+
},
11+
"ratio": 1.5,
12+
"roles": [
13+
"admin",
14+
"user"
15+
]
16+
},
17+
"entries": [
18+
{
19+
"op": null,
20+
"query": "path $",
21+
"s": "79d4ba86361d937f2993d3b98c075e3b"
22+
},
23+
{
24+
"op": null,
25+
"query": "path $.active",
26+
"s": "11b2c514709ac9c276020dd11b536b7e"
27+
},
28+
{
29+
"op": "003f1d2e1b7b9e196ad27da250135b9eac77e6612dd7f9eb3b14453ad256a6da1a0e92c830b7d88f98935176f3f51bb611d2a4b6e7ea003543d941ca2293cb24b92e",
30+
"query": "path $.field",
31+
"s": "e8f5a865f03076086c88f84092fbc11f"
32+
},
33+
{
34+
"op": "00be9ea51be74702138af46863cf77b11df5a6494cc5b9908c38d3bc879bf868055fc29ec50903e843c8d0f3b1074a751fa9",
35+
"query": "path $.hello",
36+
"s": "4ce624f4e5d42afccc8ba3cc5cc4865e"
37+
},
38+
{
39+
"op": null,
40+
"query": "path $.missing",
41+
"s": "04c5e20a3e8292b1f262286bbc1541ae"
42+
},
43+
{
44+
"op": null,
45+
"query": "path $.nested",
46+
"s": "f4f5b9fc338b0608046426e899999360"
47+
},
48+
{
49+
"op": "00be9ea51be74702130ab53921df54844d80b9f4e63c320c3d93d834bd1afa00e656487cf9f12e9b9b95045b7a7996b763e0",
50+
"query": "path $.nested.deep",
51+
"s": "a83be8b56e61de0eb91f5dd2fbd27229"
52+
},
53+
{
54+
"op": "003f1d2e1b7b9e196ad27da25013db45b3adbb0a7d0047908d88999cf9294ecac222141fbd092a272e89f28471a2a10844ee05771ac989d7c12f14fc1bbbf101db55",
55+
"query": "path $.ratio",
56+
"s": "4d398db3deeb169df3e6555bc8aa3a1a"
57+
},
58+
{
59+
"op": null,
60+
"query": "path $.roles",
61+
"s": "c85f57df77d22b5fa553278e98323eab"
62+
},
63+
{
64+
"op": "00be9ea51b6713b224d96f3d1b4841213d5ead044e18f6aba1baeb318e92a796a36ae2f50beba4b0be9b68eb11012a3a7644",
65+
"query": null,
66+
"s": "95c999fa197c0885986b14c27d413556"
67+
},
68+
{
69+
"op": "00be9ea51b6713b224d96f3d1b4841213d5ead044e18f6aba1baeb318e92a796a36ae2f50beba4b0be9b68eb11012a3a7644",
70+
"query": "path $.roles[*]",
71+
"s": "58831b09d9cfd19078f2bf19694ad878"
72+
},
73+
{
74+
"op": "00be9ea51be74701942873023e4fcffba6014d6172e991936d6aade3ddf0d80859e2f7a0d0288e21d7b5",
75+
"query": null,
76+
"s": "95c999fa197c0885986b14c27d413556"
77+
},
78+
{
79+
"op": "00be9ea51be74701942873023e4fcffba6014d6172e991936d6aade3ddf0d80859e2f7a0d0288e21d7b5",
80+
"query": "path $.roles[*]",
81+
"s": "58831b09d9cfd19078f2bf19694ad878"
82+
},
83+
{
84+
"op": null,
85+
"query": null,
86+
"s": "02940a5686f418020d74d0002de800b2"
87+
},
88+
{
89+
"op": null,
90+
"query": "value $.active == true",
91+
"s": "c927fa8e65b54b0771d4f159a06cb287"
92+
},
93+
{
94+
"op": null,
95+
"query": "value $.field == 1",
96+
"s": "a3a0b9bc706164dfadbaae8641812fa5"
97+
},
98+
{
99+
"op": null,
100+
"query": "value $.hello == \"world\"",
101+
"s": "1a5bb3bb21b3a241fa1b72f426e6fd70"
102+
},
103+
{
104+
"op": null,
105+
"query": null,
106+
"s": "ad505c15665d885e912a73587efb6210"
107+
},
108+
{
109+
"op": null,
110+
"query": null,
111+
"s": "03fde1bed397618949b7be9ae382f839"
112+
},
113+
{
114+
"op": null,
115+
"query": "value $.nested.deep == \"value\"",
116+
"s": "310c4dc2ffff5c7b59f79418f202ee33"
117+
},
118+
{
119+
"op": null,
120+
"query": "value $.ratio == 1.5",
121+
"s": "63476dad81490f4c1c5c67c62ce95a19"
122+
},
123+
{
124+
"op": null,
125+
"query": null,
126+
"s": "0839fd15b38aa4e460c869f88f0a461e"
127+
},
128+
{
129+
"op": null,
130+
"query": null,
131+
"s": "6f71a23e87e425691b5e5d810f036d5d"
132+
},
133+
{
134+
"op": null,
135+
"query": null,
136+
"s": "76de72ebb9bf5486303698f1635ac8ed"
137+
},
138+
{
139+
"op": null,
140+
"query": null,
141+
"s": "dcaf027cb5a86d3f366d38ca1ab8ab6a"
142+
},
143+
{
144+
"op": null,
145+
"query": null,
146+
"s": "4a140c3e421cd64a826a479ffae64eae"
147+
}
148+
],
149+
"index_config": {
150+
"array_index_mode": {
151+
"item": true,
152+
"position": false,
153+
"wildcard": true
154+
},
155+
"kind": "ste-vec",
156+
"mode": "compat",
157+
"prefix": "enabled"
158+
},
159+
"index_key": "01dc1a6934ac96bd071ac7f17f56f444cfbd7c5db350747d6d69d8f8756a0c60",
160+
"selectors": {
161+
"path $": "79d4ba86361d937f2993d3b98c075e3b",
162+
"path $.active": "11b2c514709ac9c276020dd11b536b7e",
163+
"path $.field": "e8f5a865f03076086c88f84092fbc11f",
164+
"path $.hello": "4ce624f4e5d42afccc8ba3cc5cc4865e",
165+
"path $.missing": "04c5e20a3e8292b1f262286bbc1541ae",
166+
"path $.nested": "f4f5b9fc338b0608046426e899999360",
167+
"path $.nested.deep": "a83be8b56e61de0eb91f5dd2fbd27229",
168+
"path $.ratio": "4d398db3deeb169df3e6555bc8aa3a1a",
169+
"path $.roles": "c85f57df77d22b5fa553278e98323eab",
170+
"path $.roles[*]": "58831b09d9cfd19078f2bf19694ad878",
171+
"value $.active == true": "c927fa8e65b54b0771d4f159a06cb287",
172+
"value $.field == 1": "a3a0b9bc706164dfadbaae8641812fa5",
173+
"value $.hello == \"world\"": "1a5bb3bb21b3a241fa1b72f426e6fd70",
174+
"value $.nested.deep == \"value\"": "310c4dc2ffff5c7b59f79418f202ee33",
175+
"value $.ratio == 1.5": "63476dad81490f4c1c5c67c62ce95a19"
176+
}
177+
}

0 commit comments

Comments
 (0)