@cipherstash/protect-ffi 0.21.4 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +9 -2
- package/lib/index.d.cts +96 -51
- package/lib/normalizeEncryptConfig.d.ts +29 -0
- package/lib/normalizeEncryptConfig.js +60 -0
- package/package.json +7 -7
package/lib/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ exports.encryptQueryBulk = encryptQueryBulk;
|
|
|
47
47
|
exports.ensureKeyset = ensureKeyset;
|
|
48
48
|
const credentials_js_1 = require("./credentials.js");
|
|
49
49
|
const native = __importStar(require("./load.cjs"));
|
|
50
|
+
const normalizeEncryptConfig_js_1 = require("./normalizeEncryptConfig.js");
|
|
50
51
|
var credentials_js_2 = require("./credentials.js");
|
|
51
52
|
Object.defineProperty(exports, "withEnvCredentials", { enumerable: true, get: function () { return credentials_js_2.withEnvCredentials; } });
|
|
52
53
|
class ProtectError extends Error {
|
|
@@ -81,9 +82,15 @@ function inferErrorCode(message) {
|
|
|
81
82
|
if (message.includes(' index configured')) {
|
|
82
83
|
return 'MISSING_INDEX';
|
|
83
84
|
}
|
|
84
|
-
if (message.includes(
|
|
85
|
+
if (message.includes('requires plaintext_type: json')) {
|
|
85
86
|
return 'STE_VEC_REQUIRES_JSON_CAST_AS';
|
|
86
87
|
}
|
|
88
|
+
if (message.includes('requires plaintext_type: text')) {
|
|
89
|
+
return 'MATCH_REQUIRES_TEXT';
|
|
90
|
+
}
|
|
91
|
+
if (message.includes('unsupported config version')) {
|
|
92
|
+
return 'UNSUPPORTED_CONFIG_VERSION';
|
|
93
|
+
}
|
|
87
94
|
return 'UNKNOWN';
|
|
88
95
|
}
|
|
89
96
|
function normalizeError(err) {
|
|
@@ -117,7 +124,7 @@ function wrapSync(fn) {
|
|
|
117
124
|
}
|
|
118
125
|
function newClient(opts) {
|
|
119
126
|
return wrapAsync(() => native.newClient({
|
|
120
|
-
|
|
127
|
+
encryptConfig: (0, normalizeEncryptConfig_js_1.normalizeEncryptConfig)(opts.encryptConfig),
|
|
121
128
|
clientOpts: (0, credentials_js_1.withEnvCredentials)(opts.clientOpts),
|
|
122
129
|
}));
|
|
123
130
|
}
|
package/lib/index.d.cts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type CredentialOpts } from './credentials.js';
|
|
2
|
+
import { type NativeEncryptConfig } from './normalizeEncryptConfig.js';
|
|
2
3
|
export { withEnvCredentials, type EnvReader, type CredentialOpts, } from './credentials.js';
|
|
3
4
|
declare const sym: unique symbol;
|
|
4
5
|
export type Client = {
|
|
5
6
|
readonly [sym]: unknown;
|
|
6
7
|
};
|
|
7
8
|
declare module './load.cjs' {
|
|
8
|
-
function newClient(opts:
|
|
9
|
+
function newClient(opts: NativeNewClientOptions): Promise<Client>;
|
|
9
10
|
function encrypt(client: Client, opts: EncryptOptions): Promise<Encrypted>;
|
|
10
11
|
function decrypt(client: Client, opts: DecryptOptions): Promise<JsPlaintext>;
|
|
11
12
|
function isEncrypted(encrypted: Encrypted): boolean;
|
|
@@ -16,7 +17,7 @@ declare module './load.cjs' {
|
|
|
16
17
|
function encryptQueryBulk(client: Client, opts: EncryptQueryBulkOptions): Promise<Encrypted[]>;
|
|
17
18
|
function ensureKeyset(opts: EnsureKeysetOpts): Promise<EnsureKeysetResult>;
|
|
18
19
|
}
|
|
19
|
-
export type ProtectErrorCode = 'INVARIANT_VIOLATION' | 'UNKNOWN_QUERY_OP' | 'UNKNOWN_COLUMN' | 'MISSING_INDEX' | 'INVALID_QUERY_INPUT' | 'INVALID_JSON_PATH' | 'STE_VEC_REQUIRES_JSON_CAST_AS' | 'UNKNOWN';
|
|
20
|
+
export type ProtectErrorCode = 'INVARIANT_VIOLATION' | 'UNKNOWN_QUERY_OP' | 'UNKNOWN_COLUMN' | 'MISSING_INDEX' | 'INVALID_QUERY_INPUT' | 'INVALID_JSON_PATH' | 'STE_VEC_REQUIRES_JSON_CAST_AS' | 'MATCH_REQUIRES_TEXT' | 'UNSUPPORTED_CONFIG_VERSION' | 'UNKNOWN';
|
|
20
21
|
export declare class ProtectError extends Error {
|
|
21
22
|
code: ProtectErrorCode;
|
|
22
23
|
details?: unknown;
|
|
@@ -67,69 +68,99 @@ export type Context = {
|
|
|
67
68
|
identityClaim: string[];
|
|
68
69
|
};
|
|
69
70
|
/**
|
|
70
|
-
* Represents
|
|
71
|
+
* Represents an EQL v2.3 payload returned by the FFI.
|
|
71
72
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* - `EqlCiphertext` (identifier + version + body)
|
|
75
|
-
* - `EqlCiphertextBody` (ciphertext + SEM fields + array flag)
|
|
76
|
-
* - `EqlSEM` (all searchable encrypted metadata fields)
|
|
73
|
+
* Discriminated union keyed on `k`. Narrow on `k` before accessing variant-only
|
|
74
|
+
* fields:
|
|
77
75
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* if (payload.k === 'sv') {
|
|
78
|
+
* payload.sv?.forEach(...)
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
80
81
|
*
|
|
81
|
-
*
|
|
82
|
+
* - `k: "ct"` — scalar payload (storage or query for `unique` / `match` / `ore`
|
|
83
|
+
* indexes). Storage payloads always carry `c`; query payloads omit `c` and
|
|
84
|
+
* carry exactly one of `hm`, `bf`, or `ob`.
|
|
85
|
+
* - `k: "sv"` — STE-vector payload. The FFI emits this for SteVec storage
|
|
86
|
+
* *and* for JSON containment queries (`ste_vec_term`), both of which carry
|
|
87
|
+
* per-selector entries in `sv` with the root document ciphertext at
|
|
88
|
+
* `sv[0].c`. Selector queries (`ste_vec_selector`) instead carry a single
|
|
89
|
+
* tokenized selector `s` and omit `sv`.
|
|
82
90
|
*/
|
|
83
|
-
export type Encrypted =
|
|
84
|
-
|
|
91
|
+
export type Encrypted = EncryptedScalar | EncryptedSteVec;
|
|
92
|
+
/** Scalar EQL v2.3 payload (`k: "ct"`). */
|
|
93
|
+
export type EncryptedScalar = {
|
|
94
|
+
k: 'ct';
|
|
95
|
+
/** EQL schema version */
|
|
96
|
+
v: number;
|
|
97
|
+
/** Table and column identifier */
|
|
85
98
|
i: {
|
|
86
99
|
t: string;
|
|
87
100
|
c: string;
|
|
88
101
|
};
|
|
89
|
-
/**
|
|
90
|
-
v: number;
|
|
91
|
-
/** The encrypted ciphertext (mp_base85 encoded, optional for query-mode payloads) */
|
|
102
|
+
/** Encrypted ciphertext (mp_base85). Required on storage payloads; absent on query payloads. */
|
|
92
103
|
c?: string;
|
|
93
|
-
/**
|
|
94
|
-
a?: boolean;
|
|
95
|
-
/** ORE block index for 64-bit integers */
|
|
96
|
-
ob?: string[];
|
|
97
|
-
/** Bloom filter for approximate match queries */
|
|
98
|
-
bf?: number[];
|
|
99
|
-
/** HMAC-SHA256 hash for exact matches */
|
|
104
|
+
/** HMAC-SHA256 hash — `unique` index term on storage, or `unique` lookup term on queries. */
|
|
100
105
|
hm?: string;
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
/** ORE CLLW fixed-width index for 64-bit values (SteVec) */
|
|
106
|
-
ocf?: string;
|
|
107
|
-
/** ORE CLLW variable-width index for strings (SteVec) */
|
|
108
|
-
ocv?: string;
|
|
109
|
-
/** Structured encryption vector entries (recursive) */
|
|
110
|
-
sv?: EqlCiphertextBody[];
|
|
106
|
+
/** Bloom filter (set bit positions) — `match` index term on storage, or `match` lookup term on queries. */
|
|
107
|
+
bf?: number[];
|
|
108
|
+
/** Block ORE u64_8_256 term — `ore` index term on storage, or `ore` comparison term on queries. */
|
|
109
|
+
ob?: string[];
|
|
111
110
|
};
|
|
112
111
|
/**
|
|
113
|
-
*
|
|
112
|
+
* STE-vector EQL v2.3 payload (`k: "sv"`). The FFI emits two disjoint shapes:
|
|
113
|
+
*
|
|
114
|
+
* - {@link EncryptedSteVecStorage} for storage encryption and JSON containment
|
|
115
|
+
* queries — carries a non-empty `sv` with the root ciphertext at `sv[0].c`.
|
|
116
|
+
* - {@link EncryptedSteVecSelector} for `ste_vec_selector` queries — carries
|
|
117
|
+
* only a tokenized selector `s`.
|
|
114
118
|
*/
|
|
115
|
-
export type
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
export type EncryptedSteVec = EncryptedSteVecStorage | EncryptedSteVecSelector;
|
|
120
|
+
/** SteVec storage payload (also used for containment queries). */
|
|
121
|
+
export type EncryptedSteVecStorage = {
|
|
122
|
+
k: 'sv';
|
|
123
|
+
v: number;
|
|
124
|
+
i: {
|
|
125
|
+
t: string;
|
|
126
|
+
c: string;
|
|
127
|
+
};
|
|
128
|
+
/** Per-selector entries; root document ciphertext lives at `sv[0].c`. */
|
|
129
|
+
sv: [SteVecEntry, ...SteVecEntry[]];
|
|
130
|
+
s?: never;
|
|
131
|
+
};
|
|
132
|
+
/** SteVec selector query payload (`ste_vec_selector`). */
|
|
133
|
+
export type EncryptedSteVecSelector = {
|
|
134
|
+
k: 'sv';
|
|
135
|
+
v: number;
|
|
136
|
+
i: {
|
|
137
|
+
t: string;
|
|
138
|
+
c: string;
|
|
139
|
+
};
|
|
140
|
+
/** Tokenized selector for path queries. */
|
|
141
|
+
s: string;
|
|
142
|
+
sv?: never;
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* One entry inside a SteVec payload (`k: "sv"`).
|
|
146
|
+
*
|
|
147
|
+
* Every element carries `s` (selector), `c` (entry ciphertext), and exactly one
|
|
148
|
+
* per-element equality / ordering term (`hm` or `oc`).
|
|
149
|
+
*/
|
|
150
|
+
export type SteVecEntry = {
|
|
151
|
+
/** Hex-encoded tokenized selector — deterministic per (path, key) */
|
|
152
|
+
s: string;
|
|
153
|
+
/** Per-entry encrypted record (mp_base85 encoded) */
|
|
154
|
+
c: string;
|
|
155
|
+
/** Array marker — true when the selector points at a JSON array context */
|
|
119
156
|
a?: boolean;
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
ocv?: string;
|
|
128
|
-
/** Nested SteVec entries (for deeply nested JSON) */
|
|
129
|
-
sv?: EqlCiphertextBody[];
|
|
130
|
-
};
|
|
131
|
-
/** @deprecated Use EqlCiphertextBody instead */
|
|
132
|
-
export type SteVecEntry = EqlCiphertextBody;
|
|
157
|
+
/** Per-entry HMAC term for non-orderable leaves (objects, arrays, booleans, null) */
|
|
158
|
+
hm?: string;
|
|
159
|
+
/** Per-entry CLLW ORE term for orderable leaves (strings, numbers) — Standard mode */
|
|
160
|
+
oc?: string;
|
|
161
|
+
};
|
|
162
|
+
/** @deprecated Use SteVecEntry instead */
|
|
163
|
+
export type EqlCiphertextBody = SteVecEntry;
|
|
133
164
|
export type EncryptConfig = {
|
|
134
165
|
v: number;
|
|
135
166
|
tables: Record<string, Record<string, Column>>;
|
|
@@ -170,10 +201,19 @@ export type ArrayIndexMode = 'all' | 'none' | {
|
|
|
170
201
|
wildcard?: boolean;
|
|
171
202
|
position?: boolean;
|
|
172
203
|
};
|
|
204
|
+
/**
|
|
205
|
+
* Encoding mode for SteVec indexes.
|
|
206
|
+
*
|
|
207
|
+
* - `standard`: standard encoding (default).
|
|
208
|
+
* - `compat`: backwards-compatible encoding. Set explicitly to preserve the
|
|
209
|
+
* pre-0.34.1-alpha.7 behaviour.
|
|
210
|
+
*/
|
|
211
|
+
export type SteVecMode = 'compat' | 'standard';
|
|
173
212
|
export type SteVecIndexOpts = {
|
|
174
213
|
prefix: string;
|
|
175
214
|
term_filters?: TokenFilter[];
|
|
176
215
|
array_index_mode?: ArrayIndexMode;
|
|
216
|
+
mode?: SteVecMode;
|
|
177
217
|
};
|
|
178
218
|
export type Tokenizer = {
|
|
179
219
|
kind: 'standard';
|
|
@@ -188,6 +228,11 @@ export type NewClientOptions = {
|
|
|
188
228
|
encryptConfig: EncryptConfig;
|
|
189
229
|
clientOpts?: ClientOpts;
|
|
190
230
|
};
|
|
231
|
+
/** Options passed to the native `newClient` after vocabulary normalization. */
|
|
232
|
+
type NativeNewClientOptions = {
|
|
233
|
+
encryptConfig: NativeEncryptConfig;
|
|
234
|
+
clientOpts?: ClientOpts;
|
|
235
|
+
};
|
|
191
236
|
export type ClientOpts = CredentialOpts & {
|
|
192
237
|
keyset?: KeysetIdentifier;
|
|
193
238
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Column, EncryptConfig } from './index.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* The `cast_as` vocabulary the native addon (cipherstash-config's
|
|
4
|
+
* `CanonicalEncryptionConfig`) accepts. The public `CastAs` union contains
|
|
5
|
+
* three JS-only members (`string`, `number`, `bigint`) that are remapped to
|
|
6
|
+
* their canonical equivalents (`text`, `float`, `big_int`) before being
|
|
7
|
+
* handed to the native side.
|
|
8
|
+
*/
|
|
9
|
+
export type NativeCastAs = 'text' | 'float' | 'big_int' | 'boolean' | 'date' | 'json' | 'timestamp';
|
|
10
|
+
/** A column after normalization — `cast_as` is in the canonical vocabulary. */
|
|
11
|
+
export type NativeColumn = Omit<Column, 'cast_as'> & {
|
|
12
|
+
cast_as?: NativeCastAs;
|
|
13
|
+
};
|
|
14
|
+
/** An encrypt config in the vocabulary the native addon expects. */
|
|
15
|
+
export type NativeEncryptConfig = Omit<EncryptConfig, 'tables'> & {
|
|
16
|
+
tables: Record<string, Record<string, NativeColumn>>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Translate a public `EncryptConfig` into the vocabulary the native addon
|
|
20
|
+
* expects:
|
|
21
|
+
*
|
|
22
|
+
* - `cast_as` values `string`/`number`/`bigint` become `text`/`float`/`big_int`.
|
|
23
|
+
* - `ste_vec` indexes without an explicit `array_index_mode` default to
|
|
24
|
+
* `'none'` — the library would otherwise default to `'all'`.
|
|
25
|
+
*
|
|
26
|
+
* `mode` is intentionally left untouched: an omitted `mode` follows the
|
|
27
|
+
* library default (`standard`). The input config is never mutated.
|
|
28
|
+
*/
|
|
29
|
+
export declare function normalizeEncryptConfig(config: EncryptConfig): NativeEncryptConfig;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeEncryptConfig = normalizeEncryptConfig;
|
|
4
|
+
/**
|
|
5
|
+
* The native addon uses a different `cast_as` vocabulary than the public JS
|
|
6
|
+
* API. These three JS values have no direct equivalent and are remapped to
|
|
7
|
+
* their canonical names.
|
|
8
|
+
*/
|
|
9
|
+
const CAST_AS_REMAP = {
|
|
10
|
+
string: 'text',
|
|
11
|
+
number: 'float',
|
|
12
|
+
bigint: 'big_int',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Translate a public `EncryptConfig` into the vocabulary the native addon
|
|
16
|
+
* expects:
|
|
17
|
+
*
|
|
18
|
+
* - `cast_as` values `string`/`number`/`bigint` become `text`/`float`/`big_int`.
|
|
19
|
+
* - `ste_vec` indexes without an explicit `array_index_mode` default to
|
|
20
|
+
* `'none'` — the library would otherwise default to `'all'`.
|
|
21
|
+
*
|
|
22
|
+
* `mode` is intentionally left untouched: an omitted `mode` follows the
|
|
23
|
+
* library default (`standard`). The input config is never mutated.
|
|
24
|
+
*/
|
|
25
|
+
function normalizeEncryptConfig(config) {
|
|
26
|
+
const tables = {};
|
|
27
|
+
for (const [tableName, columns] of Object.entries(config.tables)) {
|
|
28
|
+
const normalizedColumns = {};
|
|
29
|
+
for (const [columnName, column] of Object.entries(columns)) {
|
|
30
|
+
normalizedColumns[columnName] = normalizeColumn(column);
|
|
31
|
+
}
|
|
32
|
+
tables[tableName] = normalizedColumns;
|
|
33
|
+
}
|
|
34
|
+
return { ...config, tables };
|
|
35
|
+
}
|
|
36
|
+
function normalizeColumn(column) {
|
|
37
|
+
const { cast_as, indexes, ...rest } = column;
|
|
38
|
+
const normalized = { ...rest };
|
|
39
|
+
if (cast_as !== undefined) {
|
|
40
|
+
normalized.cast_as = remapCastAs(cast_as);
|
|
41
|
+
}
|
|
42
|
+
const steVec = indexes?.ste_vec;
|
|
43
|
+
if (indexes !== undefined) {
|
|
44
|
+
normalized.indexes = indexes;
|
|
45
|
+
}
|
|
46
|
+
if (steVec !== undefined && steVec.array_index_mode === undefined) {
|
|
47
|
+
normalized.indexes = {
|
|
48
|
+
...indexes,
|
|
49
|
+
ste_vec: { ...steVec, array_index_mode: 'none' },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
function remapCastAs(value) {
|
|
55
|
+
if (value in CAST_AS_REMAP) {
|
|
56
|
+
return CAST_AS_REMAP[value];
|
|
57
|
+
}
|
|
58
|
+
// The remaining `CastAs` members are already canonical `NativeCastAs` values.
|
|
59
|
+
return value;
|
|
60
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cipherstash/protect-ffi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/index.cjs",
|
|
6
6
|
"scripts": {
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"vite": "^8.0.5"
|
|
76
76
|
},
|
|
77
77
|
"optionalDependencies": {
|
|
78
|
-
"@cipherstash/protect-ffi-darwin-x64": "0.
|
|
79
|
-
"@cipherstash/protect-ffi-darwin-arm64": "0.
|
|
80
|
-
"@cipherstash/protect-ffi-win32-x64-msvc": "0.
|
|
81
|
-
"@cipherstash/protect-ffi-linux-x64-gnu": "0.
|
|
82
|
-
"@cipherstash/protect-ffi-linux-arm64-gnu": "0.
|
|
83
|
-
"@cipherstash/protect-ffi-linux-x64-musl": "0.
|
|
78
|
+
"@cipherstash/protect-ffi-darwin-x64": "0.22.0",
|
|
79
|
+
"@cipherstash/protect-ffi-darwin-arm64": "0.22.0",
|
|
80
|
+
"@cipherstash/protect-ffi-win32-x64-msvc": "0.22.0",
|
|
81
|
+
"@cipherstash/protect-ffi-linux-x64-gnu": "0.22.0",
|
|
82
|
+
"@cipherstash/protect-ffi-linux-arm64-gnu": "0.22.0",
|
|
83
|
+
"@cipherstash/protect-ffi-linux-x64-musl": "0.22.0"
|
|
84
84
|
}
|
|
85
85
|
}
|