byok-vault 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ra
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # byok-vault
2
+
3
+ Browser-native BYOK vault for serverless/local-first AI apps.
4
+
5
+ ## Security Reality Check (Read First)
6
+
7
+ - This project has **not** been formally audited.
8
+ - It protects against passive issues (plaintext keys in storage, accidental exposure, low-effort scraping).
9
+ - It does **not** protect against active in-origin script injection (XSS). If malicious JS executes on your origin, it can still intercept decrypted keys in-flight.
10
+ - `sessionStorage` caching is a UX optimization to reduce passphrase prompts, **not** a stronger security boundary.
11
+
12
+ If your threat model requires resistance to active injection attacks, use a server-side proxy.
13
+
14
+ ## What It Provides
15
+
16
+ - AES-GCM encryption at rest in `localStorage`.
17
+ - PBKDF2 key derivation (default `200,000` iterations) with per-user random salt.
18
+ - Scoped key access via `withKey(async (key) => { ... })`.
19
+ - Optional token circuit breaker with:
20
+ - pre-flight soft check (`requestedTokens`)
21
+ - post-call hard accounting (`reportUsage(tokens)`)
22
+ - dev warning when `withKey` finishes without `reportUsage`.
23
+ - `nuke()` reset flow to clear encrypted key and session state.
24
+
25
+ ## Why Use This
26
+
27
+ Most BYOK apps choose between two bad defaults:
28
+
29
+ - plaintext key entry in the browser (trust-killing UX), or
30
+ - rolling custom client-side crypto where implementation mistakes are common.
31
+
32
+ `byok-vault` is useful when you want browser-native key handling with opinionated defaults:
33
+
34
+ - encrypted-at-rest storage with per-key random salt and AES-GCM,
35
+ - scoped key access (`withKey`) instead of wide key plumbing through app code,
36
+ - built-in token budget circuit breaker (`requestedTokens` + `reportUsage`).
37
+
38
+ Use this if your threat model is client-side BYOK with passive exposure concerns.
39
+ Do not use this as an active-XSS defense; use a server-side proxy for that.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ npm install byok-vault
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```ts
50
+ import { BYOKVault } from "byok-vault";
51
+
52
+ const vault = new BYOKVault({
53
+ maxTokens: 30_000,
54
+ minPassphraseLength: 8
55
+ });
56
+
57
+ await vault.setKey(userApiKey, userPassphrase);
58
+ await vault.unlock(userPassphrase);
59
+
60
+ await vault.withKey(
61
+ async (key) => {
62
+ const response = await fetch("https://api.example.com/llm", {
63
+ method: "POST",
64
+ headers: {
65
+ Authorization: `Bearer ${key}`,
66
+ "Content-Type": "application/json"
67
+ },
68
+ body: JSON.stringify({ prompt: "hello" })
69
+ }).then((r) => r.json());
70
+
71
+ const used = response.usage.total_tokens;
72
+ vault.reportUsage(used); // hard usage accounting
73
+ },
74
+ {
75
+ requestedTokens: 1200 // optional soft pre-flight estimate
76
+ }
77
+ );
78
+ ```
79
+
80
+ ## Circuit Breaker Notes
81
+
82
+ - `requestedTokens` check is a soft guardrail based on your estimate.
83
+ - `reportUsage(tokens)` is the hard truth.
84
+ - When limit is exceeded, the **next** request is blocked with a hard error.
85
+ - In dev mode, vault warns if `withKey` returns successfully without `reportUsage`.
86
+
87
+ ## Provider Usage Parsing Snippets
88
+
89
+ OpenAI-style:
90
+
91
+ ```ts
92
+ const tokens = response.usage?.total_tokens ?? 0;
93
+ vault.reportUsage(tokens);
94
+ ```
95
+
96
+ Anthropic-style:
97
+
98
+ ```ts
99
+ const input = response.usage?.input_tokens ?? 0;
100
+ const output = response.usage?.output_tokens ?? 0;
101
+ vault.reportUsage(input + output);
102
+ ```
103
+
104
+ ## API
105
+
106
+ ```ts
107
+ new BYOKVault(options?)
108
+ ```
109
+
110
+ Options:
111
+
112
+ - `namespace?: string` storage key prefix (default `byok-vault`)
113
+ - `minPassphraseLength?: number` default `8`
114
+ - `pbkdf2Iterations?: number` default `200000`
115
+ - `maxTokens?: number` enables circuit breaker
116
+ - `devMode?: boolean` defaults to `NODE_ENV !== "production"` when available
117
+ - `localStorage?: Storage` / `sessionStorage?: Storage` for testing/custom storage
118
+ - `logger?: { warn(message: string): void }` custom warning sink
119
+
120
+ Methods:
121
+
122
+ - `setKey(apiKey, passphrase): Promise<void>`
123
+ - `unlock(passphrase): Promise<void>`
124
+ - `withKey(callback, { requestedTokens?, passphrase? }): Promise<T>`
125
+ - `reportUsage(tokens): void`
126
+ - `getUsage(): number`
127
+ - `getRemainingTokens(): number`
128
+ - `getMaxTokens(): number | null`
129
+ - `hasStoredKey(): boolean`
130
+ - `isLocked(): boolean`
131
+ - `getEncryptedBlob(): EncryptedKeyBlob | null`
132
+ - `lock(): void`
133
+ - `nuke(): void`
134
+
135
+ ## Threat Model and Limitations
136
+
137
+ - JavaScript cannot force immediate memory zeroization of strings; decrypted keys can remain in heap memory until GC.
138
+ - Passphrase quality matters. A short PIN (for example 4 digits) is brute-forceable even with high PBKDF2 iteration counts.
139
+ - PBKDF2 iteration count has a hard floor at `200000`; lower values throw at construction time.
140
+ - This package intentionally has zero runtime dependencies, but still has normal dev dependencies for build/test tooling.
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ npm install
146
+ npm run typecheck
147
+ npm test
148
+ npm run build
149
+ npm run pack:check
150
+ npm run demo
151
+ ```
152
+
153
+ ## Sample Project (Gemini)
154
+
155
+ See `examples/local-first-byok-sample/README.md` for a separate sample app that uses this package with Gemini API calls.
156
+
157
+ ## Human + LLM Docs
158
+
159
+ - Human integration guide: `docs/HUMANS.md`
160
+ - LLM reference: `docs/LLMS.md`
161
+ - LLM index file: `llms.txt`
@@ -0,0 +1,20 @@
1
+ interface CircuitBreakerOptions {
2
+ maxTokens: number;
3
+ storage: Storage;
4
+ storageKey: string;
5
+ }
6
+ export declare class CircuitBreaker {
7
+ private usage;
8
+ private readonly maxTokens;
9
+ private readonly storage;
10
+ private readonly storageKey;
11
+ constructor(options: CircuitBreakerOptions);
12
+ assertCanProceed(requestedTokens?: number): void;
13
+ reportUsage(tokens: number): void;
14
+ getUsage(): number;
15
+ getMaxTokens(): number;
16
+ getRemainingTokens(): number;
17
+ reset(): void;
18
+ }
19
+ export {};
20
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"AAEA,UAAU,qBAAqB;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAUD,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,OAAO,EAAE,qBAAqB;IAU1C,gBAAgB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI;IAqBhD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQjC,QAAQ,IAAI,MAAM;IAIlB,YAAY,IAAI,MAAM;IAItB,kBAAkB,IAAI,MAAM;IAI5B,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,58 @@
1
+ import { CircuitBreakerLimitError, InvalidUsageReportError } from "./errors.js";
2
+ function parseUsage(raw) {
3
+ if (!raw) {
4
+ return 0;
5
+ }
6
+ const parsed = Number.parseInt(raw, 10);
7
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
8
+ }
9
+ export class CircuitBreaker {
10
+ usage;
11
+ maxTokens;
12
+ storage;
13
+ storageKey;
14
+ constructor(options) {
15
+ if (!Number.isFinite(options.maxTokens) || options.maxTokens <= 0) {
16
+ throw new Error("maxTokens must be a finite number greater than zero.");
17
+ }
18
+ this.maxTokens = Math.floor(options.maxTokens);
19
+ this.storage = options.storage;
20
+ this.storageKey = options.storageKey;
21
+ this.usage = parseUsage(this.storage.getItem(this.storageKey));
22
+ }
23
+ assertCanProceed(requestedTokens) {
24
+ if (this.usage >= this.maxTokens) {
25
+ throw new CircuitBreakerLimitError(`Token limit reached (${this.usage}/${this.maxTokens}). Reset or nuke before sending another request.`);
26
+ }
27
+ if (requestedTokens === undefined) {
28
+ return;
29
+ }
30
+ if (!Number.isFinite(requestedTokens) || requestedTokens < 0) {
31
+ throw new InvalidUsageReportError();
32
+ }
33
+ if (this.usage + Math.floor(requestedTokens) > this.maxTokens) {
34
+ throw new CircuitBreakerLimitError(`Pre-flight estimate would exceed token limit (${this.usage} + ${Math.floor(requestedTokens)} > ${this.maxTokens}).`);
35
+ }
36
+ }
37
+ reportUsage(tokens) {
38
+ if (!Number.isFinite(tokens) || tokens < 0) {
39
+ throw new InvalidUsageReportError();
40
+ }
41
+ this.usage += Math.floor(tokens);
42
+ this.storage.setItem(this.storageKey, String(this.usage));
43
+ }
44
+ getUsage() {
45
+ return this.usage;
46
+ }
47
+ getMaxTokens() {
48
+ return this.maxTokens;
49
+ }
50
+ getRemainingTokens() {
51
+ return Math.max(this.maxTokens - this.usage, 0);
52
+ }
53
+ reset() {
54
+ this.usage = 0;
55
+ this.storage.removeItem(this.storageKey);
56
+ }
57
+ }
58
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const DEFAULT_PBKDF2_ITERATIONS = 200000;
2
+ export interface EncryptedKeyBlob {
3
+ version: 1;
4
+ iterations: number;
5
+ salt: string;
6
+ iv: string;
7
+ ciphertext: string;
8
+ createdAt: string;
9
+ }
10
+ interface EncryptOptions {
11
+ iterations?: number;
12
+ saltBytes?: number;
13
+ }
14
+ export declare function deriveKeyBits(passphrase: string, salt: Uint8Array, iterations?: number): Promise<Uint8Array>;
15
+ export declare function decryptWithKeyBits(blob: EncryptedKeyBlob, keyBits: Uint8Array): Promise<string>;
16
+ export declare function encryptKey(key: string, passphrase: string, options?: EncryptOptions): Promise<EncryptedKeyBlob>;
17
+ export declare function decryptKey(blob: EncryptedKeyBlob, passphrase: string): Promise<string>;
18
+ export {};
19
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,yBAAyB,SAAU,CAAC;AAKjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAoBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,UAAU,EAChB,UAAU,SAA4B,GACrC,OAAO,CAAC,UAAU,CAAC,CAoBrB;AA+BD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,gBAAgB,CAAC,CAa3B;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,gBAAgB,EACtB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAIjB"}
package/dist/crypto.js ADDED
@@ -0,0 +1,82 @@
1
+ import { base64ToBytes, bytesToBase64, bytesToUtf8, utf8ToBytes } from "./encoding.js";
2
+ import { WrongPassphraseError } from "./errors.js";
3
+ export const DEFAULT_PBKDF2_ITERATIONS = 200_000;
4
+ const DEFAULT_SALT_BYTES = 16;
5
+ const IV_BYTES = 12;
6
+ function assertWebCrypto() {
7
+ if (!globalThis.crypto?.subtle) {
8
+ throw new Error("Web Crypto API is not available in this environment.");
9
+ }
10
+ return globalThis.crypto;
11
+ }
12
+ function getRandomBytes(size) {
13
+ const cryptoProvider = assertWebCrypto();
14
+ const random = new Uint8Array(size);
15
+ cryptoProvider.getRandomValues(random);
16
+ return random;
17
+ }
18
+ function asBufferSource(bytes) {
19
+ return bytes;
20
+ }
21
+ export async function deriveKeyBits(passphrase, salt, iterations = DEFAULT_PBKDF2_ITERATIONS) {
22
+ const cryptoProvider = assertWebCrypto();
23
+ const passphraseKey = await cryptoProvider.subtle.importKey("raw", asBufferSource(utf8ToBytes(passphrase)), "PBKDF2", false, ["deriveBits"]);
24
+ const keyBits = await cryptoProvider.subtle.deriveBits({
25
+ name: "PBKDF2",
26
+ hash: "SHA-256",
27
+ salt: asBufferSource(salt),
28
+ iterations
29
+ }, passphraseKey, 256);
30
+ return new Uint8Array(keyBits);
31
+ }
32
+ async function importAesGcmKey(keyBits) {
33
+ const cryptoProvider = assertWebCrypto();
34
+ return cryptoProvider.subtle.importKey("raw", asBufferSource(keyBits), { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
35
+ }
36
+ async function encryptWithKeyBits(keyBits, plaintext) {
37
+ const cryptoProvider = assertWebCrypto();
38
+ const iv = getRandomBytes(IV_BYTES);
39
+ const key = await importAesGcmKey(keyBits);
40
+ const ciphertext = await cryptoProvider.subtle.encrypt({
41
+ name: "AES-GCM",
42
+ iv: asBufferSource(iv)
43
+ }, key, asBufferSource(utf8ToBytes(plaintext)));
44
+ return { iv, ciphertext: new Uint8Array(ciphertext) };
45
+ }
46
+ export async function decryptWithKeyBits(blob, keyBits) {
47
+ const cryptoProvider = assertWebCrypto();
48
+ const key = await importAesGcmKey(keyBits);
49
+ try {
50
+ const plaintext = await cryptoProvider.subtle.decrypt({
51
+ name: "AES-GCM",
52
+ iv: asBufferSource(base64ToBytes(blob.iv))
53
+ }, key, asBufferSource(base64ToBytes(blob.ciphertext)));
54
+ return bytesToUtf8(new Uint8Array(plaintext));
55
+ }
56
+ catch (error) {
57
+ if (error instanceof Error && error.name === "OperationError") {
58
+ throw new WrongPassphraseError();
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ export async function encryptKey(key, passphrase, options = {}) {
64
+ const salt = getRandomBytes(options.saltBytes ?? DEFAULT_SALT_BYTES);
65
+ const iterations = options.iterations ?? DEFAULT_PBKDF2_ITERATIONS;
66
+ const keyBits = await deriveKeyBits(passphrase, salt, iterations);
67
+ const encrypted = await encryptWithKeyBits(keyBits, key);
68
+ return {
69
+ version: 1,
70
+ iterations,
71
+ salt: bytesToBase64(salt),
72
+ iv: bytesToBase64(encrypted.iv),
73
+ ciphertext: bytesToBase64(encrypted.ciphertext),
74
+ createdAt: new Date().toISOString()
75
+ };
76
+ }
77
+ export async function decryptKey(blob, passphrase) {
78
+ const salt = base64ToBytes(blob.salt);
79
+ const keyBits = await deriveKeyBits(passphrase, salt, blob.iterations);
80
+ return decryptWithKeyBits(blob, keyBits);
81
+ }
82
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function utf8ToBytes(value: string): Uint8Array;
2
+ export declare function bytesToUtf8(value: Uint8Array): string;
3
+ export declare function bytesToBase64(bytes: Uint8Array): string;
4
+ export declare function base64ToBytes(value: string): Uint8Array;
5
+ //# sourceMappingURL=encoding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.d.ts","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAGA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAErD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAQvD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAOvD"}
@@ -0,0 +1,26 @@
1
+ const encoder = new TextEncoder();
2
+ const decoder = new TextDecoder();
3
+ export function utf8ToBytes(value) {
4
+ return encoder.encode(value);
5
+ }
6
+ export function bytesToUtf8(value) {
7
+ return decoder.decode(value);
8
+ }
9
+ export function bytesToBase64(bytes) {
10
+ let binary = "";
11
+ const chunkSize = 0x8000;
12
+ for (let index = 0; index < bytes.length; index += chunkSize) {
13
+ const chunk = bytes.subarray(index, index + chunkSize);
14
+ binary += String.fromCharCode(...chunk);
15
+ }
16
+ return btoa(binary);
17
+ }
18
+ export function base64ToBytes(value) {
19
+ const binary = atob(value);
20
+ const output = new Uint8Array(binary.length);
21
+ for (let index = 0; index < binary.length; index += 1) {
22
+ output[index] = binary.charCodeAt(index);
23
+ }
24
+ return output;
25
+ }
26
+ //# sourceMappingURL=encoding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoding.js","sourceRoot":"","sources":["../src/encoding.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAClC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAiB;IAC3C,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,SAAS,GAAG,MAAM,CAAC;IACzB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;QAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC;QACvD,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ export declare class BYOKVaultError extends Error {
2
+ readonly code: string;
3
+ constructor(code: string, message: string);
4
+ }
5
+ export declare class PassphrasePolicyError extends BYOKVaultError {
6
+ constructor(minLength: number);
7
+ }
8
+ export declare class PBKDF2PolicyError extends BYOKVaultError {
9
+ constructor(minIterations: number);
10
+ }
11
+ export declare class KeyNotFoundError extends BYOKVaultError {
12
+ constructor();
13
+ }
14
+ export declare class VaultLockedError extends BYOKVaultError {
15
+ constructor();
16
+ }
17
+ export declare class WrongPassphraseError extends BYOKVaultError {
18
+ constructor();
19
+ }
20
+ export declare class InvalidUsageReportError extends BYOKVaultError {
21
+ constructor();
22
+ }
23
+ export declare class CircuitBreakerLimitError extends BYOKVaultError {
24
+ constructor(message: string);
25
+ }
26
+ export declare class CircuitBreakerDisabledError extends BYOKVaultError {
27
+ constructor();
28
+ }
29
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,qBAAa,qBAAsB,SAAQ,cAAc;gBAC3C,SAAS,EAAE,MAAM;CAM9B;AAED,qBAAa,iBAAkB,SAAQ,cAAc;gBACvC,aAAa,EAAE,MAAM;CAMlC;AAED,qBAAa,gBAAiB,SAAQ,cAAc;;CAInD;AAED,qBAAa,gBAAiB,SAAQ,cAAc;;CAOnD;AAED,qBAAa,oBAAqB,SAAQ,cAAc;;CAOvD;AAED,qBAAa,uBAAwB,SAAQ,cAAc;;CAO1D;AAED,qBAAa,wBAAyB,SAAQ,cAAc;gBAC9C,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,2BAA4B,SAAQ,cAAc;;CAO9D"}
package/dist/errors.js ADDED
@@ -0,0 +1,49 @@
1
+ export class BYOKVaultError extends Error {
2
+ code;
3
+ constructor(code, message) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = this.constructor.name;
7
+ }
8
+ }
9
+ export class PassphrasePolicyError extends BYOKVaultError {
10
+ constructor(minLength) {
11
+ super("PASSPHRASE_POLICY", `Passphrase must be at least ${minLength} characters.`);
12
+ }
13
+ }
14
+ export class PBKDF2PolicyError extends BYOKVaultError {
15
+ constructor(minIterations) {
16
+ super("PBKDF2_POLICY", `pbkdf2Iterations must be a finite integer greater than or equal to ${minIterations}.`);
17
+ }
18
+ }
19
+ export class KeyNotFoundError extends BYOKVaultError {
20
+ constructor() {
21
+ super("KEY_NOT_FOUND", "No encrypted API key is stored in the vault.");
22
+ }
23
+ }
24
+ export class VaultLockedError extends BYOKVaultError {
25
+ constructor() {
26
+ super("VAULT_LOCKED", "Vault is locked. Call unlock(passphrase) or pass a passphrase to withKey.");
27
+ }
28
+ }
29
+ export class WrongPassphraseError extends BYOKVaultError {
30
+ constructor() {
31
+ super("WRONG_PASSPHRASE", "Could not decrypt key. The passphrase appears to be incorrect.");
32
+ }
33
+ }
34
+ export class InvalidUsageReportError extends BYOKVaultError {
35
+ constructor() {
36
+ super("INVALID_USAGE_REPORT", "Token usage must be a finite number greater than or equal to zero.");
37
+ }
38
+ }
39
+ export class CircuitBreakerLimitError extends BYOKVaultError {
40
+ constructor(message) {
41
+ super("CIRCUIT_BREAKER_LIMIT", message);
42
+ }
43
+ }
44
+ export class CircuitBreakerDisabledError extends BYOKVaultError {
45
+ constructor() {
46
+ super("CIRCUIT_BREAKER_DISABLED", "Circuit breaker is disabled because maxTokens was not configured.");
47
+ }
48
+ }
49
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IAC9B,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,cAAc;IACvD,YAAY,SAAiB;QAC3B,KAAK,CACH,mBAAmB,EACnB,+BAA+B,SAAS,cAAc,CACvD,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IACnD,YAAY,aAAqB;QAC/B,KAAK,CACH,eAAe,EACf,sEAAsE,aAAa,GAAG,CACvF,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,cAAc;IAClD;QACE,KAAK,CAAC,eAAe,EAAE,8CAA8C,CAAC,CAAC;IACzE,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,cAAc;IAClD;QACE,KAAK,CACH,cAAc,EACd,2EAA2E,CAC5E,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,oBAAqB,SAAQ,cAAc;IACtD;QACE,KAAK,CACH,kBAAkB,EAClB,gEAAgE,CACjE,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,cAAc;IACzD;QACE,KAAK,CACH,sBAAsB,EACtB,oEAAoE,CACrE,CAAC;IACJ,CAAC;CACF;AAED,MAAM,OAAO,wBAAyB,SAAQ,cAAc;IAC1D,YAAY,OAAe;QACzB,KAAK,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,2BAA4B,SAAQ,cAAc;IAC7D;QACE,KAAK,CACH,0BAA0B,EAC1B,mEAAmE,CACpE,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ export { DEFAULT_PBKDF2_ITERATIONS, decryptKey, decryptWithKeyBits, deriveKeyBits, encryptKey } from "./crypto.js";
2
+ export { CircuitBreaker } from "./circuit-breaker.js";
3
+ export { BYOKVault } from "./vault.js";
4
+ export { BYOKVaultError, CircuitBreakerDisabledError, CircuitBreakerLimitError, InvalidUsageReportError, KeyNotFoundError, PBKDF2PolicyError, PassphrasePolicyError, VaultLockedError, WrongPassphraseError } from "./errors.js";
5
+ export type { EncryptedKeyBlob } from "./crypto.js";
6
+ export type { BYOKVaultOptions, WithKeyOptions } from "./vault.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,UAAU,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { DEFAULT_PBKDF2_ITERATIONS, decryptKey, decryptWithKeyBits, deriveKeyBits, encryptKey } from "./crypto.js";
2
+ export { CircuitBreaker } from "./circuit-breaker.js";
3
+ export { BYOKVault } from "./vault.js";
4
+ export { BYOKVaultError, CircuitBreakerDisabledError, CircuitBreakerLimitError, InvalidUsageReportError, KeyNotFoundError, PBKDF2PolicyError, PassphrasePolicyError, VaultLockedError, WrongPassphraseError } from "./errors.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,UAAU,EACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,cAAc,EACd,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,29 @@
1
+ import type { EncryptedKeyBlob } from "./crypto.js";
2
+ export interface StorageKeys {
3
+ encryptedKey: string;
4
+ sessionKey: string;
5
+ tokenUsage: string;
6
+ }
7
+ export declare function getStorageKeys(namespace: string): StorageKeys;
8
+ export declare function resolveStorage(kind: "localStorage" | "sessionStorage", fallback?: Storage): Storage;
9
+ export declare class EncryptedKeyStorage {
10
+ private readonly storage;
11
+ private readonly key;
12
+ constructor(storage: Storage, key: string);
13
+ get(): EncryptedKeyBlob | null;
14
+ set(blob: EncryptedKeyBlob): void;
15
+ clear(): void;
16
+ }
17
+ export declare class SessionKeyCache {
18
+ private readonly storage;
19
+ private readonly key;
20
+ constructor(storage: Storage, key: string);
21
+ load(salt: string, iterations: number): Uint8Array | null;
22
+ save(payload: {
23
+ salt: string;
24
+ iterations: number;
25
+ keyBits: Uint8Array;
26
+ }): void;
27
+ clear(): void;
28
+ }
29
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQpD,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAM7D;AAiBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,gBAAgB,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CASnG;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAW,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAtC,OAAO,EAAE,OAAO,EAAmB,GAAG,EAAE,MAAM;IAE3E,GAAG,IAAI,gBAAgB,GAAG,IAAI;IAa9B,GAAG,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAIjC,KAAK,IAAI,IAAI;CAGd;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAW,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAtC,OAAO,EAAE,OAAO,EAAmB,GAAG,EAAE,MAAM;IAE3E,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAoBzD,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI;IAS9E,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,95 @@
1
+ import { base64ToBytes, bytesToBase64 } from "./encoding.js";
2
+ export function getStorageKeys(namespace) {
3
+ return {
4
+ encryptedKey: `${namespace}:encrypted-key`,
5
+ sessionKey: `${namespace}:derived-key`,
6
+ tokenUsage: `${namespace}:token-usage`
7
+ };
8
+ }
9
+ function isEncryptedKeyBlob(input) {
10
+ if (!input || typeof input !== "object") {
11
+ return false;
12
+ }
13
+ const blob = input;
14
+ return (blob.version === 1 &&
15
+ typeof blob.iterations === "number" &&
16
+ typeof blob.salt === "string" &&
17
+ typeof blob.iv === "string" &&
18
+ typeof blob.ciphertext === "string" &&
19
+ typeof blob.createdAt === "string");
20
+ }
21
+ export function resolveStorage(kind, fallback) {
22
+ if (fallback) {
23
+ return fallback;
24
+ }
25
+ const candidate = globalThis[kind];
26
+ if (!candidate) {
27
+ throw new Error(`${kind} is not available. Provide it through constructor options.`);
28
+ }
29
+ return candidate;
30
+ }
31
+ export class EncryptedKeyStorage {
32
+ storage;
33
+ key;
34
+ constructor(storage, key) {
35
+ this.storage = storage;
36
+ this.key = key;
37
+ }
38
+ get() {
39
+ const raw = this.storage.getItem(this.key);
40
+ if (!raw) {
41
+ return null;
42
+ }
43
+ try {
44
+ const parsed = JSON.parse(raw);
45
+ return isEncryptedKeyBlob(parsed) ? parsed : null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ set(blob) {
52
+ this.storage.setItem(this.key, JSON.stringify(blob));
53
+ }
54
+ clear() {
55
+ this.storage.removeItem(this.key);
56
+ }
57
+ }
58
+ export class SessionKeyCache {
59
+ storage;
60
+ key;
61
+ constructor(storage, key) {
62
+ this.storage = storage;
63
+ this.key = key;
64
+ }
65
+ load(salt, iterations) {
66
+ const raw = this.storage.getItem(this.key);
67
+ if (!raw) {
68
+ return null;
69
+ }
70
+ try {
71
+ const parsed = JSON.parse(raw);
72
+ if (parsed.salt !== salt ||
73
+ parsed.iterations !== iterations ||
74
+ typeof parsed.keyBits !== "string") {
75
+ return null;
76
+ }
77
+ return base64ToBytes(parsed.keyBits);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ save(payload) {
84
+ const record = {
85
+ salt: payload.salt,
86
+ iterations: payload.iterations,
87
+ keyBits: bytesToBase64(payload.keyBits)
88
+ };
89
+ this.storage.setItem(this.key, JSON.stringify(record));
90
+ }
91
+ clear() {
92
+ this.storage.removeItem(this.key);
93
+ }
94
+ }
95
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type EncryptedKeyBlob } from "./crypto.js";
2
+ export interface WithKeyOptions {
3
+ requestedTokens?: number;
4
+ passphrase?: string;
5
+ }
6
+ export interface BYOKVaultOptions {
7
+ namespace?: string;
8
+ minPassphraseLength?: number;
9
+ pbkdf2Iterations?: number;
10
+ maxTokens?: number;
11
+ devMode?: boolean;
12
+ localStorage?: Storage;
13
+ sessionStorage?: Storage;
14
+ logger?: Pick<Console, "warn">;
15
+ }
16
+ export declare class BYOKVault {
17
+ private readonly keyStorage;
18
+ private readonly sessionCache;
19
+ private readonly minPassphraseLength;
20
+ private readonly pbkdf2Iterations;
21
+ private readonly breaker?;
22
+ private readonly devMode;
23
+ private readonly logger;
24
+ private readonly scopes;
25
+ constructor(options?: BYOKVaultOptions);
26
+ setKey(apiKey: string, passphrase: string): Promise<void>;
27
+ unlock(passphrase: string): Promise<void>;
28
+ withKey<T>(callback: (decryptedKey: string) => Promise<T> | T, options?: WithKeyOptions): Promise<T>;
29
+ reportUsage(tokens: number): void;
30
+ getUsage(): number;
31
+ getRemainingTokens(): number;
32
+ getMaxTokens(): number | null;
33
+ hasStoredKey(): boolean;
34
+ isLocked(): boolean;
35
+ getEncryptedBlob(): EncryptedKeyBlob | null;
36
+ lock(): void;
37
+ nuke(): void;
38
+ private assertPassphrase;
39
+ private requireStoredBlob;
40
+ private resolveDecryptedKey;
41
+ private decryptOrThrowWrongPassphrase;
42
+ }
43
+ //# sourceMappingURL=vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAuBrB,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;CAChC;AAUD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAiB;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;gBAE/B,OAAO,GAAE,gBAAqB;IAsCpC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BzD,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazC,OAAO,CAAC,CAAC,EACb,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EAClD,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC;IAuBb,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAWjC,QAAQ,IAAI,MAAM;IAIlB,kBAAkB,IAAI,MAAM;IAI5B,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,YAAY,IAAI,OAAO;IAIvB,QAAQ,IAAI,OAAO;IAQnB,gBAAgB,IAAI,gBAAgB,GAAG,IAAI;IAI3C,IAAI,IAAI,IAAI;IAKZ,IAAI,IAAI,IAAI;IAOZ,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,iBAAiB;YAQX,mBAAmB;YAkCnB,6BAA6B;CAa5C"}
package/dist/vault.js ADDED
@@ -0,0 +1,198 @@
1
+ import { CircuitBreaker } from "./circuit-breaker.js";
2
+ import { base64ToBytes } from "./encoding.js";
3
+ import { DEFAULT_PBKDF2_ITERATIONS, decryptWithKeyBits, deriveKeyBits, encryptKey } from "./crypto.js";
4
+ import { CircuitBreakerDisabledError, KeyNotFoundError, PBKDF2PolicyError, PassphrasePolicyError, VaultLockedError, WrongPassphraseError } from "./errors.js";
5
+ import { EncryptedKeyStorage, SessionKeyCache, getStorageKeys, resolveStorage } from "./storage.js";
6
+ const DEFAULT_NAMESPACE = "byok-vault";
7
+ const DEFAULT_MIN_PASSPHRASE_LENGTH = 8;
8
+ function inferDevMode() {
9
+ const processCandidate = globalThis.process;
10
+ if (!processCandidate?.env?.NODE_ENV) {
11
+ return true;
12
+ }
13
+ return processCandidate.env.NODE_ENV !== "production";
14
+ }
15
+ export class BYOKVault {
16
+ keyStorage;
17
+ sessionCache;
18
+ minPassphraseLength;
19
+ pbkdf2Iterations;
20
+ breaker;
21
+ devMode;
22
+ logger;
23
+ scopes = [];
24
+ constructor(options = {}) {
25
+ const namespace = options.namespace ?? DEFAULT_NAMESPACE;
26
+ const localStorage = resolveStorage("localStorage", options.localStorage);
27
+ const sessionStorage = resolveStorage("sessionStorage", options.sessionStorage);
28
+ const keys = getStorageKeys(namespace);
29
+ this.keyStorage = new EncryptedKeyStorage(localStorage, keys.encryptedKey);
30
+ this.sessionCache = new SessionKeyCache(sessionStorage, keys.sessionKey);
31
+ this.minPassphraseLength = options.minPassphraseLength ?? DEFAULT_MIN_PASSPHRASE_LENGTH;
32
+ if (!Number.isFinite(this.minPassphraseLength) ||
33
+ !Number.isInteger(this.minPassphraseLength) ||
34
+ this.minPassphraseLength < 1) {
35
+ throw new Error("minPassphraseLength must be an integer greater than or equal to 1.");
36
+ }
37
+ this.pbkdf2Iterations = options.pbkdf2Iterations ?? DEFAULT_PBKDF2_ITERATIONS;
38
+ if (!Number.isFinite(this.pbkdf2Iterations) ||
39
+ !Number.isInteger(this.pbkdf2Iterations) ||
40
+ this.pbkdf2Iterations < DEFAULT_PBKDF2_ITERATIONS) {
41
+ throw new PBKDF2PolicyError(DEFAULT_PBKDF2_ITERATIONS);
42
+ }
43
+ this.devMode = options.devMode ?? inferDevMode();
44
+ this.logger = options.logger ?? console;
45
+ if (options.maxTokens !== undefined) {
46
+ this.breaker = new CircuitBreaker({
47
+ maxTokens: options.maxTokens,
48
+ storage: sessionStorage,
49
+ storageKey: keys.tokenUsage
50
+ });
51
+ }
52
+ }
53
+ async setKey(apiKey, passphrase) {
54
+ this.assertPassphrase(passphrase);
55
+ if (!apiKey) {
56
+ throw new Error("apiKey cannot be empty.");
57
+ }
58
+ const blob = await encryptKey(apiKey, passphrase, {
59
+ iterations: this.pbkdf2Iterations
60
+ });
61
+ this.keyStorage.set(blob);
62
+ const keyBits = await deriveKeyBits(passphrase, base64ToBytes(blob.salt), blob.iterations);
63
+ // sessionStorage caching is only for passphrase UX; it is not an extra security boundary.
64
+ this.sessionCache.save({
65
+ salt: blob.salt,
66
+ iterations: blob.iterations,
67
+ keyBits
68
+ });
69
+ this.breaker?.reset();
70
+ }
71
+ async unlock(passphrase) {
72
+ this.assertPassphrase(passphrase);
73
+ const blob = this.requireStoredBlob();
74
+ const salt = base64ToBytes(blob.salt);
75
+ const keyBits = await deriveKeyBits(passphrase, salt, blob.iterations);
76
+ await this.decryptOrThrowWrongPassphrase(blob, keyBits);
77
+ this.sessionCache.save({
78
+ salt: blob.salt,
79
+ iterations: blob.iterations,
80
+ keyBits
81
+ });
82
+ }
83
+ async withKey(callback, options = {}) {
84
+ this.breaker?.assertCanProceed(options.requestedTokens);
85
+ const scope = { reported: false };
86
+ let callbackCompleted = false;
87
+ this.scopes.push(scope);
88
+ try {
89
+ // If malicious script runs in-origin (XSS), it can still read this value in-flight.
90
+ // This API narrows exposure windows; it does not eliminate active injection risk.
91
+ const decryptedKey = await this.resolveDecryptedKey(options.passphrase);
92
+ const result = await callback(decryptedKey);
93
+ callbackCompleted = true;
94
+ return result;
95
+ }
96
+ finally {
97
+ this.scopes.pop();
98
+ if (this.breaker && this.devMode && callbackCompleted && !scope.reported) {
99
+ this.logger.warn("[byok-vault] withKey completed without reportUsage(tokens). Circuit breaker accounting is incomplete.");
100
+ }
101
+ }
102
+ }
103
+ reportUsage(tokens) {
104
+ if (!this.breaker) {
105
+ throw new CircuitBreakerDisabledError();
106
+ }
107
+ this.breaker.reportUsage(tokens);
108
+ const activeScope = this.scopes.at(-1);
109
+ if (activeScope) {
110
+ activeScope.reported = true;
111
+ }
112
+ }
113
+ getUsage() {
114
+ return this.breaker?.getUsage() ?? 0;
115
+ }
116
+ getRemainingTokens() {
117
+ return this.breaker?.getRemainingTokens() ?? Number.POSITIVE_INFINITY;
118
+ }
119
+ getMaxTokens() {
120
+ return this.breaker ? this.breaker.getMaxTokens() : null;
121
+ }
122
+ hasStoredKey() {
123
+ return this.keyStorage.get() !== null;
124
+ }
125
+ isLocked() {
126
+ const blob = this.keyStorage.get();
127
+ if (!blob) {
128
+ return true;
129
+ }
130
+ return this.sessionCache.load(blob.salt, blob.iterations) === null;
131
+ }
132
+ getEncryptedBlob() {
133
+ return this.keyStorage.get();
134
+ }
135
+ lock() {
136
+ this.sessionCache.clear();
137
+ this.scopes.length = 0;
138
+ }
139
+ nuke() {
140
+ this.keyStorage.clear();
141
+ this.sessionCache.clear();
142
+ this.breaker?.reset();
143
+ this.scopes.length = 0;
144
+ }
145
+ assertPassphrase(passphrase) {
146
+ if (passphrase.length < this.minPassphraseLength) {
147
+ throw new PassphrasePolicyError(this.minPassphraseLength);
148
+ }
149
+ }
150
+ requireStoredBlob() {
151
+ const blob = this.keyStorage.get();
152
+ if (!blob) {
153
+ throw new KeyNotFoundError();
154
+ }
155
+ return blob;
156
+ }
157
+ async resolveDecryptedKey(passphrase) {
158
+ const blob = this.requireStoredBlob();
159
+ const cachedBits = this.sessionCache.load(blob.salt, blob.iterations);
160
+ if (cachedBits) {
161
+ try {
162
+ return await decryptWithKeyBits(blob, cachedBits);
163
+ }
164
+ catch (error) {
165
+ if (error instanceof WrongPassphraseError) {
166
+ this.sessionCache.clear();
167
+ }
168
+ else {
169
+ throw error;
170
+ }
171
+ }
172
+ }
173
+ if (!passphrase) {
174
+ throw new VaultLockedError();
175
+ }
176
+ this.assertPassphrase(passphrase);
177
+ const keyBits = await deriveKeyBits(passphrase, base64ToBytes(blob.salt), blob.iterations);
178
+ const decryptedKey = await this.decryptOrThrowWrongPassphrase(blob, keyBits);
179
+ this.sessionCache.save({
180
+ salt: blob.salt,
181
+ iterations: blob.iterations,
182
+ keyBits
183
+ });
184
+ return decryptedKey;
185
+ }
186
+ async decryptOrThrowWrongPassphrase(blob, keyBits) {
187
+ try {
188
+ return await decryptWithKeyBits(blob, keyBits);
189
+ }
190
+ catch (error) {
191
+ if (error instanceof WrongPassphraseError) {
192
+ throw error;
193
+ }
194
+ throw error;
195
+ }
196
+ }
197
+ }
198
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"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"}
package/docs/HUMANS.md ADDED
@@ -0,0 +1,80 @@
1
+ # Human Docs: byok-vault
2
+
3
+ This guide is for developers integrating `byok-vault` into real apps.
4
+
5
+ ## What This Library Solves
6
+
7
+ - Encrypts user API keys in-browser before writing to `localStorage`.
8
+ - Lets you scope decrypted key access to one callback with `withKey(...)`.
9
+ - Adds optional per-session token budget tracking via a circuit breaker.
10
+
11
+ It is not a backend replacement for high-security threat models with active XSS risk.
12
+
13
+ ## Quick Integration Checklist
14
+
15
+ 1. Ask user for API key and passphrase.
16
+ 2. Save once with `await vault.setKey(apiKey, passphrase)`.
17
+ 3. For each request, call `vault.withKey(...)`.
18
+ 4. If breaker enabled, call `vault.reportUsage(tokens)` after each successful provider response.
19
+ 5. Add reset UI that calls `vault.nuke()`.
20
+
21
+ ## Minimal Usage Pattern
22
+
23
+ ```ts
24
+ import { BYOKVault } from "byok-vault";
25
+
26
+ const vault = new BYOKVault({
27
+ maxTokens: 30_000
28
+ });
29
+
30
+ await vault.setKey(userApiKey, userPassphrase);
31
+
32
+ await vault.withKey(
33
+ async (key) => {
34
+ const response = await fetch("/your-provider-call", {
35
+ method: "POST",
36
+ headers: { Authorization: `Bearer ${key}` }
37
+ }).then((r) => r.json());
38
+
39
+ const used = response.usage?.total_tokens ?? 0;
40
+ vault.reportUsage(used);
41
+ },
42
+ { requestedTokens: 1200 }
43
+ );
44
+ ```
45
+
46
+ ## UX Recommendations
47
+
48
+ - Explain passphrase purpose clearly: protects key at rest in browser storage.
49
+ - Enforce strong passphrase UX (default floor is 8 chars, consider stronger copy and meter).
50
+ - Show current token usage and remaining budget if breaker is enabled.
51
+ - Provide a visible "reset vault" control wired to `nuke()`.
52
+
53
+ ## Security Boundaries (Plain English)
54
+
55
+ - `sessionStorage` caching is convenience only, not stronger security.
56
+ - If hostile JS executes in your origin, it can still intercept keys in-flight.
57
+ - Decrypted strings can remain in JS memory until garbage collection.
58
+ - This package is not formally audited.
59
+
60
+ ## Common Mistakes
61
+
62
+ - Enabling `maxTokens` but forgetting `reportUsage(tokens)`.
63
+ - Treating `requestedTokens` pre-flight as exact accounting.
64
+ - Assuming this protects against active XSS.
65
+ - Lowering PBKDF2 iterations below `200000` (constructor throws).
66
+
67
+ ## Error Handling You Should Surface
68
+
69
+ - `PASSPHRASE_POLICY`: passphrase too short.
70
+ - `WRONG_PASSPHRASE`: user entered incorrect passphrase.
71
+ - `VAULT_LOCKED`: no cached session key and no passphrase provided.
72
+ - `CIRCUIT_BREAKER_LIMIT`: budget exhausted/pre-flight blocked.
73
+ - `KEY_NOT_FOUND`: no stored key yet.
74
+
75
+ ## Production Readiness Checklist
76
+
77
+ - Add CSP and strict input sanitization in your app to reduce XSS risk.
78
+ - Instrument `reportUsage` code path and alert on missing usage reporting.
79
+ - Use `pack:check` and CI before publishing changes.
80
+ - Document threat model to users in product copy.
package/docs/LLMS.md ADDED
@@ -0,0 +1,114 @@
1
+ # LLM Docs: byok-vault
2
+
3
+ This file is a machine-oriented reference for code assistants and agents.
4
+
5
+ ## Package Identity
6
+
7
+ - Name: `byok-vault`
8
+ - Runtime deps: none
9
+ - Environment: browser Web Crypto API (`crypto.subtle`), `localStorage`, `sessionStorage`
10
+ - Primary class: `BYOKVault`
11
+
12
+ ## Core Guarantees
13
+
14
+ - Stored API keys are encrypted at rest using AES-GCM.
15
+ - Per-key salt is random and unique per encryption operation.
16
+ - AES key material is derived via PBKDF2 (SHA-256), default and enforced floor: `200000` iterations.
17
+ - Decrypted key is only provided inside `withKey(callback)`.
18
+
19
+ ## Non-Goals / Limits
20
+
21
+ - No defense against active XSS in same origin.
22
+ - No hard provider SDK integrations.
23
+ - No authoritative token accounting without `reportUsage(tokens)`.
24
+
25
+ ## Canonical API Contracts
26
+
27
+ ### Constructor
28
+
29
+ ```ts
30
+ new BYOKVault(options?)
31
+ ```
32
+
33
+ Important options:
34
+
35
+ - `namespace?: string` -> storage key prefix
36
+ - `minPassphraseLength?: number` -> integer >= 1 (default 8)
37
+ - `pbkdf2Iterations?: number` -> integer >= 200000
38
+ - `maxTokens?: number` -> enables circuit breaker if set
39
+ - `devMode?: boolean`
40
+ - `localStorage?: Storage`, `sessionStorage?: Storage`
41
+ - `logger?: { warn(message: string): void }`
42
+
43
+ ### Methods
44
+
45
+ - `setKey(apiKey, passphrase): Promise<void>`
46
+ - `unlock(passphrase): Promise<void>`
47
+ - `withKey(callback, { requestedTokens?, passphrase? }): Promise<T>`
48
+ - `reportUsage(tokens): void`
49
+ - `getUsage(): number`
50
+ - `getRemainingTokens(): number`
51
+ - `getMaxTokens(): number | null`
52
+ - `hasStoredKey(): boolean`
53
+ - `isLocked(): boolean`
54
+ - `getEncryptedBlob(): EncryptedKeyBlob | null`
55
+ - `lock(): void`
56
+ - `nuke(): void`
57
+
58
+ ## Circuit Breaker Semantics
59
+
60
+ - `requestedTokens` is pre-flight estimate only.
61
+ - `reportUsage(tokens)` is post-call hard accounting.
62
+ - Breaker blocks the next request when usage already at/over budget.
63
+ - In dev mode, warning is emitted if `withKey` returns successfully without `reportUsage`.
64
+ - If callback throws, missing `reportUsage` warning is not emitted.
65
+
66
+ ## Error Codes
67
+
68
+ - `PASSPHRASE_POLICY`
69
+ - `PBKDF2_POLICY`
70
+ - `KEY_NOT_FOUND`
71
+ - `VAULT_LOCKED`
72
+ - `WRONG_PASSPHRASE`
73
+ - `INVALID_USAGE_REPORT`
74
+ - `CIRCUIT_BREAKER_LIMIT`
75
+ - `CIRCUIT_BREAKER_DISABLED`
76
+
77
+ ## Correct Usage Pattern (Agent Guidance)
78
+
79
+ 1. Construct `BYOKVault`.
80
+ 2. Save key once via `setKey(...)`.
81
+ 3. Use `withKey(...)` around each provider call.
82
+ 4. Parse provider usage from response and call `reportUsage(tokens)` if breaker enabled.
83
+ 5. Use `nuke()` for full reset; use `lock()` for session-only lock.
84
+
85
+ ## Anti-Patterns (Do Not Generate)
86
+
87
+ - Do not store plaintext API keys in storage.
88
+ - Do not call `withKey` and omit `reportUsage` when `maxTokens` is configured.
89
+ - Do not claim this library mitigates active XSS.
90
+ - Do not set `pbkdf2Iterations < 200000`.
91
+
92
+ ## Provider Usage Parsing Examples
93
+
94
+ OpenAI style:
95
+
96
+ ```ts
97
+ const tokens = response.usage?.total_tokens ?? 0;
98
+ vault.reportUsage(tokens);
99
+ ```
100
+
101
+ Anthropic style:
102
+
103
+ ```ts
104
+ const tokens =
105
+ (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0);
106
+ vault.reportUsage(tokens);
107
+ ```
108
+
109
+ Gemini style:
110
+
111
+ ```ts
112
+ const tokens = response.usageMetadata?.totalTokenCount ?? 0;
113
+ vault.reportUsage(tokens);
114
+ ```
package/llms.txt ADDED
@@ -0,0 +1,25 @@
1
+ # byok-vault
2
+
3
+ Browser-native BYOK vault for local-first/serverless AI apps.
4
+
5
+ ## Preferred Docs
6
+
7
+ - README: `README.md`
8
+ - Human integration guide: `docs/HUMANS.md`
9
+ - LLM reference: `docs/LLMS.md`
10
+ - Gemini sample app: `examples/local-first-byok-sample/README.md`
11
+
12
+ ## Stable Facts
13
+
14
+ - Primary API surface is `BYOKVault`.
15
+ - Encryption at rest: AES-GCM.
16
+ - Key derivation: PBKDF2 SHA-256 with minimum `200000` iterations.
17
+ - Circuit breaker is optional and requires `reportUsage(tokens)` to be meaningful.
18
+ - Library is not a defense against active XSS in same-origin execution context.
19
+
20
+ ## Safe Generation Rules For Agents
21
+
22
+ - Use `withKey(async (key) => { ... })` for provider calls.
23
+ - If `maxTokens` is set, include `vault.reportUsage(tokens)` after successful responses.
24
+ - Do not generate claims that this package is formally audited.
25
+ - Do not claim it eliminates XSS risk.
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "byok-vault",
3
+ "version": "0.1.0",
4
+ "description": "Browser-native BYOK vault with encrypted storage and token circuit breaker.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "docs",
17
+ "llms.txt"
18
+ ],
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "build": "npm run build:lib",
22
+ "build:lib": "tsc -p tsconfig.build.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "pack:check": "node ./scripts/check-pack.mjs",
27
+ "demo": "vite --config demo/vite.config.ts",
28
+ "demo:build": "vite build --config demo/vite.config.ts"
29
+ },
30
+ "keywords": [
31
+ "byok",
32
+ "vault",
33
+ "browser",
34
+ "aes-gcm",
35
+ "pbkdf2"
36
+ ],
37
+ "author": "Ra <ravicity999@gmail.com>",
38
+ "license": "MIT",
39
+ "devDependencies": {
40
+ "@types/node": "^22.15.19",
41
+ "jsdom": "^25.0.1",
42
+ "typescript": "^5.8.3",
43
+ "vite": "^5.4.19",
44
+ "vitest": "^2.1.9"
45
+ }
46
+ }