@cipherstash/protect-ffi 0.27.0 → 0.28.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/LICENSE.md +1 -1
- package/README.md +72 -16
- package/dist/wasm/protect_ffi_bg.js +44 -2
- package/dist/wasm/protect_ffi_bg.wasm +0 -0
- package/dist/wasm/protect_ffi_bg.wasm.d.ts +2 -2
- package/dist/wasm/protect_ffi_inline.js +1 -1
- package/lib/bigintWire.d.ts +56 -0
- package/lib/bigintWire.js +83 -0
- package/lib/index.cjs +11 -4
- package/lib/index.d.cts +23 -1
- package/package.json +11 -11
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CipherStash Client FFI (`@cipherstash/protect-ffi`)
|
|
2
2
|
|
|
3
3
|
> [!IMPORTANT]
|
|
4
|
-
> If you are looking to implement this package into your application please use the official [
|
|
4
|
+
> If you are looking to implement this package into your application please use the official [`@cipherstash/stack` package](https://github.com/cipherstash/stack).
|
|
5
5
|
|
|
6
6
|
This project provides the JS bindings for the CipherStash Client Rust SDK and is bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
|
|
7
7
|
|
|
@@ -19,19 +19,18 @@ This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli)
|
|
|
19
19
|
|
|
20
20
|
## Local setup
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
You will be prompted to sign in or create an account and follow steps to create a keyset and client key.
|
|
22
|
+
Authenticate the `stash` CLI to set up your local environment. It runs via `npx` — no separate install needed:
|
|
25
23
|
|
|
26
24
|
```sh
|
|
27
|
-
|
|
28
|
-
stash setup
|
|
25
|
+
npx stash auth login
|
|
29
26
|
```
|
|
30
27
|
|
|
28
|
+
You will be prompted to sign in or create an account.
|
|
29
|
+
|
|
31
30
|
## Exploring
|
|
32
31
|
|
|
33
32
|
After building `protect-ffi`, you can explore its exports at the Node console.
|
|
34
|
-
|
|
33
|
+
Credentials must be available for the call to `newClient()` to succeed — resolved from the CipherStash profile store, from environment variables, or from an explicit `strategy` passed to `newClient()`. For local development the simplest option is `npx stash auth login`, which populates the profile store. Alternatively, set `CS_WORKSPACE_CRN`, `CS_ACCESS_KEY`, and the `CS_CLIENT_ID`/`CS_CLIENT_KEY` keypair in your environment.
|
|
35
34
|
|
|
36
35
|
```sh
|
|
37
36
|
$ npm i
|
|
@@ -91,8 +90,8 @@ Notes:
|
|
|
91
90
|
- v3 query encryption currently supports JSON containment only; scalar and
|
|
92
91
|
selector queries throw `EQL_V3_QUERY_UNSUPPORTED` and need an
|
|
93
92
|
`eqlVersion: 2` client.
|
|
94
|
-
- `ope`-indexed columns map to `<family>_ord_ope`
|
|
95
|
-
|
|
93
|
+
- `ope`-indexed columns map to `<family>_ord_ope` and carry the `op`
|
|
94
|
+
(CLLW-OPE) term (emitted since cipherstash-client 0.38.1).
|
|
96
95
|
|
|
97
96
|
> [!NOTE]
|
|
98
97
|
> **Breaking TypeScript change:** `encrypt`/`encryptBulk` now return
|
|
@@ -104,6 +103,55 @@ Notes:
|
|
|
104
103
|
> `payload.v === 3` to detect the v3 members. (A bare `payload.k === 'ct'`
|
|
105
104
|
> does not compile against the union.)
|
|
106
105
|
|
|
106
|
+
## BigInt plaintexts
|
|
107
|
+
|
|
108
|
+
Encrypted `cast_as: 'bigint'` columns store signed 64-bit integers
|
|
109
|
+
(PostgreSQL `bigint`). `encrypt` / `encryptBulk` / `encryptQuery` /
|
|
110
|
+
`encryptQueryBulk` accept the plaintext as either a JS `number` or a JS
|
|
111
|
+
`bigint`:
|
|
112
|
+
|
|
113
|
+
- `bigint` inputs are exact and bounds-checked against the full i64 range:
|
|
114
|
+
**-9223372036854775808 to 9223372036854775807** (-2^63 to 2^63 - 1).
|
|
115
|
+
Values outside that range throw a `RangeError` (a plain `RangeError`, not
|
|
116
|
+
a `ProtectError`) naming the bounds and the offending direction. Search
|
|
117
|
+
index terms (`hm`, `ob`, `op`) are derived from the same value, so the
|
|
118
|
+
boundary check covers index-term generation too.
|
|
119
|
+
- `number` inputs keep the existing exact-integer guard: fractional,
|
|
120
|
+
non-finite, or out-of-range values error instead of being silently
|
|
121
|
+
truncated.
|
|
122
|
+
- A `bigint` is only accepted as the top-level plaintext of a scalar
|
|
123
|
+
column. JSON has no bigint, so bigints nested inside `cast_as: 'json'`
|
|
124
|
+
documents (or JSON containment query terms) are rejected with a
|
|
125
|
+
`TypeError` on both Neon and wasm. More generally, plaintexts follow
|
|
126
|
+
`JSON.stringify` semantics on both platforms: `toJSON` is honored (a
|
|
127
|
+
`Date` becomes its ISO string), `undefined` properties are dropped, and
|
|
128
|
+
non-finite numbers become `null`.
|
|
129
|
+
- The internal wire form `{"__protect_ffi_bigint__": "<digits>"}` is
|
|
130
|
+
reserved: a `cast_as: 'json'` plaintext consisting of exactly that
|
|
131
|
+
single-key shape (with an in-range i64 decimal string) is read as a
|
|
132
|
+
bigint at the boundary and rejected for json columns. Nested
|
|
133
|
+
occurrences of the key inside a larger document are unaffected.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const ciphertext = await addon.encrypt(client, {
|
|
137
|
+
plaintext: 9007199254740993n, // beyond Number.MAX_SAFE_INTEGER — stays exact
|
|
138
|
+
column: 'score',
|
|
139
|
+
table: 'users',
|
|
140
|
+
})
|
|
141
|
+
const decrypted = await addon.decrypt(client, { ciphertext })
|
|
142
|
+
// decrypted === 9007199254740993n (a JS bigint)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> [!WARNING]
|
|
146
|
+
> **Breaking change:** `decrypt` / `decryptBulk` / `decryptBulkFallible`
|
|
147
|
+
> now ALWAYS return a JS `bigint` for `cast_as: 'bigint'` columns — even
|
|
148
|
+
> for values that fit in a JS number. Previous releases returned a
|
|
149
|
+
> `number`, silently losing precision beyond `Number.MAX_SAFE_INTEGER`
|
|
150
|
+
> (2^53 - 1). Code comparing or doing arithmetic on decrypted bigint
|
|
151
|
+
> columns must be updated (e.g. `decrypted === 123n` instead of
|
|
152
|
+
> `decrypted === 123`, or `Number(decrypted)` when the value is known to
|
|
153
|
+
> be small).
|
|
154
|
+
|
|
107
155
|
## Errors
|
|
108
156
|
|
|
109
157
|
Async API calls throw `ProtectError` with a stable `code` for programmatic handling.
|
|
@@ -151,8 +199,7 @@ Initiate a dry run of a patch release of this library via GitHub Actions. This p
|
|
|
151
199
|
|
|
152
200
|
#### `npm test`
|
|
153
201
|
|
|
154
|
-
|
|
155
|
-
Also runs Rust tests.
|
|
202
|
+
Typechecks the TypeScript and runs the unit tests (tsc + vitest), formats and lints Rust and TypeScript code, and runs Rust tests.
|
|
156
203
|
|
|
157
204
|
Note: `npm test` at project root does not run integration tests.
|
|
158
205
|
For integration tests, see [below](#integration-tests).
|
|
@@ -175,6 +222,8 @@ protect-ffi/
|
|
|
175
222
|
| └── src/
|
|
176
223
|
| └── lib.rs
|
|
177
224
|
├── platforms/
|
|
225
|
+
├── docs/
|
|
226
|
+
├── scripts/
|
|
178
227
|
├── package.json
|
|
179
228
|
└── target/
|
|
180
229
|
```
|
|
@@ -191,6 +240,8 @@ protect-ffi/
|
|
|
191
240
|
| `crates/` | The directory tree containing the Rust source code for the project. |
|
|
192
241
|
| `lib.rs` | Entry point for the Rust source code. |
|
|
193
242
|
| `platforms/` | The directory containing distributions of the binary addon backend for each platform supported by this library. |
|
|
243
|
+
| `docs/` | JSONB integration docs and migration notes. |
|
|
244
|
+
| `scripts/` | Build helper scripts (e.g. WASM inlining). |
|
|
194
245
|
| `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. |
|
|
195
246
|
| `target/` | Binary artifacts generated by the Rust build. |
|
|
196
247
|
|
|
@@ -208,9 +259,11 @@ Example environment variables:
|
|
|
208
259
|
```
|
|
209
260
|
CS_CLIENT_ID=
|
|
210
261
|
CS_CLIENT_KEY=
|
|
262
|
+
# The integration tests read CS_CLIENT_ACCESS_KEY; the library itself reads CS_ACCESS_KEY
|
|
211
263
|
CS_CLIENT_ACCESS_KEY=
|
|
212
264
|
CS_WORKSPACE_CRN=
|
|
213
|
-
|
|
265
|
+
# Must match the port used by the integration-test setup (mise.toml)
|
|
266
|
+
PGPORT=5436
|
|
214
267
|
PGDATABASE=cipherstash
|
|
215
268
|
PGUSER=cipherstash
|
|
216
269
|
PGPASSWORD=password
|
|
@@ -249,7 +302,7 @@ The release workflow is responsible for:
|
|
|
249
302
|
|
|
250
303
|
To perform a release:
|
|
251
304
|
|
|
252
|
-
1. Navigate to the ["Release" workflow page](https://github.com/cipherstash/
|
|
305
|
+
1. Navigate to the ["Release" workflow page](https://github.com/cipherstash/protectjs-ffi/actions/workflows/release.yml) in GitHub.
|
|
253
306
|
1. Click on "Run workflow".
|
|
254
307
|
1. Select the branch to release from.
|
|
255
308
|
Use the default of `main` unless you want to do a pre-release version or dry run from a branch.
|
|
@@ -260,8 +313,11 @@ To perform a release:
|
|
|
260
313
|
Select "custom" in the dropdown and fill in the "Custom version" text box if you want to use a semver string instead of the shorthand (patch, minor, major, etc.).
|
|
261
314
|
1. Click "Run workflow".
|
|
262
315
|
|
|
263
|
-
|
|
264
|
-
|
|
316
|
+
Release notes and the changelog are automated. Add notes for your changes under the
|
|
317
|
+
`[Unreleased]` heading in [`CHANGELOG.md`](./CHANGELOG.md). On release, the `version`
|
|
318
|
+
npm lifecycle hook promotes that section to a dated release entry
|
|
319
|
+
(`scripts/changelog-release.mjs`), and the workflow publishes the promoted section as
|
|
320
|
+
the GitHub release notes (`scripts/changelog-extract.mjs`).
|
|
265
321
|
|
|
266
322
|
## Learn More
|
|
267
323
|
|
|
@@ -247,6 +247,10 @@ export function newClient(opts) {
|
|
|
247
247
|
const ret = wasm.newClient(opts);
|
|
248
248
|
return ret;
|
|
249
249
|
}
|
|
250
|
+
export function __wbg_BigInt_7882274c8d2ac74c() { return handleError(function (arg0) {
|
|
251
|
+
const ret = BigInt(arg0);
|
|
252
|
+
return ret;
|
|
253
|
+
}, arguments); }
|
|
250
254
|
export function __wbg_Error_83742b46f01ce22d(arg0, arg1) {
|
|
251
255
|
const ret = Error(getStringFromWasm0(arg0, arg1));
|
|
252
256
|
return ret;
|
|
@@ -350,6 +354,10 @@ export function __wbg_arrayBuffer_eb8e9ca620af2a19() { return handleError(functi
|
|
|
350
354
|
const ret = arg0.arrayBuffer();
|
|
351
355
|
return ret;
|
|
352
356
|
}, arguments); }
|
|
357
|
+
export function __wbg_assign_2c1c53ce7e57ea6e(arg0, arg1) {
|
|
358
|
+
const ret = Object.assign(arg0, arg1);
|
|
359
|
+
return ret;
|
|
360
|
+
}
|
|
353
361
|
export function __wbg_buffer_60b8043cd926067d(arg0) {
|
|
354
362
|
const ret = arg0.buffer;
|
|
355
363
|
return ret;
|
|
@@ -473,6 +481,16 @@ export function __wbg_instanceof_Map_f194b366846aca0c(arg0) {
|
|
|
473
481
|
const ret = result;
|
|
474
482
|
return ret;
|
|
475
483
|
}
|
|
484
|
+
export function __wbg_instanceof_Object_be1962063fcc0c9f(arg0) {
|
|
485
|
+
let result;
|
|
486
|
+
try {
|
|
487
|
+
result = arg0 instanceof Object;
|
|
488
|
+
} catch (_) {
|
|
489
|
+
result = false;
|
|
490
|
+
}
|
|
491
|
+
const ret = result;
|
|
492
|
+
return ret;
|
|
493
|
+
}
|
|
476
494
|
export function __wbg_instanceof_Promise_7c3bdd7805c2c6e6(arg0) {
|
|
477
495
|
let result;
|
|
478
496
|
try {
|
|
@@ -551,6 +569,10 @@ export function __wbg_new_ab79df5bd7c26067() {
|
|
|
551
569
|
const ret = new Object();
|
|
552
570
|
return ret;
|
|
553
571
|
}
|
|
572
|
+
export function __wbg_new_aff07d10ed2fdd7f(arg0, arg1) {
|
|
573
|
+
const ret = new RangeError(getStringFromWasm0(arg0, arg1));
|
|
574
|
+
return ret;
|
|
575
|
+
}
|
|
554
576
|
export function __wbg_new_c518c60af666645b() { return handleError(function () {
|
|
555
577
|
const ret = new AbortController();
|
|
556
578
|
return ret;
|
|
@@ -605,6 +627,10 @@ export function __wbg_node_84ea875411254db1(arg0) {
|
|
|
605
627
|
const ret = arg0.node;
|
|
606
628
|
return ret;
|
|
607
629
|
}
|
|
630
|
+
export function __wbg_parse_e9eddd2a82c706eb() { return handleError(function (arg0, arg1) {
|
|
631
|
+
const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
|
|
632
|
+
return ret;
|
|
633
|
+
}, arguments); }
|
|
608
634
|
export function __wbg_process_44c7a14e11e9f69e(arg0) {
|
|
609
635
|
const ret = arg0.process;
|
|
610
636
|
return ret;
|
|
@@ -612,6 +638,10 @@ export function __wbg_process_44c7a14e11e9f69e(arg0) {
|
|
|
612
638
|
export function __wbg_prototypesetcall_d62e5099504357e6(arg0, arg1, arg2) {
|
|
613
639
|
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
|
614
640
|
}
|
|
641
|
+
export function __wbg_push_e87b0e732085a946(arg0, arg1) {
|
|
642
|
+
const ret = arg0.push(arg1);
|
|
643
|
+
return ret;
|
|
644
|
+
}
|
|
615
645
|
export function __wbg_queueMicrotask_0c399741342fb10f(arg0) {
|
|
616
646
|
const ret = arg0.queueMicrotask;
|
|
617
647
|
return ret;
|
|
@@ -643,6 +673,10 @@ export function __wbg_set_282384002438957f(arg0, arg1, arg2) {
|
|
|
643
673
|
export function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {
|
|
644
674
|
arg0[arg1] = arg2;
|
|
645
675
|
}
|
|
676
|
+
export function __wbg_set_7eaa4f96924fd6b3() { return handleError(function (arg0, arg1, arg2) {
|
|
677
|
+
const ret = Reflect.set(arg0, arg1, arg2);
|
|
678
|
+
return ret;
|
|
679
|
+
}, arguments); }
|
|
646
680
|
export function __wbg_set_8c0b3ffcf05d61c2(arg0, arg1, arg2) {
|
|
647
681
|
arg0.set(getArrayU8FromWasm0(arg1, arg2));
|
|
648
682
|
}
|
|
@@ -702,6 +736,10 @@ export function __wbg_status_318629ab93a22955(arg0) {
|
|
|
702
736
|
const ret = arg0.status;
|
|
703
737
|
return ret;
|
|
704
738
|
}
|
|
739
|
+
export function __wbg_stringify_5ae93966a84901ac() { return handleError(function (arg0) {
|
|
740
|
+
const ret = JSON.stringify(arg0);
|
|
741
|
+
return ret;
|
|
742
|
+
}, arguments); }
|
|
705
743
|
export function __wbg_subarray_a068d24e39478a8a(arg0, arg1, arg2) {
|
|
706
744
|
const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
|
|
707
745
|
return ret;
|
|
@@ -718,6 +756,10 @@ export function __wbg_then_9e335f6dd892bc11(arg0, arg1, arg2) {
|
|
|
718
756
|
const ret = arg0.then(arg1, arg2);
|
|
719
757
|
return ret;
|
|
720
758
|
}
|
|
759
|
+
export function __wbg_toString_68d455d069f7169a() { return handleError(function (arg0, arg1) {
|
|
760
|
+
const ret = arg0.toString(arg1);
|
|
761
|
+
return ret;
|
|
762
|
+
}, arguments); }
|
|
721
763
|
export function __wbg_url_7fefc1820fba4e0c(arg0, arg1) {
|
|
722
764
|
const ret = arg1.url;
|
|
723
765
|
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
@@ -742,12 +784,12 @@ export function __wbg_wasmclient_new(arg0) {
|
|
|
742
784
|
return ret;
|
|
743
785
|
}
|
|
744
786
|
export function __wbindgen_cast_0000000000000001(arg0, arg1) {
|
|
745
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
787
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 893, function: Function { arguments: [], shim_idx: 894, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
746
788
|
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hd9dedd770edb0aa1, wasm_bindgen__convert__closures_____invoke__h8b170b34f8b593df);
|
|
747
789
|
return ret;
|
|
748
790
|
}
|
|
749
791
|
export function __wbindgen_cast_0000000000000002(arg0, arg1) {
|
|
750
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
792
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 946, function: Function { arguments: [Externref], shim_idx: 947, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
751
793
|
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h94971d029da3e81d, wasm_bindgen__convert__closures_____invoke__hacad64c6c6b78997);
|
|
752
794
|
return ret;
|
|
753
795
|
}
|
|
Binary file
|
|
@@ -30,11 +30,11 @@ export const wasm_bindgen__closure__destroy__h94971d029da3e81d: (a: number, b: n
|
|
|
30
30
|
export const wasm_bindgen__convert__closures_____invoke__hacad64c6c6b78997: (a: number, b: number, c: any) => [number, number];
|
|
31
31
|
export const wasm_bindgen__convert__closures_____invoke__h0cb80728b35f261e: (a: number, b: number, c: any, d: any) => void;
|
|
32
32
|
export const wasm_bindgen__convert__closures_____invoke__h8b170b34f8b593df: (a: number, b: number) => void;
|
|
33
|
-
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
34
|
-
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
35
33
|
export const __wbindgen_exn_store: (a: number) => void;
|
|
36
34
|
export const __externref_table_alloc: () => number;
|
|
37
35
|
export const __wbindgen_externrefs: WebAssembly.Table;
|
|
36
|
+
export const __wbindgen_malloc: (a: number, b: number) => number;
|
|
37
|
+
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
38
38
|
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
39
39
|
export const __externref_table_dealloc: (a: number) => void;
|
|
40
40
|
export const __wbindgen_start: () => void;
|