asherah 4.0.40 → 4.0.42

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.
Files changed (3) hide show
  1. package/index.d.ts +40 -4
  2. package/npm/index.js +25 -4
  3. package/package.json +13 -12
package/index.d.ts CHANGED
@@ -169,7 +169,7 @@ export type AsherahConfigCompat = {
169
169
  * serviceName: 'my-svc',
170
170
  * productId: 'my-prod',
171
171
  * metastore: 'memory', // production: 'rdbms' or 'dynamodb'
172
- * kms: 'static', // production: 'aws'
172
+ * kms: 'test-debug-static', // production: 'aws'
173
173
  * });
174
174
  * ```
175
175
  *
@@ -230,11 +230,20 @@ export declare function setenv(env: string): void;
230
230
  * const drr = asherah.encrypt('user-42', Buffer.from('secret'));
231
231
  * // store drr in your database
232
232
  * ```
233
+ *
234
+ * **Blocking the Node.js event loop**: this function runs on the JS
235
+ * thread and synchronously waits for the metastore (MySQL/Postgres/
236
+ * DynamoDB) and KMS round-trips. While it executes no other JavaScript
237
+ * — timers, network callbacks, GC scheduling — can run. For any
238
+ * production workload that touches the network on a cache miss, prefer
239
+ * {@link encryptAsync}, which offloads to the Rust tokio runtime and
240
+ * keeps the event loop responsive.
233
241
  */
234
242
  export declare function encrypt(partitionId: string, data: Buffer): string;
235
243
 
236
244
  /** Async variant of {@link encrypt}. The work runs on the Rust tokio
237
- * runtime; the Node event loop is NOT blocked. */
245
+ * runtime; the Node event loop is NOT blocked. Prefer this in any
246
+ * service that handles concurrent requests. */
238
247
  export declare function encryptAsync(partitionId: string, data: Buffer): Promise<string>;
239
248
 
240
249
  /**
@@ -248,10 +257,16 @@ export declare function encryptAsync(partitionId: string, data: Buffer): Promise
248
257
  * @throws TypeError if either argument is null/undefined.
249
258
  * @throws Error if the JSON is malformed, the partition doesn't match,
250
259
  * the parent key has been revoked, or the AEAD tag fails.
260
+ *
261
+ * **Blocking the Node.js event loop**: like {@link encrypt}, this runs
262
+ * synchronously on the JS thread and waits for any metastore/KMS calls.
263
+ * Use {@link decryptAsync} in services that handle concurrent traffic.
251
264
  */
252
265
  export declare function decrypt(partitionId: string, dataRowRecordJson: string): Buffer;
253
266
 
254
- /** Async variant of {@link decrypt}. */
267
+ /** Async variant of {@link decrypt}. The work runs on the Rust tokio
268
+ * runtime; the Node event loop stays responsive. Prefer this in any
269
+ * service that handles concurrent requests. */
255
270
  export declare function decryptAsync(partitionId: string, dataRowRecordJson: string): Promise<Buffer>;
256
271
 
257
272
  /** UTF-8 string-typed wrapper around {@link encrypt}. Empty `string`
@@ -267,6 +282,27 @@ export declare function decryptString(partitionId: string, dataRowRecordJson: st
267
282
  /** Async variant of {@link decryptString}. */
268
283
  export declare function decryptStringAsync(partitionId: string, dataRowRecordJson: string): Promise<string>;
269
284
 
285
+ /**
286
+ * Best-effort wipe of a plaintext `Buffer` returned by {@link decrypt}
287
+ * or {@link decryptAsync}.
288
+ *
289
+ * The native (Rust) buffer is volatile-wiped before the Buffer reaches
290
+ * JavaScript, but the V8 `Buffer` lives on the JS heap where the GC may
291
+ * have already copied the bytes during compaction. Pass the Buffer here
292
+ * to overwrite its current backing store with zeros via
293
+ * `buffer.fill(0)`. Mirrors `Zeroize([]byte)` in the Go binding,
294
+ * `AsherahSession.ZeroizePlaintext(byte[])` in .NET, and
295
+ * `Asherah.clearPlaintext(byte[])` in Java.
296
+ *
297
+ * **Caveat:** treat the plaintext as opaque within a tight scope and
298
+ * never copy or substring it before clearing — V8 may have aliased
299
+ * portions of the underlying ArrayBuffer that this call doesn't reach.
300
+ *
301
+ * T-finding "Node has no parallel `clearPlaintext`" in
302
+ * `docs/review-2026-05-05-findings.md`.
303
+ */
304
+ export declare function clearPlaintext(plaintext: Buffer | null | undefined): void;
305
+
270
306
  // ─── Factory / Session API (recommended) ────────────────────────────────────
271
307
 
