@dudko.dev/agent-web 0.0.18 → 0.0.20
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/README.md +59 -1
- package/dist/agent-web.js +138 -100
- package/dist/agent-web.js.map +1 -1
- package/dist/{chunk-H7W6MBK2.js → chunk-V7COB2EA.js} +6 -2
- package/dist/chunk-V7COB2EA.js.map +1 -0
- package/dist/index.cjs +2207 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1066 -69
- package/dist/index.d.ts +1066 -69
- package/dist/index.js +2131 -190
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +88 -37
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +10 -0
- package/dist/mcp.d.ts +10 -0
- package/dist/mcp.js +85 -37
- package/dist/mcp.js.map +1 -1
- package/package.json +14 -14
- package/dist/chunk-H7W6MBK2.js.map +0 -1
|
@@ -3,16 +3,19 @@ import { openDB } from "idb";
|
|
|
3
3
|
var KEYS_STORE = "keys";
|
|
4
4
|
var SECRETS_STORE = "secrets";
|
|
5
5
|
var SESSIONS_STORE = "sessions";
|
|
6
|
+
var FILES_STORE = "files";
|
|
7
|
+
var DB_VERSION = 2;
|
|
6
8
|
var cache = /* @__PURE__ */ new Map();
|
|
7
9
|
var openAgentWebDB = (opts = {}) => {
|
|
8
10
|
const name = opts.dbName ?? "agent-web";
|
|
9
11
|
let p = cache.get(name);
|
|
10
12
|
if (!p) {
|
|
11
|
-
p = openDB(name,
|
|
13
|
+
p = openDB(name, DB_VERSION, {
|
|
12
14
|
upgrade(db) {
|
|
13
15
|
if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE);
|
|
14
16
|
if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE);
|
|
15
17
|
if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE);
|
|
18
|
+
if (!db.objectStoreNames.contains(FILES_STORE)) db.createObjectStore(FILES_STORE);
|
|
16
19
|
}
|
|
17
20
|
});
|
|
18
21
|
cache.set(name, p);
|
|
@@ -116,10 +119,11 @@ export {
|
|
|
116
119
|
KEYS_STORE,
|
|
117
120
|
SECRETS_STORE,
|
|
118
121
|
SESSIONS_STORE,
|
|
122
|
+
FILES_STORE,
|
|
119
123
|
openAgentWebDB,
|
|
120
124
|
getOrCreateVaultKey,
|
|
121
125
|
encryptJSON,
|
|
122
126
|
decryptJSON,
|
|
123
127
|
IndexedDBVault
|
|
124
128
|
};
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-V7COB2EA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/db.ts","../src/secrets/crypto.ts","../src/secrets/vault.ts"],"sourcesContent":["import { openDB, type IDBPDatabase } from 'idb'\n\nexport interface AgentWebDBOptions {\n /** Database name (default 'agent-web'). Use a distinct name to isolate apps. */\n dbName?: string\n}\n\nexport const KEYS_STORE = 'keys'\nexport const SECRETS_STORE = 'secrets'\nexport const SESSIONS_STORE = 'sessions'\nexport const FILES_STORE = 'files'\n\n// v1: keys, secrets, sessions. v2: + files (the virtual file system).\nconst DB_VERSION = 2\n\nlet cache = new Map<string, Promise<IDBPDatabase>>()\n\n/**\n * The single owner of the IndexedDB database. Every persistent store — the\n * non-extractable crypto key, encrypted secrets, and session transcripts —\n * lives under ONE database name + version. Centralising `openDB` here prevents\n * the classic bug where two modules open the same DB name with different store\n * sets and the second call throws a VersionError.\n */\nexport const openAgentWebDB = (opts: AgentWebDBOptions = {}): Promise<IDBPDatabase> => {\n const name = opts.dbName ?? 'agent-web'\n let p = cache.get(name)\n if (!p) {\n p = openDB(name, DB_VERSION, {\n upgrade(db) {\n // Idempotent by store: an existing v1 database gains only what it lacks.\n if (!db.objectStoreNames.contains(KEYS_STORE)) db.createObjectStore(KEYS_STORE)\n if (!db.objectStoreNames.contains(SECRETS_STORE)) db.createObjectStore(SECRETS_STORE)\n if (!db.objectStoreNames.contains(SESSIONS_STORE)) db.createObjectStore(SESSIONS_STORE)\n if (!db.objectStoreNames.contains(FILES_STORE)) db.createObjectStore(FILES_STORE)\n },\n })\n cache.set(name, p)\n // A failed open (private mode, storage denied) must not stay cached, or\n // every later call would replay the same rejection forever.\n p.catch(() => {\n if (cache.get(name) === p) cache.delete(name)\n })\n }\n return p\n}\n\n/** Test hook: drop cached connections so a fresh (e.g. fake-indexeddb) DB is opened. */\nexport const _resetDBCache = (): void => {\n cache = new Map()\n}\n","import { KEYS_STORE, openAgentWebDB } from '../storage/db.js'\n\nconst KEY_ID = 'vault-key-v1'\n\nconst webcrypto = (): Crypto => {\n const c = (globalThis as { crypto?: Crypto }).crypto\n if (!c?.subtle || !c.getRandomValues) {\n throw new Error('WebCrypto (crypto.subtle) is unavailable in this environment')\n }\n return c\n}\n\nconst loadOrCreateKey = async (dbName?: string): Promise<CryptoKey> => {\n const db = await openAgentWebDB({ dbName })\n const existing = (await db.get(KEYS_STORE, KEY_ID)) as CryptoKey | undefined\n if (existing) return existing\n const key = await webcrypto().subtle.generateKey(\n { name: 'AES-GCM', length: 256 },\n /* extractable */ false,\n ['encrypt', 'decrypt'],\n )\n // Double-check inside one readwrite transaction: another tab may have created\n // a key while ours was generating. Losing that race and overwriting would\n // leave the other tab's secrets permanently undecryptable.\n const tx = db.transaction(KEYS_STORE, 'readwrite')\n const winner = (await tx.store.get(KEY_ID)) as CryptoKey | undefined\n if (winner) {\n await tx.done\n return winner\n }\n await tx.store.put(key, KEY_ID)\n await tx.done\n return key\n}\n\nconst keyCache = new Map<string, Promise<CryptoKey>>()\n\n/**\n * Fetch (or lazily create) the vault's AES-GCM key. The key is generated\n * NON-EXTRACTABLE and stored as a live `CryptoKey` in IndexedDB — a CryptoKey\n * is structured-cloneable, so it persists across reloads WITHOUT its raw bytes\n * ever being exposed to JavaScript. Not even in-page script can read it out; it\n * can only be used to encrypt / decrypt. That is what makes at-rest storage of\n * the secrets meaningful (see IndexedDBVault's threat model).\n *\n * Concurrent callers share one in-flight promise per DB, and creation is\n * double-checked in a single IDB transaction, so two racing writers can never\n * end up encrypting under different keys.\n */\nexport const getOrCreateVaultKey = (dbName?: string): Promise<CryptoKey> => {\n const cacheKey = dbName ?? 'agent-web'\n let p = keyCache.get(cacheKey)\n if (!p) {\n p = loadOrCreateKey(dbName)\n keyCache.set(cacheKey, p)\n p.catch(() => {\n if (keyCache.get(cacheKey) === p) keyCache.delete(cacheKey)\n })\n }\n return p\n}\n\nexport interface EncryptedBlob {\n /** 96-bit random IV, fresh per write. */\n iv: Uint8Array\n ciphertext: ArrayBuffer\n}\n\nconst textEncoder = new TextEncoder()\nconst textDecoder = new TextDecoder()\n\n/** Encrypt a JSON-serialisable value with a fresh 96-bit IV (AES-GCM). */\nexport const encryptJSON = async (key: CryptoKey, value: unknown): Promise<EncryptedBlob> => {\n const c = webcrypto()\n const iv = c.getRandomValues(new Uint8Array(12))\n const data = textEncoder.encode(JSON.stringify(value))\n const ciphertext = await c.subtle.encrypt({ name: 'AES-GCM', iv }, key, data)\n return { iv, ciphertext }\n}\n\n/** Decrypt a blob produced by encryptJSON. Throws if the key or blob don't match. */\nexport const decryptJSON = async <T = unknown>(key: CryptoKey, blob: EncryptedBlob): Promise<T> => {\n // Copy the IV into a fresh ArrayBuffer-backed view: after a round-trip through\n // IndexedDB its element buffer is typed as ArrayBufferLike, which no longer\n // satisfies BufferSource under TS's stricter typed-array generics.\n const iv = new Uint8Array(blob.iv)\n const plain = await webcrypto().subtle.decrypt({ name: 'AES-GCM', iv }, key, blob.ciphertext)\n return JSON.parse(textDecoder.decode(plain)) as T\n}\n","import { openAgentWebDB, SECRETS_STORE } from '../storage/db.js'\nimport { decryptJSON, encryptJSON, getOrCreateVaultKey, type EncryptedBlob } from './crypto.js'\n\nexport interface VaultOptions {\n /** Database name, forwarded to the shared IDB owner (default 'agent-web'). */\n dbName?: string\n}\n\n/**\n * Encrypted key/value secret store backed by IndexedDB. Values are encrypted at\n * rest with the non-extractable AES-GCM vault key (see crypto.ts).\n *\n * ── THREAT MODEL (read before shipping any app-owned key to a browser) ──\n * This vault PROTECTS secrets **at rest** — an IndexedDB / disk dump yields\n * only ciphertext — and against **casual DevTools inspection** — no plaintext\n * key is stored, and the AES key cannot be exported even by in-page script.\n *\n * It does **NOT** defend against **active XSS on your origin**: any script that\n * already runs on the page can call `getSecret()`, or use the live `CryptoKey`\n * to decrypt. Browser storage is not a secrets manager.\n *\n * Therefore this vault is for the **end user's OWN key on their OWN device**\n * (bring-your-own-key). A shared, app-owned key must NEVER reach the client —\n * put it behind a server proxy (`ProviderModelSpec.baseURL`) or the Vercel AI\n * Gateway instead.\n */\nexport class IndexedDBVault {\n private readonly dbName?: string\n\n constructor(opts: VaultOptions = {}) {\n this.dbName = opts.dbName\n }\n\n private key(): Promise<CryptoKey> {\n return getOrCreateVaultKey(this.dbName)\n }\n\n /** Encrypt and store a secret string under `id`. */\n async setSecret(id: string, value: string): Promise<void> {\n const blob = await encryptJSON(await this.key(), value)\n const db = await openAgentWebDB({ dbName: this.dbName })\n await db.put(SECRETS_STORE, blob, id)\n }\n\n /** Fetch and decrypt a secret, or undefined if absent. */\n async getSecret(id: string): Promise<string | undefined> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n const blob = (await db.get(SECRETS_STORE, id)) as EncryptedBlob | undefined\n if (!blob) return undefined\n return decryptJSON<string>(await this.key(), blob)\n }\n\n async deleteSecret(id: string): Promise<void> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n await db.delete(SECRETS_STORE, id)\n }\n\n async listSecretIds(): Promise<string[]> {\n const db = await openAgentWebDB({ dbName: this.dbName })\n return (await db.getAllKeys(SECRETS_STORE)).map(String)\n }\n}\n"],"mappings":"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","names":[]}
|