From e0eb6907a83232e632846f3b07eb631356fa7c84 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Tue, 22 Sep 2026 19:44:48 +1000 Subject: [PATCH 1/5] feat(encryption): surface hardware-acceleration detection (LAB-523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both bindings run the same cachekit-core AES probe that cachekit-py surfaces as hardware_acceleration_enabled, but neither exposed it, so the answer to "is AES hardware-accelerated here?" was unreachable from TypeScript. TenantKeys.hardwareAccelerationEnabled() on the NAPI and wasm bindings delegates to core; EncryptionManagerCore.isHardwareAccelerated() surfaces it, initialising on demand so it answers at startup, and returns null (unknown) rather than false when an older binding predates the accessor. On wasm32 it is honestly false — no AES instructions to detect. Informational only — crypto dispatch is unchanged. Co-Authored-By: Claude Fable 5.1 --- packages/cachekit-core-ts/index.d.ts | 10 ++++++ packages/cachekit-core-ts/src/lib.rs | 12 +++++++ packages/cachekit-core-wasm/index.d.ts | 5 +++ packages/cachekit-core-wasm/src/lib.rs | 9 ++++++ packages/cachekit/README.md | 19 ++++++++++++ .../src/encryption/manager-core.test.ts | 31 +++++++++++++++++++ .../cachekit/src/encryption/manager-core.ts | 25 +++++++++++++++ ...encryption-real-crypto.integration.test.ts | 5 +++ .../encryption.protocol.workers.test.ts | 2 ++ 9 files changed, 118 insertions(+) diff --git a/packages/cachekit-core-ts/index.d.ts b/packages/cachekit-core-ts/index.d.ts index 5a853d6..a165fa0 100644 --- a/packages/cachekit-core-ts/index.d.ts +++ b/packages/cachekit-core-ts/index.d.ts @@ -99,6 +99,16 @@ export declare class TenantKeys { * current key only, turning every pre-rotation entry into a miss. */ keyringEntryCount(): number + /** + * Whether cachekit-core detected AES hardware support on this host. + * + * Informational only — `ring` picks its AES implementation independently + * of this flag. Runtime AES-NI probe on x86/x86_64, compile-time target + * features on aarch64. Same signal as Python's + * `hardware_acceleration_enabled` and cachekit-rs's + * `EncryptionLayer::hardware_acceleration_enabled()`. + */ + hardwareAccelerationEnabled(): boolean } /** diff --git a/packages/cachekit-core-ts/src/lib.rs b/packages/cachekit-core-ts/src/lib.rs index bd8b80c..2b9e591 100644 --- a/packages/cachekit-core-ts/src/lib.rs +++ b/packages/cachekit-core-ts/src/lib.rs @@ -278,6 +278,18 @@ impl TenantKeys { pub fn keyring_entry_count(&self) -> u32 { self.keyring_entries } + + /// Whether cachekit-core detected AES hardware support on this host. + /// + /// Informational only — `ring` picks its AES implementation independently + /// of this flag. Runtime AES-NI probe on x86/x86_64, compile-time target + /// features on aarch64. Same signal as Python's + /// `hardware_acceleration_enabled` and cachekit-rs's + /// `EncryptionLayer::hardware_acceleration_enabled()`. + #[napi] + pub fn hardware_acceleration_enabled(&self) -> bool { + self.encryptor.hardware_acceleration_enabled() + } } /// Derive per-tenant keys using HKDF-SHA256. diff --git a/packages/cachekit-core-wasm/index.d.ts b/packages/cachekit-core-wasm/index.d.ts index e85d81e..83dc3b7 100644 --- a/packages/cachekit-core-wasm/index.d.ts +++ b/packages/cachekit-core-wasm/index.d.ts @@ -44,6 +44,11 @@ export declare class TenantKeys { * keys) — SDK attestation that rotation config survived the boundary. */ keyringEntryCount(): number; + /** + * Whether cachekit-core detected AES hardware support — always `false` on + * wasm32 (software `aes-gcm`); present so the handle matches the NAPI binding. + */ + hardwareAccelerationEnabled(): boolean; } /** Derive a 32-byte domain key using HKDF-SHA256 (RFC 5869). */ diff --git a/packages/cachekit-core-wasm/src/lib.rs b/packages/cachekit-core-wasm/src/lib.rs index a7a8f68..92aed9d 100644 --- a/packages/cachekit-core-wasm/src/lib.rs +++ b/packages/cachekit-core-wasm/src/lib.rs @@ -185,6 +185,15 @@ impl TenantKeys { pub fn keyring_entry_count(&self) -> u32 { self.keyring_entries } + + /// Whether cachekit-core detected AES hardware support. Always `false` on + /// wasm32 — no AES instructions to detect, `aes-gcm` runs in software — + /// exposed so the handle matches the NAPI binding and callers get an + /// honest answer instead of `undefined`. + #[wasm_bindgen(js_name = hardwareAccelerationEnabled)] + pub fn hardware_acceleration_enabled(&self) -> bool { + self.encryptor.hardware_acceleration_enabled() + } } /// Derive per-tenant keys using HKDF-SHA256. diff --git a/packages/cachekit/README.md b/packages/cachekit/README.md index 7739ce3..c7c76bd 100644 --- a/packages/cachekit/README.md +++ b/packages/cachekit/README.md @@ -454,6 +454,25 @@ With the CachekitIO backend, the `X-CacheKit-L1-*` telemetry headers are wired automatically from the cache's live L1/L2 hit and miss counters; pass your own `metricsProvider` in the backend config to override. +**Is AES hardware-accelerated on this host?** `isHardwareAccelerated()` on the +encryption manager reports cachekit-core's detection — a runtime AES-NI probe on +x86/x86_64, compile-time target features on aarch64, and always `false` on +Cloudflare Workers (wasm32 has no AES instructions; `aes-gcm` runs in software). +Informational: the crypto backend picks its implementation independently, so use +it to explain `.secure` latency on a host without AES instructions, not to change +behaviour. It initialises the bindings if needed, so it answers at startup, and +returns `null` (unknown) only when the installed native binding predates the +accessor. Same signal as cachekit-py's `hardware_acceleration_enabled` and +cachekit-rs's `hardware_acceleration_enabled()`. + +```typescript +import { EncryptionManager } from '@cachekit-io/cachekit'; + +const manager = new EncryptionManager(process.env.CACHEKIT_MASTER_KEY!, 'tenant-123'); +console.log('AES hardware acceleration:', await manager.isHardwareAccelerated()); +manager.dispose(); +``` + ## Cloudflare Workers The SDK ships a Workers-native entrypoint: `@cachekit-io/cachekit/workers` diff --git a/packages/cachekit/src/encryption/manager-core.test.ts b/packages/cachekit/src/encryption/manager-core.test.ts index 9949800..a4d6a3c 100644 --- a/packages/cachekit/src/encryption/manager-core.test.ts +++ b/packages/cachekit/src/encryption/manager-core.test.ts @@ -27,6 +27,7 @@ function mockBindings(overrides?: Partial) { encryptionFingerprint: () => new Uint8Array(16), getNonceCounter: () => 0, keyringEntryCount: () => 1 + (previousMasterKeys?.length ?? 0), + hardwareAccelerationEnabled: () => true, free() { freed.push(keys); }, @@ -247,4 +248,34 @@ describe('EncryptionManagerCore keyring config (previousMasterKeys)', () => { expect(freed.length).toBe(1); manager.dispose(); }); + + it('reports hardware acceleration from the binding, initialising on demand', async () => { + const { bindings, derived } = mockBindings(); + const manager = new TestManager(async () => bindings); + + // Answers at startup, before any encrypt — and derives exactly once. + expect(await manager.isHardwareAccelerated()).toBe(true); + expect(derived.length).toBe(1); + await manager.encrypt(new Uint8Array([1]), 'ns:k'); + expect(derived.length).toBe(1); + + manager.dispose(); + await expect(manager.isHardwareAccelerated()).rejects.toThrow(EncryptionError); + }); + + it('reports null (unknown), not false, when the binding predates the accessor', async () => { + const { bindings } = mockBindings(); + vi.mocked(bindings.deriveTenantKeys).mockImplementation( + (_masterKey: Uint8Array, tenantId: string) => ({ + tenantId, + encryptionFingerprint: () => new Uint8Array(16), + getNonceCounter: () => 0, + // no hardwareAccelerationEnabled — older binding + }) + ); + const manager = new TestManager(async () => bindings); + + expect(await manager.isHardwareAccelerated()).toBeNull(); + manager.dispose(); + }); }); diff --git a/packages/cachekit/src/encryption/manager-core.ts b/packages/cachekit/src/encryption/manager-core.ts index d741c64..65e9b5e 100644 --- a/packages/cachekit/src/encryption/manager-core.ts +++ b/packages/cachekit/src/encryption/manager-core.ts @@ -24,6 +24,13 @@ export interface EncryptionTenantKeys { * rather than silently decrypting with the current key only. */ keyringEntryCount?(): number; + /** + * Whether cachekit-core detected AES hardware support on this host + * (informational — the crypto backend dispatches independently). Optional + * because older binding binaries predate it; the manager reports `null` + * (unknown) rather than guessing `false`. + */ + hardwareAccelerationEnabled?(): boolean; /** * Deterministic zeroize-and-release (wasm bindings). NAPI handles zeroize * via GC finalizer instead and don't expose this. @@ -336,6 +343,24 @@ export class EncryptionManagerCore { return this.tenantKeys.encryptionFingerprint(); } + /** + * Whether AES-256-GCM is hardware-accelerated on this host, per + * cachekit-core's detection: a runtime AES-NI probe on x86/x86_64, + * compile-time target features on aarch64, always `false` on wasm32 + * (Workers). Informational — the crypto backend picks its implementation + * independently; use it to explain `.secure` latency, not to change + * behaviour. Initialises the bindings if needed, so it answers at startup + * before the first encrypt. `null` means unknown: the installed binding + * predates the accessor. Same signal as Python's + * `hardware_acceleration_enabled`. + * + * @throws {EncryptionError} if the manager is disposed or bindings fail to load + */ + async isHardwareAccelerated(): Promise { + await this.ensureInitialized(); + return this.tenantKeys!.hardwareAccelerationEnabled?.() ?? null; + } + /** * Dispose of the encryption manager and zeroize keys. * diff --git a/packages/cachekit/test/integration/encryption-real-crypto.integration.test.ts b/packages/cachekit/test/integration/encryption-real-crypto.integration.test.ts index 022a7df..24596ed 100644 --- a/packages/cachekit/test/integration/encryption-real-crypto.integration.test.ts +++ b/packages/cachekit/test/integration/encryption-real-crypto.integration.test.ts @@ -81,6 +81,11 @@ describe('Real Crypto Integration (No Mocks)', () => { expect(freshKeys.getNonceCounter()).toBe(0); }); + it('reports AES hardware acceleration as a boolean from the real binding', () => { + // The installed binding carries the accessor: a boolean, never undefined. + expect(typeof tenantKeys.hardwareAccelerationEnabled()).toBe('boolean'); + }); + it('produces different keys for different tenants', () => { const keys1 = deriveTenantKeys(TEST_MASTER_KEY, 'tenant-1'); const keys2 = deriveTenantKeys(TEST_MASTER_KEY, 'tenant-2'); diff --git a/packages/cachekit/test/workers/encryption.protocol.workers.test.ts b/packages/cachekit/test/workers/encryption.protocol.workers.test.ts index 79fd612..2d2de71 100644 --- a/packages/cachekit/test/workers/encryption.protocol.workers.test.ts +++ b/packages/cachekit/test/workers/encryption.protocol.workers.test.ts @@ -137,6 +137,8 @@ describe('encryption vectors — raw wasm bindings', () => { const plaintext = hexToBytes(vectors[0].plaintext_hex); expect(tk.getNonceCounter()).toBe(0); + // wasm32 has no AES instructions: detection is honestly false, never undefined. + expect(tk.hardwareAccelerationEnabled()).toBe(false); const ct1 = encryptWithTenantKeys(plaintext, aad, tk); expect(tk.getNonceCounter()).toBe(1); const ct2 = encryptWithTenantKeys(plaintext, aad, tk); From ad1de943bfb6c0f85773c033e6cb9820594d9077 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Tue, 22 Sep 2026 19:59:29 +1000 Subject: [PATCH 2/5] refactor(encryption): trim hardware-acceleration docs to SDK facts (LAB-523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review found the same per-architecture narrative restated across the bindings, the manager, and the README, and it mis-described aarch64: core tests NEON there, which every aarch64 target enables, so the flag is true on every aarch64 build regardless of the Crypto Extension. Binding and JSDoc comments now state only what the SDK owns — forwards core, informational, null means the binding predates the accessor — and the README carries one dated per-architecture line with the honest aarch64 caveat. The mock test drops two assertions already covered by the ensureInitialized() suite. --- packages/cachekit-core-ts/index.d.ts | 7 ++----- packages/cachekit-core-ts/src/lib.rs | 7 ++----- packages/cachekit-core-wasm/index.d.ts | 5 +---- packages/cachekit-core-wasm/src/lib.rs | 6 ++---- packages/cachekit/README.md | 17 ++++++++--------- .../src/encryption/manager-core.test.ts | 4 ---- .../cachekit/src/encryption/manager-core.ts | 17 +++++------------ 7 files changed, 20 insertions(+), 43 deletions(-) diff --git a/packages/cachekit-core-ts/index.d.ts b/packages/cachekit-core-ts/index.d.ts index a165fa0..e67c295 100644 --- a/packages/cachekit-core-ts/index.d.ts +++ b/packages/cachekit-core-ts/index.d.ts @@ -102,11 +102,8 @@ export declare class TenantKeys { /** * Whether cachekit-core detected AES hardware support on this host. * - * Informational only — `ring` picks its AES implementation independently - * of this flag. Runtime AES-NI probe on x86/x86_64, compile-time target - * features on aarch64. Same signal as Python's - * `hardware_acceleration_enabled` and cachekit-rs's - * `EncryptionLayer::hardware_acceleration_enabled()`. + * Forwards `ZeroKnowledgeEncryptor::hardware_acceleration_enabled()`. + * Informational only — `ring` dispatches independently of it. */ hardwareAccelerationEnabled(): boolean } diff --git a/packages/cachekit-core-ts/src/lib.rs b/packages/cachekit-core-ts/src/lib.rs index 2b9e591..1efece0 100644 --- a/packages/cachekit-core-ts/src/lib.rs +++ b/packages/cachekit-core-ts/src/lib.rs @@ -281,11 +281,8 @@ impl TenantKeys { /// Whether cachekit-core detected AES hardware support on this host. /// - /// Informational only — `ring` picks its AES implementation independently - /// of this flag. Runtime AES-NI probe on x86/x86_64, compile-time target - /// features on aarch64. Same signal as Python's - /// `hardware_acceleration_enabled` and cachekit-rs's - /// `EncryptionLayer::hardware_acceleration_enabled()`. + /// Forwards `ZeroKnowledgeEncryptor::hardware_acceleration_enabled()`. + /// Informational only — `ring` dispatches independently of it. #[napi] pub fn hardware_acceleration_enabled(&self) -> bool { self.encryptor.hardware_acceleration_enabled() diff --git a/packages/cachekit-core-wasm/index.d.ts b/packages/cachekit-core-wasm/index.d.ts index 83dc3b7..bc251b4 100644 --- a/packages/cachekit-core-wasm/index.d.ts +++ b/packages/cachekit-core-wasm/index.d.ts @@ -44,10 +44,7 @@ export declare class TenantKeys { * keys) — SDK attestation that rotation config survived the boundary. */ keyringEntryCount(): number; - /** - * Whether cachekit-core detected AES hardware support — always `false` on - * wasm32 (software `aes-gcm`); present so the handle matches the NAPI binding. - */ + /** cachekit-core's AES hardware detection — `false` on wasm32; matches the NAPI binding. */ hardwareAccelerationEnabled(): boolean; } diff --git a/packages/cachekit-core-wasm/src/lib.rs b/packages/cachekit-core-wasm/src/lib.rs index 92aed9d..f587dbb 100644 --- a/packages/cachekit-core-wasm/src/lib.rs +++ b/packages/cachekit-core-wasm/src/lib.rs @@ -186,10 +186,8 @@ impl TenantKeys { self.keyring_entries } - /// Whether cachekit-core detected AES hardware support. Always `false` on - /// wasm32 — no AES instructions to detect, `aes-gcm` runs in software — - /// exposed so the handle matches the NAPI binding and callers get an - /// honest answer instead of `undefined`. + /// Forwards cachekit-core's AES hardware detection — `false` on wasm32 + /// (no AES instructions); present so the handle matches the NAPI binding. #[wasm_bindgen(js_name = hardwareAccelerationEnabled)] pub fn hardware_acceleration_enabled(&self) -> bool { self.encryptor.hardware_acceleration_enabled() diff --git a/packages/cachekit/README.md b/packages/cachekit/README.md index c7c76bd..6553139 100644 --- a/packages/cachekit/README.md +++ b/packages/cachekit/README.md @@ -455,15 +455,14 @@ automatically from the cache's live L1/L2 hit and miss counters; pass your own `metricsProvider` in the backend config to override. **Is AES hardware-accelerated on this host?** `isHardwareAccelerated()` on the -encryption manager reports cachekit-core's detection — a runtime AES-NI probe on -x86/x86_64, compile-time target features on aarch64, and always `false` on -Cloudflare Workers (wasm32 has no AES instructions; `aes-gcm` runs in software). -Informational: the crypto backend picks its implementation independently, so use -it to explain `.secure` latency on a host without AES instructions, not to change -behaviour. It initialises the bindings if needed, so it answers at startup, and -returns `null` (unknown) only when the installed native binding predates the -accessor. Same signal as cachekit-py's `hardware_acceleration_enabled` and -cachekit-rs's `hardware_acceleration_enabled()`. +encryption manager forwards cachekit-core's detection. Informational only: the +crypto backend picks its implementation independently, so use it to explain +`.secure` latency, not to change behaviour. It initialises the bindings if +needed, and returns `null` (unknown) only when the installed native binding +predates the accessor. The per-architecture semantics are core's — as of +cachekit-core 0.6 a runtime AES-NI probe on x86/x86_64, `true` on every aarch64 +build (a NEON check, not the Crypto Extension), and `false` on Cloudflare +Workers (wasm32 has no AES instructions). ```typescript import { EncryptionManager } from '@cachekit-io/cachekit'; diff --git a/packages/cachekit/src/encryption/manager-core.test.ts b/packages/cachekit/src/encryption/manager-core.test.ts index a4d6a3c..b3cd2ad 100644 --- a/packages/cachekit/src/encryption/manager-core.test.ts +++ b/packages/cachekit/src/encryption/manager-core.test.ts @@ -256,11 +256,7 @@ describe('EncryptionManagerCore keyring config (previousMasterKeys)', () => { // Answers at startup, before any encrypt — and derives exactly once. expect(await manager.isHardwareAccelerated()).toBe(true); expect(derived.length).toBe(1); - await manager.encrypt(new Uint8Array([1]), 'ns:k'); - expect(derived.length).toBe(1); - manager.dispose(); - await expect(manager.isHardwareAccelerated()).rejects.toThrow(EncryptionError); }); it('reports null (unknown), not false, when the binding predates the accessor', async () => { diff --git a/packages/cachekit/src/encryption/manager-core.ts b/packages/cachekit/src/encryption/manager-core.ts index 65e9b5e..c80a6b1 100644 --- a/packages/cachekit/src/encryption/manager-core.ts +++ b/packages/cachekit/src/encryption/manager-core.ts @@ -25,10 +25,8 @@ export interface EncryptionTenantKeys { */ keyringEntryCount?(): number; /** - * Whether cachekit-core detected AES hardware support on this host - * (informational — the crypto backend dispatches independently). Optional - * because older binding binaries predate it; the manager reports `null` - * (unknown) rather than guessing `false`. + * cachekit-core's AES hardware detection (informational). Optional: older + * binding binaries predate it, and the manager reports `null` for those. */ hardwareAccelerationEnabled?(): boolean; /** @@ -345,14 +343,9 @@ export class EncryptionManagerCore { /** * Whether AES-256-GCM is hardware-accelerated on this host, per - * cachekit-core's detection: a runtime AES-NI probe on x86/x86_64, - * compile-time target features on aarch64, always `false` on wasm32 - * (Workers). Informational — the crypto backend picks its implementation - * independently; use it to explain `.secure` latency, not to change - * behaviour. Initialises the bindings if needed, so it answers at startup - * before the first encrypt. `null` means unknown: the installed binding - * predates the accessor. Same signal as Python's - * `hardware_acceleration_enabled`. + * cachekit-core's detection (informational — see the README). Initialises + * the bindings if needed, so it answers at startup. `null` = unknown: the + * installed binding predates the accessor. * * @throws {EncryptionError} if the manager is disposed or bindings fail to load */ From 69fbe820beeb5f5aa36665e104760cf6b055051c Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Tue, 22 Sep 2026 21:05:19 +1000 Subject: [PATCH 3/5] fix(encryption): honour the documented throw when dispose races isHardwareAccelerated isHardwareAccelerated() documents @throws EncryptionError when the manager is disposed, but read tenantKeys through a non-null assertion. On an already-initialised manager ensureInitialized() returns early, so a dispose() landing while the call is suspended at the await left the continuation reading a null tenantKeys and throwing TypeError instead. encrypt() and decrypt() convert the same null read inside their catch; this path has none, so it now checks explicitly. Adds a regression test that disposes an initialised manager mid-call and asserts EncryptionError (it fails with TypeError without the guard). --- .../cachekit/src/encryption/manager-core.test.ts | 14 ++++++++++++++ packages/cachekit/src/encryption/manager-core.ts | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/cachekit/src/encryption/manager-core.test.ts b/packages/cachekit/src/encryption/manager-core.test.ts index b3cd2ad..67351dd 100644 --- a/packages/cachekit/src/encryption/manager-core.test.ts +++ b/packages/cachekit/src/encryption/manager-core.test.ts @@ -274,4 +274,18 @@ describe('EncryptionManagerCore keyring config (previousMasterKeys)', () => { expect(await manager.isHardwareAccelerated()).toBeNull(); manager.dispose(); }); + + it('rejects with EncryptionError, not TypeError, when dispose races an initialised read', async () => { + const { bindings } = mockBindings(); + const manager = new TestManager(async () => bindings); + + // Initialise first, so ensureInitialized() takes its early-return path and + // the read below resumes only after dispose() has nulled tenantKeys. + expect(await manager.isHardwareAccelerated()).toBe(true); + + const inFlight = manager.isHardwareAccelerated(); + manager.dispose(); + + await expect(inFlight).rejects.toThrow(EncryptionError); + }); }); diff --git a/packages/cachekit/src/encryption/manager-core.ts b/packages/cachekit/src/encryption/manager-core.ts index c80a6b1..ec0cf83 100644 --- a/packages/cachekit/src/encryption/manager-core.ts +++ b/packages/cachekit/src/encryption/manager-core.ts @@ -351,7 +351,14 @@ export class EncryptionManagerCore { */ async isHardwareAccelerated(): Promise { await this.ensureInitialized(); - return this.tenantKeys!.hardwareAccelerationEnabled?.() ?? null; + // ensureInitialized() returns early on an already-initialised manager, so + // dispose() can land while this call is suspended at the await. encrypt() + // and decrypt() convert the resulting null read inside their catch; this + // path has none, so it checks explicitly rather than trusting a `!`. + if (this.disposed || !this.tenantKeys) { + throw new EncryptionError('EncryptionManager has been disposed'); + } + return this.tenantKeys.hardwareAccelerationEnabled?.() ?? null; } /** From fec951dedf2121c4f91aaccc64f0f2ed886fbcfb Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Tue, 22 Sep 2026 23:01:43 +1000 Subject: [PATCH 4/5] docs(encryption): isHardwareAccelerated null applies to any binding (LAB-523) The README said `null` is returned only when "the installed native binding" predates the accessor, but `hardwareAccelerationEnabled?.() ?? null` in manager-core.ts is binding-agnostic: an older wasm binding hits the same path. The accessor's own JSDoc already says "installed binding"; this aligns the README with both the implementation and that doc comment. --- packages/cachekit/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cachekit/README.md b/packages/cachekit/README.md index 6553139..b9fdf8d 100644 --- a/packages/cachekit/README.md +++ b/packages/cachekit/README.md @@ -458,7 +458,7 @@ automatically from the cache's live L1/L2 hit and miss counters; pass your own encryption manager forwards cachekit-core's detection. Informational only: the crypto backend picks its implementation independently, so use it to explain `.secure` latency, not to change behaviour. It initialises the bindings if -needed, and returns `null` (unknown) only when the installed native binding +needed, and returns `null` (unknown) only when the installed binding predates the accessor. The per-architecture semantics are core's — as of cachekit-core 0.6 a runtime AES-NI probe on x86/x86_64, `true` on every aarch64 build (a NEON check, not the Crypto Extension), and `false` on Cloudflare From 568840679c1097340f9ea112558b598c722f3860 Mon Sep 17 00:00:00 2001 From: Ray Walker Date: Thu, 24 Sep 2026 01:05:06 +1000 Subject: [PATCH 5/5] docs(encryption): dispose the manager in a finally in the isHardwareAccelerated example (LAB-523) The example is the README's only dispose() call, so it is the shape readers copy. try/finally keeps dispose() on the rejection path; no catch, so the error still surfaces. --- packages/cachekit/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/cachekit/README.md b/packages/cachekit/README.md index b9fdf8d..681cda7 100644 --- a/packages/cachekit/README.md +++ b/packages/cachekit/README.md @@ -468,8 +468,11 @@ Workers (wasm32 has no AES instructions). import { EncryptionManager } from '@cachekit-io/cachekit'; const manager = new EncryptionManager(process.env.CACHEKIT_MASTER_KEY!, 'tenant-123'); -console.log('AES hardware acceleration:', await manager.isHardwareAccelerated()); -manager.dispose(); +try { + console.log('AES hardware acceleration:', await manager.isHardwareAccelerated()); +} finally { + manager.dispose(); +} ``` ## Cloudflare Workers