272
308
  /**
@@ -280,7 +316,7 @@ export declare function decryptStringAsync(partitionId: string, dataRowRecordJso
280
316
  * serviceName: 'my-svc',
281
317
  * productId: 'my-prod',
282
318
  * metastore: 'memory',
283
- * kms: 'static',
319
+ * kms: 'test-debug-static',
284
320
  * });
285
321
  * const session = factory.getSession('user-42');
286
322
  * try {
package/npm/index.js CHANGED
@@ -152,10 +152,13 @@ const METASTORE_ALIASES = {
152
152
  'test-debug-static': 'static',
153
153
  };
154
154
 
155
- // Legacy/debug KMS aliases
156
- const KMS_ALIASES = {
157
- 'test-debug-static': 'static',
158
- };
155
+ // Legacy/debug KMS aliases.
156
+ //
157
+ // The Rust core treats `static` and `test-debug-static` as synonyms
158
+ // — both fall back to the publicly known test key when no hex is
159
+ // supplied — so no JS-side normalization is needed. The map is kept
160
+ // as a hook for future aliases.
161
+ const KMS_ALIASES = {};
159
162
 
160
163
  function normalizeConfig(config) {
161
164
  if (!config || typeof config !== 'object') {
@@ -264,6 +267,23 @@ function decryptAsync(partitionId, dataRowRecord) {
264
267
  return native.decryptAsync(partitionId, toBuffer(dataRowRecord));
265
268
  }
266
269
 
270
+ // Best-effort wipe of a plaintext Buffer. The native (Rust) buffer is
271
+ // volatile-wiped before reaching JS; this clears the JS-side copy.
272
+ // V8 may have aliased portions of the ArrayBuffer that this call
273
+ // doesn't reach, so the wipe is best-effort. Mirrors Go's `Zeroize`,
274
+ // .NET's `ZeroizePlaintext`, and Java's `clearPlaintext`. T-finding
275
+ // "Node has no parallel clearPlaintext" in
276
+ // docs/review-2026-05-05-findings.md.
277
+ function clearPlaintext(plaintext) {
278
+ if (plaintext == null) {
279
+ return;
280
+ }
281
+ if (typeof plaintext.fill !== 'function') {
282
+ throw new TypeError('clearPlaintext: argument must be a Buffer or Uint8Array');
283
+ }
284
+ plaintext.fill(0);
285
+ }
286
+
267
287
  // Export everything from native addon
268
288
  Object.assign(module.exports, native);
269
289
 
@@ -272,6 +292,7 @@ module.exports.setup = setup;
272
292
  module.exports.setupAsync = setupAsync;
273
293
  module.exports.decrypt = decrypt;
274
294
  module.exports.decryptAsync = decryptAsync;
295
+ module.exports.clearPlaintext = clearPlaintext;
275
296
 
276
297
  // snake_case aliases for canonical API compatibility
277
298
  module.exports.setup_async = setupAsync;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asherah",
3
- "version": "4.0.40",
3
+ "version": "4.0.42",
4
4
  "private": false,
5
5
  "description": "Asherah application-layer encryption for Node.js with automatic key rotation, powered by the native Rust implementation.",
6
6
  "author": "Jay Gowdy",
@@ -57,11 +57,12 @@
57
57
  "scripts": {
58
58
  "build": "napi build",
59
59
  "build:release": "napi build --release",
60
- "test": "node test/roundtrip.js && node test/e2e-consumer.js",
61
- "test:unit": "node test/roundtrip.js",
60
+ "test": "node test/roundtrip.js && node test/rotation.js && node test/e2e-consumer.js",
61
+ "test:unit": "node test/roundtrip.js && node test/rotation.js",
62
+ "test:rotation": "node test/rotation.js",
62
63
  "test:e2e": "node test/e2e-consumer.js",
63
64
  "test:e2e-aws": "node test/e2e-aws.js",
64
- "test:bun": "bun test/roundtrip.js && bun test/e2e-consumer.js && bun test/bun-compat.js"
65
+ "test:bun": "bun test/roundtrip.js && bun test/rotation.js && bun test/e2e-consumer.js && bun test/bun-compat.js"
65
66
  },
66
67
  "devDependencies": {
67
68
  "@napi-rs/cli": "^2.18.0"
@@ -70,13 +71,13 @@
70
71
  "node": ">= 18"
71
72
  },
72
73
  "optionalDependencies": {
73
- "asherah-darwin-arm64": "4.0.40",
74
- "asherah-darwin-x64": "4.0.40",
75
- "asherah-linux-x64-gnu": "4.0.40",
76
- "asherah-linux-arm64-gnu": "4.0.40",
77
- "asherah-linux-x64-musl": "4.0.40",
78
- "asherah-linux-arm64-musl": "4.0.40",
79
- "asherah-windows-x64": "4.0.40",
80
- "asherah-windows-arm64": "4.0.40"
74
+ "asherah-darwin-arm64": "4.0.42",
75
+ "asherah-darwin-x64": "4.0.42",
76
+ "asherah-linux-x64-gnu": "4.0.42",
77
+ "asherah-linux-arm64-gnu": "4.0.42",
78
+ "asherah-linux-x64-musl": "4.0.42",
79
+ "asherah-linux-arm64-musl": "4.0.42",
80
+ "asherah-windows-x64": "4.0.42",
81
+ "asherah-windows-arm64": "4.0.42"
81
82
  }
82
83
  }