@agimon-ai/po-vault 0.0.0-alpha.61
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 +149 -0
- package/README.md +57 -0
- package/dist/apiContracts.cjs +17 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +13 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/contracts-C6dCp2Il.mjs +9 -0
- package/dist/contracts-C6dCp2Il.mjs.map +1 -0
- package/dist/contracts-Do7L_d_L.cjs +13 -0
- package/dist/contracts-Do7L_d_L.cjs.map +1 -0
- package/dist/extensions/server.cjs +655 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +627 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/testing.cjs +89 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.mts +10 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +88 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/type-fwXKnI1F.d.mts +42 -0
- package/dist/type-fwXKnI1F.d.mts.map +1 -0
- package/dist/vaultRules-B84nuLpN.mjs +83 -0
- package/dist/vaultRules-B84nuLpN.mjs.map +1 -0
- package/dist/vaultRules-CUudYo7G.cjs +202 -0
- package/dist/vaultRules-CUudYo7G.cjs.map +1 -0
- package/llms.txt +7 -0
- package/package.json +113 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.mjs","names":["Buffer","open","Buffer","open","rootGlobal"],"sources":["../../src/services/vaultCrypto/index.ts","../../src/services/vault/index.ts","../../src/services/vaultDirectory/index.ts","../../src/services/vaultKeyring/index.ts","../../src/extensions/(backend)/_lib/root.server.ts","../../src/extensions/(backend)/root.server.ts","../../generated/server.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\nimport { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\n\nimport { VAULT_IV_BYTES, VAULT_KEY_BYTES, VAULT_TAG_BYTES } from '../../constants/vault';\nimport type { SealedBox } from './type';\n\n/** AES-256-GCM with a fresh 12-byte IV, bound to `aad`. Ported from the MCP proxy's FileTokenStore. */\nexport function seal(key: Uint8Array, plaintext: string, aad: string): SealedBox {\n const iv = randomBytes(VAULT_IV_BYTES);\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n cipher.setAAD(Buffer.from(aad, 'utf8'));\n const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);\n return { iv, tag: cipher.getAuthTag(), ciphertext };\n}\n\n/** The plaintext, or `undefined` when the key, the AAD or any byte of the box is wrong. */\nexport function open(key: Uint8Array, box: SealedBox, aad: string): string | undefined {\n if (key.length !== VAULT_KEY_BYTES || box.iv.length !== VAULT_IV_BYTES || box.tag.length !== VAULT_TAG_BYTES)\n return undefined;\n try {\n const decipher = createDecipheriv('aes-256-gcm', key, box.iv);\n decipher.setAAD(Buffer.from(aad, 'utf8'));\n decipher.setAuthTag(box.tag);\n return Buffer.concat([decipher.update(box.ciphertext), decipher.final()]).toString('utf8');\n } catch {\n return undefined;\n }\n}\n\n/** `iv | tag | ciphertext`, the layout of the stored key check. */\nexport function packBox(box: SealedBox): Uint8Array {\n return Buffer.concat([box.iv, box.tag, box.ciphertext]);\n}\n\nexport function unpackBox(packed: Uint8Array): SealedBox | undefined {\n const bytes = Buffer.from(packed.buffer, packed.byteOffset, packed.byteLength);\n if (bytes.length <= VAULT_IV_BYTES + VAULT_TAG_BYTES) return undefined;\n return {\n iv: bytes.subarray(0, VAULT_IV_BYTES),\n tag: bytes.subarray(VAULT_IV_BYTES, VAULT_IV_BYTES + VAULT_TAG_BYTES),\n ciphertext: bytes.subarray(VAULT_IV_BYTES + VAULT_TAG_BYTES),\n };\n}\n\nexport function randomKey(): Uint8Array {\n return randomBytes(VAULT_KEY_BYTES);\n}\n","import { Buffer } from 'node:buffer';\nimport { randomBytes, randomUUID } from 'node:crypto';\n\nimport type { PoDbMigration } from '@agimon-ai/po-db';\n\nimport {\n VAULT_CHECK_AAD,\n VAULT_CHECK_PLAINTEXT,\n VAULT_DB_PREFIX,\n VAULT_ID_BYTES,\n VAULT_INIT_LEASE,\n VAULT_KEY_BYTES,\n VAULT_KEY_LOST_NOTICE,\n VAULT_KEYRING_ACCOUNT_PREFIX,\n VAULT_LEASE_JITTER_MS,\n VAULT_LEASE_POLL_MS,\n VAULT_LEASE_RENEW_MS,\n VAULT_LEASE_TTL_MS,\n VAULT_LEASE_WAIT_MS,\n VAULT_META_CHECK,\n VAULT_META_ID,\n} from '../../constants/vault';\nimport { open, packBox, randomKey, seal, unpackBox } from '../vaultCrypto';\nimport type { SealedBox } from '../vaultCrypto/type';\nimport { asVaultError, assertEntryName, assertRevision, serializeValue, VaultError } from '../vaultRules';\nimport type {\n CreateVaultOptions,\n VaultDeleteOptions,\n VaultHandle,\n VaultKeyring,\n VaultPutOptions,\n VaultRecord,\n VaultValue,\n} from './type';\n\n/** The vault's schema in po-db. Every object carries the `vault_` prefix po-db enforces. */\nexport const VAULT_MIGRATIONS: readonly PoDbMigration[] = [\n {\n version: 1,\n statements: [\n 'CREATE TABLE vault_meta (name TEXT PRIMARY KEY, value BLOB NOT NULL) STRICT, WITHOUT ROWID',\n `CREATE TABLE vault_entries (\n namespace TEXT NOT NULL, key TEXT NOT NULL,\n revision INTEGER NOT NULL CHECK (revision >= 1), updated_at INTEGER NOT NULL,\n iv BLOB NOT NULL CHECK (length(iv) = 12), tag BLOB NOT NULL CHECK (length(tag) = 16),\n ciphertext BLOB NOT NULL, PRIMARY KEY (namespace, key)\n ) STRICT, WITHOUT ROWID`,\n 'CREATE TABLE vault_lease (name TEXT PRIMARY KEY, owner TEXT NOT NULL, expires_at INTEGER NOT NULL) STRICT, WITHOUT ROWID',\n ],\n },\n];\n\ninterface EntryRow {\n revision: number;\n updated_at: number;\n iv: Uint8Array;\n tag: Uint8Array;\n ciphertext: Uint8Array;\n}\n\ninterface WrittenRow {\n revision: number;\n updated_at: number;\n}\n\ninterface LoadedKey {\n readonly key: Uint8Array;\n /** The `dek_check` bytes this key was proven against. */\n readonly check: Uint8Array;\n}\n\ninterface Candidate {\n readonly source: 'keyring' | 'file';\n readonly key: Uint8Array;\n}\n\ninterface Gathered {\n readonly candidates: readonly Candidate[];\n readonly keyring: VaultKeyring | null;\n readonly keyringFailed: boolean;\n readonly fileFailed: boolean;\n}\n\nconst SELECT_ENTRY =\n 'SELECT revision, updated_at, iv, tag, ciphertext FROM vault_entries WHERE namespace = ? AND key = ?';\nconst PUT_LAST_WRITE_WINS = `INSERT INTO vault_entries (namespace, key, revision, updated_at, iv, tag, ciphertext)\n VALUES (?, ?, 1, ?, ?, ?, ?)\n ON CONFLICT (namespace, key) DO UPDATE SET revision = vault_entries.revision + 1, updated_at = excluded.updated_at,\n iv = excluded.iv, tag = excluded.tag, ciphertext = excluded.ciphertext\n RETURNING revision, updated_at`;\nconst PUT_CREATE_ONLY = `INSERT INTO vault_entries (namespace, key, revision, updated_at, iv, tag, ciphertext)\n VALUES (?, ?, 1, ?, ?, ?, ?)\n ON CONFLICT (namespace, key) DO NOTHING\n RETURNING revision, updated_at`;\nconst PUT_EXPECT_REVISION = `UPDATE vault_entries SET revision = ?, updated_at = ?, iv = ?, tag = ?, ciphertext = ?\n WHERE namespace = ? AND key = ? AND revision = ?\n RETURNING revision, updated_at`;\nconst ACQUIRE_LEASE = `INSERT INTO vault_lease (name, owner, expires_at) VALUES (?, ?, ?)\n ON CONFLICT (name) DO UPDATE SET owner = excluded.owner, expires_at = excluded.expires_at\n WHERE vault_lease.expires_at < ? OR vault_lease.owner = excluded.owner\n RETURNING owner`;\nconst RELEASE_LEASE = 'DELETE FROM vault_lease WHERE name = ? AND owner = ?';\nconst SELECT_META = 'SELECT value FROM vault_meta WHERE name = ?';\nconst INSERT_META = 'INSERT INTO vault_meta (name, value) VALUES (?, ?) ON CONFLICT (name) DO NOTHING';\nconst REPLACE_META =\n 'INSERT INTO vault_meta (name, value) VALUES (?, ?) ON CONFLICT (name) DO UPDATE SET value = excluded.value';\n\nfunction entryAad(namespace: string, key: string): string {\n return `${namespace}\\0${key}`;\n}\n\nfunction bytesEqual(left: Uint8Array, right: Uint8Array): boolean {\n return Buffer.compare(left, right) === 0;\n}\n\nfunction boxOf(row: EntryRow): SealedBox {\n return { iv: row.iv, tag: row.tag, ciphertext: row.ciphertext };\n}\n\nfunction provesKey(key: Uint8Array, check: Uint8Array): boolean {\n const box = unpackBox(check);\n return box !== undefined && open(key, box, VAULT_CHECK_AAD) === VAULT_CHECK_PLAINTEXT;\n}\n\nfunction isBusy(error: unknown): boolean {\n if (error === null || typeof error !== 'object') return false;\n const code = 'errcode' in error ? error.errcode : undefined;\n // SQLITE_BUSY and SQLITE_LOCKED.\n if (code === 5 || code === 6) return true;\n return error instanceof Error && /database (?:table )?is locked|SQLITE_BUSY/u.test(error.message);\n}\n\nfunction sleep(ms: number, signal: AbortSignal | undefined): Promise<void> {\n return new Promise((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/**\n * The vault on po-db: AES-256-GCM rows in `vault_entries` under one data-encryption key kept in\n * the OS keyring or, without one, in an owner-only `key.bin`.\n *\n * Every read and write is a single statement, so nothing here holds the shared connection's\n * write lock across a network call. Cross-process work that must span one goes through\n * `withLock`, a lease row with a TTL.\n */\nexport function createVault(options: CreateVaultOptions): VaultHandle {\n const { db, directory, keyFiles, onNotice } = options;\n const database = db.database;\n const now = options.now ?? Date.now;\n const leaseTtlMs = options.leaseTtlMs ?? VAULT_LEASE_TTL_MS;\n const leaseRenewMs = options.leaseRenewMs ?? VAULT_LEASE_RENEW_MS;\n const leaseWaitMs = options.leaseWaitMs ?? VAULT_LEASE_WAIT_MS;\n const renewals = new Set<ReturnType<typeof setInterval>>();\n let migrated: Promise<void> | undefined;\n let loaded: Promise<LoadedKey> | undefined;\n let keyringModule: Promise<VaultKeyring | null> | undefined;\n let backend: 'keyring' | 'file' | undefined;\n let closed = false;\n\n async function guarded<T>(work: () => Promise<T>): Promise<T> {\n try {\n return await work();\n } catch (error) {\n throw asVaultError(error);\n }\n }\n\n function ready(): Promise<void> {\n migrated ??= guarded(() => db.migrate(VAULT_DB_PREFIX, VAULT_MIGRATIONS)).catch((error: unknown) => {\n migrated = undefined;\n throw error;\n });\n return migrated;\n }\n\n async function readMeta(name: string): Promise<Uint8Array | undefined> {\n return (await database.get<{ value: Uint8Array }>(SELECT_META, name))?.value;\n }\n\n async function keyringAccount(): Promise<string> {\n await database.run(INSERT_META, VAULT_META_ID, randomBytes(VAULT_ID_BYTES));\n const id = await readMeta(VAULT_META_ID);\n if (id === undefined) throw new VaultError('unavailable');\n return `${VAULT_KEYRING_ACCOUNT_PREFIX}${Buffer.from(id).toString('hex')}`;\n }\n\n async function gather(account: string): Promise<Gathered> {\n keyringModule ??= options.keyring().catch(() => null);\n const keyring = await keyringModule;\n const candidates: Candidate[] = [];\n let keyringFailed = false;\n let fileFailed = false;\n if (keyring !== null) {\n try {\n const password = await keyring.getPassword(account);\n if (password !== null) {\n const key = Buffer.from(password, 'base64');\n if (key.length === VAULT_KEY_BYTES) candidates.push({ source: 'keyring', key });\n else keyringFailed = true;\n }\n } catch {\n keyringFailed = true;\n }\n }\n try {\n const key = await keyFiles.readKeyFile(directory);\n if (key !== undefined) candidates.push({ source: 'file', key });\n } catch {\n fileFailed = true;\n }\n return { candidates, keyring, keyringFailed, fileFailed };\n }\n\n /** The first candidate that decrypts `dek_check`, seeding the check from the first candidate when there is none. */\n async function pick(gathered: Gathered): Promise<LoadedKey | undefined> {\n let check = await readMeta(VAULT_META_CHECK);\n if (check === undefined) {\n const first = gathered.candidates[0];\n if (first === undefined) return undefined;\n await database.run(\n INSERT_META,\n VAULT_META_CHECK,\n packBox(seal(first.key, VAULT_CHECK_PLAINTEXT, VAULT_CHECK_AAD)),\n );\n check = await readMeta(VAULT_META_CHECK);\n if (check === undefined) return undefined;\n }\n for (const candidate of gathered.candidates) {\n if (!provesKey(candidate.key, check)) continue;\n backend = candidate.source;\n return { key: candidate.key, check };\n }\n return undefined;\n }\n\n /**\n * A source that failed may still hold the real key, so no new key replaces it. A throwing\n * keyring only blocks once a check row exists: before that there is nothing to lose, and a\n * host whose keyring daemon is missing falls back to `key.bin`.\n */\n function blocked(gathered: Gathered, check: Uint8Array | undefined): boolean {\n return gathered.fileFailed || (gathered.keyringFailed && check !== undefined);\n }\n\n /** Stores a new key in the keyring, read back to prove it; otherwise in a new `key.bin`. Never both. */\n async function store(gathered: Gathered, account: string, key: Uint8Array): Promise<'keyring' | 'file'> {\n if (gathered.keyring !== null) {\n const encoded = Buffer.from(key).toString('base64');\n try {\n await gathered.keyring.setPassword(account, encoded);\n if ((await gathered.keyring.getPassword(account)) === encoded) return 'keyring';\n } catch {\n // Falls through to the key file.\n }\n }\n // A key.bin that failed the check belongs to no key this database knows; O_EXCL needs it gone.\n if (gathered.candidates.some((candidate) => candidate.source === 'file')) await keyFiles.removeKeyFile(directory);\n await keyFiles.createKeyFile(directory, key);\n return 'file';\n }\n\n async function loadKey(): Promise<LoadedKey> {\n await ready();\n const account = await keyringAccount();\n const first = await gather(account);\n const found = await pick(first);\n if (found !== undefined) return found;\n if (blocked(first, await readMeta(VAULT_META_CHECK))) throw new VaultError('unavailable');\n return lease(VAULT_INIT_LEASE, async () => {\n const again = await gather(account);\n const existing = await pick(again);\n if (existing !== undefined) return existing;\n const check = await readMeta(VAULT_META_CHECK);\n if (blocked(again, check)) throw new VaultError('unavailable');\n const key = randomKey();\n const source = await store(again, account, key);\n const packed = packBox(seal(key, VAULT_CHECK_PLAINTEXT, VAULT_CHECK_AAD));\n if (check === undefined) {\n await database.run(REPLACE_META, VAULT_META_CHECK, packed);\n } else {\n // The old key is gone, so its rows can never be read again.\n await database.transaction(async (transaction) => {\n await transaction.run('DELETE FROM vault_entries');\n await transaction.run(REPLACE_META, VAULT_META_CHECK, packed);\n });\n onNotice(VAULT_KEY_LOST_NOTICE);\n }\n backend = source;\n return { key, check: packed };\n });\n }\n\n function currentKey(): Promise<LoadedKey> {\n loaded ??= guarded(loadKey).catch((error: unknown) => {\n loaded = undefined;\n throw error;\n });\n return loaded;\n }\n\n /** The key, reloaded first when another process has replaced `dek_check` since it was loaded. */\n async function freshKey(): Promise<LoadedKey> {\n const cached = await currentKey();\n const check = await guarded(() => readMeta(VAULT_META_CHECK));\n if (check !== undefined && bytesEqual(check, cached.check)) return cached;\n if (loaded !== undefined && (await loaded) === cached) loaded = undefined;\n return currentKey();\n }\n\n async function tryAcquire(name: string, owner: string): Promise<boolean> {\n const at = now();\n try {\n return (await database.get(ACQUIRE_LEASE, name, owner, at + leaseTtlMs, at)) !== undefined;\n } catch (error) {\n if (isBusy(error)) return false;\n throw new VaultError('unavailable');\n }\n }\n\n async function lease<T>(name: string, op: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T> {\n if (closed) throw new VaultError('unavailable');\n await ready();\n const owner = randomUUID();\n const deadline = now() + leaseWaitMs;\n for (;;) {\n signal?.throwIfAborted();\n if (await tryAcquire(name, owner)) break;\n if (now() >= deadline) throw new VaultError('unavailable');\n await sleep(VAULT_LEASE_POLL_MS + Math.random() * VAULT_LEASE_JITTER_MS, signal);\n }\n const controller = new AbortController();\n const onAbort = () => controller.abort(signal?.reason);\n signal?.addEventListener('abort', onAbort, { once: true });\n const timer = setInterval(() => {\n tryAcquire(name, owner).then(\n (held) => {\n if (!held) controller.abort(new VaultError('unavailable'));\n },\n // ponytail: a failed renewal waits for the next tick; the TTL decides whether the lease survives.\n () => undefined,\n );\n }, leaseRenewMs);\n timer.unref?.();\n renewals.add(timer);\n try {\n return await op(controller.signal);\n } finally {\n clearInterval(timer);\n renewals.delete(timer);\n signal?.removeEventListener('abort', onAbort);\n try {\n await database.run(RELEASE_LEASE, name, owner);\n } catch {\n // The TTL releases a lease whose delete failed.\n }\n }\n }\n\n async function writeRow(sql: string, params: readonly (string | number | Uint8Array)[]): Promise<WrittenRow> {\n const row = await guarded(() => database.get<WrittenRow>(sql, ...params));\n if (row === undefined) throw new VaultError('conflict');\n return row;\n }\n\n return {\n async get(namespace: string, key: string): Promise<VaultRecord | undefined> {\n assertEntryName(namespace, key);\n await ready();\n const row = await guarded(() => database.get<EntryRow>(SELECT_ENTRY, namespace, key));\n if (row === undefined) return undefined;\n const aad = entryAad(namespace, key);\n let loadedKey = await currentKey();\n let text = open(loadedKey.key, boxOf(row), aad);\n if (text === undefined) {\n const check = await guarded(() => readMeta(VAULT_META_CHECK));\n if (check === undefined || !provesKey(loadedKey.key, check)) {\n loaded = undefined;\n loadedKey = await currentKey();\n text = open(loadedKey.key, boxOf(row), aad);\n }\n }\n if (text === undefined) {\n // The key is right and the row still fails: it was tampered with or moved; nobody can read it.\n await guarded(() =>\n database.run(\n 'DELETE FROM vault_entries WHERE namespace = ? AND key = ? AND revision = ?',\n namespace,\n key,\n row.revision,\n ),\n );\n return undefined;\n }\n return { value: JSON.parse(text) as VaultValue, revision: row.revision, updatedAt: row.updated_at };\n },\n\n async put(namespace: string, key: string, value: VaultValue, putOptions?: VaultPutOptions): Promise<VaultRecord> {\n assertEntryName(namespace, key);\n const expectRevision = putOptions?.expectRevision;\n if (expectRevision !== undefined && expectRevision !== null) assertRevision(expectRevision);\n const text = serializeValue(value);\n await ready();\n const loadedKey = await freshKey();\n const box = seal(loadedKey.key, text, entryAad(namespace, key));\n const at = now();\n const row =\n expectRevision === undefined\n ? await writeRow(PUT_LAST_WRITE_WINS, [namespace, key, at, box.iv, box.tag, box.ciphertext])\n : expectRevision === null\n ? await writeRow(PUT_CREATE_ONLY, [namespace, key, at, box.iv, box.tag, box.ciphertext])\n : await writeRow(PUT_EXPECT_REVISION, [\n expectRevision + 1,\n at,\n box.iv,\n box.tag,\n box.ciphertext,\n namespace,\n key,\n expectRevision,\n ]);\n return { value: JSON.parse(text) as VaultValue, revision: row.revision, updatedAt: row.updated_at };\n },\n\n async delete(namespace: string, key: string, deleteOptions?: VaultDeleteOptions): Promise<void> {\n assertEntryName(namespace, key);\n const expectRevision = deleteOptions?.expectRevision;\n if (expectRevision !== undefined) assertRevision(expectRevision);\n await ready();\n if (expectRevision === undefined) {\n await guarded(() => database.run('DELETE FROM vault_entries WHERE namespace = ? AND key = ?', namespace, key));\n return;\n }\n await writeRow(\n 'DELETE FROM vault_entries WHERE namespace = ? AND key = ? AND revision = ? RETURNING revision, 0 AS updated_at',\n [namespace, key, expectRevision],\n );\n },\n\n withLock<T>(\n namespace: string,\n key: string,\n op: (signal: AbortSignal) => Promise<T>,\n signal?: AbortSignal,\n ): Promise<T> {\n try {\n assertEntryName(namespace, key);\n } catch (error) {\n return Promise.reject(error);\n }\n if (typeof op !== 'function') return Promise.reject(new VaultError('invalid'));\n return lease(entryAad(namespace, key), op, signal);\n },\n\n status() {\n return { backend };\n },\n\n close(): void {\n closed = true;\n for (const timer of renewals) clearInterval(timer);\n renewals.clear();\n },\n };\n}\n","import { constants } from 'node:fs';\nimport { lstat, mkdir, open, chmod, unlink } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { VAULT_DIRECTORY_MODE, VAULT_KEY_BYTES, VAULT_KEY_FILE, VAULT_KEY_FILE_MODE } from '../../constants/vault';\nimport { VaultError } from '../vaultRules';\n\n/** Windows has no O_NOFOLLOW; there a plain open stands in. */\nconst NO_FOLLOW = constants.O_NOFOLLOW ?? 0;\nconst GROUP_OTHER_BITS = 0o077;\nconst POSIX = process.platform !== 'win32';\n\nfunction currentUid(): number | undefined {\n return typeof process.getuid === 'function' ? process.getuid() : undefined;\n}\n\nfunction ownedByUser(uid: number): boolean {\n const user = currentUid();\n return user === undefined || uid === user;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return error !== null && typeof error === 'object' && 'code' in error ? String(error.code) : undefined;\n}\n\n/**\n * Creates `directory` owner-only, or checks and repairs an existing one.\n *\n * Refuses a symlink, a non-directory and a folder another user owns. Loose permission bits\n * are tightened to 0700.\n */\nexport async function preparePrivateDirectory(directory: string): Promise<void> {\n try {\n await mkdir(directory, { recursive: true, mode: VAULT_DIRECTORY_MODE });\n const stats = await lstat(directory);\n if (stats.isSymbolicLink() || !stats.isDirectory() || !ownedByUser(stats.uid)) throw new VaultError('unavailable');\n if (POSIX && (stats.mode & GROUP_OTHER_BITS) !== 0) await chmod(directory, VAULT_DIRECTORY_MODE);\n } catch (error) {\n throw error instanceof VaultError ? error : new VaultError('unavailable');\n }\n}\n\n/**\n * The 32-byte key in `<directory>/key.bin`, or `undefined` when there is none.\n *\n * The file must be a regular, owner-only file with one link, opened without following a\n * symlink; anything else throws `unavailable` so the caller never replaces a key it could not read.\n */\nexport async function readKeyFile(directory: string): Promise<Uint8Array | undefined> {\n let handle;\n try {\n handle = await open(path.join(directory, VAULT_KEY_FILE), constants.O_RDONLY | NO_FOLLOW);\n } catch (error) {\n if (errorCode(error) === 'ENOENT' || errorCode(error) === 'ENOTDIR') return undefined;\n throw new VaultError('unavailable');\n }\n try {\n const stats = await handle.stat();\n if (\n !stats.isFile() ||\n stats.nlink !== 1 ||\n !ownedByUser(stats.uid) ||\n (POSIX && (stats.mode & GROUP_OTHER_BITS) !== 0) ||\n stats.size !== VAULT_KEY_BYTES\n ) {\n throw new VaultError('unavailable');\n }\n const key = Buffer.alloc(VAULT_KEY_BYTES);\n const { bytesRead } = await handle.read(key, 0, VAULT_KEY_BYTES, 0);\n if (bytesRead !== VAULT_KEY_BYTES) throw new VaultError('unavailable');\n return key;\n } catch (error) {\n throw error instanceof VaultError ? error : new VaultError('unavailable');\n } finally {\n await handle.close();\n }\n}\n\n/** Writes a new `key.bin` (0600, exclusive create, no symlink follow) and syncs it to disk. */\nexport async function createKeyFile(directory: string, key: Uint8Array): Promise<void> {\n if (key.length !== VAULT_KEY_BYTES) throw new VaultError('invalid');\n await preparePrivateDirectory(directory);\n try {\n const handle = await open(\n path.join(directory, VAULT_KEY_FILE),\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | NO_FOLLOW,\n VAULT_KEY_FILE_MODE,\n );\n try {\n await handle.write(key, 0, key.length, 0);\n await handle.sync();\n } finally {\n await handle.close();\n }\n } catch {\n throw new VaultError('unavailable');\n }\n}\n\n/** Removes a `key.bin` the vault proved stale; a missing file is fine. */\nexport async function removeKeyFile(directory: string): Promise<void> {\n try {\n await unlink(path.join(directory, VAULT_KEY_FILE));\n } catch (error) {\n if (errorCode(error) !== 'ENOENT') throw new VaultError('unavailable');\n }\n}\n","import { VAULT_KEYRING_SERVICE } from '../../constants/vault';\nimport type { VaultKeyring } from '../vault/type';\n\n/**\n * The OS keyring through `@napi-rs/keyring`, loaded on first use, or `null` when this platform\n * has no build of the module.\n */\nexport async function loadKeyring(): Promise<VaultKeyring | null> {\n try {\n const { AsyncEntry } = await import('@napi-rs/keyring');\n return {\n getPassword: (account) => new AsyncEntry(VAULT_KEYRING_SERVICE, account).getPassword(),\n setPassword: (account, password) => new AsyncEntry(VAULT_KEYRING_SERVICE, account).setPassword(password),\n };\n } catch {\n return null;\n }\n}\n","import path from 'node:path';\n\nimport type { RootDeclaration } from '@agimon-ai/po-core/extensionFile';\nimport { poHomeDirectory } from '@agimon-ai/po-core/piSettings';\nimport {\n PO_SERVER_SCOPE_SERVICE,\n requirePoServerScope,\n type PoServerPluginContext,\n} from '@agimon-ai/po-core/serverPlugin';\nimport { PO_DB_SERVICE, requirePoDb, type PoDb } from '@agimon-ai/po-db';\n\nimport { VAULT_DIRECTORY } from '../../../constants/vault';\nimport { PO_VAULT_CONTRACT } from '../../../schemas/contracts';\nimport { createVault } from '../../../services/vault';\nimport type { VaultHandle } from '../../../services/vault/type';\nimport { createKeyFile, readKeyFile, removeKeyFile } from '../../../services/vaultDirectory';\nimport { loadKeyring } from '../../../services/vaultKeyring';\n\n/**\n * The vault, bound as `po/vault` at the global level on top of the shared `po/db`.\n *\n * Only a global host with a home directory builds it. The vault loads its key lazily on first\n * use, so mounting never touches the keyring or `<po home>/vault`.\n */\nexport async function createPoVaultGlobalRoot(\n plugin: PoServerPluginContext,\n): Promise<RootDeclaration<VaultHandle | undefined, PoServerPluginContext>> {\n const host = plugin.host;\n const homeDirectory = host.context.homeDirectory;\n if (host.scope !== 'global' || homeDirectory === undefined) return { value: undefined };\n const directory = path.join(poHomeDirectory(homeDirectory, host.context.environment ?? {}), VAULT_DIRECTORY);\n let vault: VaultHandle | undefined;\n let boundDb: PoDb | undefined;\n const provider = plugin.context.inject([PO_SERVER_SCOPE_SERVICE, PO_DB_SERVICE], (injected) => {\n // One vault per po-db binding; a re-run of this fiber with the same database only publishes it again.\n const db = requirePoDb(injected);\n if (vault === undefined || boundDb !== db) {\n vault?.close();\n boundDb = db;\n vault = createVault({\n db,\n directory,\n keyring: loadKeyring,\n keyFiles: { readKeyFile, createKeyFile, removeKeyFile },\n onNotice: (message) => host.context.onNotice(`Vault: ${message}`),\n });\n }\n return requirePoServerScope(injected).provide(PO_VAULT_CONTRACT, vault);\n });\n await provider.await();\n return {\n value: vault,\n onDispose: async () => {\n await provider.dispose();\n vault?.close();\n },\n };\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport { PO_DB_CONTRACT } from '@agimon-ai/po-db';\n\nimport { createPoVaultGlobalRoot } from './_lib/root.server';\n\nexport default defineRoot(createPoVaultGlobalRoot, { inject: [PO_DB_CONTRACT] });\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverPlugin';\nimport { composeRootHooks, composeRootInjects } from '@agimon-ai/po-core/extensionFile';\n\nimport rootGlobal from '../src/extensions/(backend)/root.server';\n\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-vault',\n providerOverrides: composeServerProviderOverrides(rootGlobal),\n injects: composeRootInjects({ global: [rootGlobal], workspace: [rootGlobal], session: [rootGlobal] }),\n global: async (context) => {\n const scopeGlobal = await context.scopeRoot('global', rootGlobal, context);\n return {\n ...composeRootHooks(scopeGlobal),\n services: [...(scopeGlobal.services ?? [])],\n };\n },\n workspace: async (context) => {\n const scopeGlobal = await context.scopeRoot('global', rootGlobal, context);\n return {\n ...composeRootHooks(scopeGlobal),\n services: [...(scopeGlobal.services ?? [])],\n };\n },\n session: async (context) => {\n const scopeGlobal = await context.scopeRoot('global', rootGlobal, context);\n return {\n ...composeRootHooks(scopeGlobal),\n services: [...(scopeGlobal.services ?? [])],\n get activities(): PoServerSessionPlugin['activities'] { return [...(scopeGlobal.activities ?? [])]; },\n };\n },\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_vaultRules = require("./vaultRules-CUudYo7G.cjs");
|
|
3
|
+
const require_contracts = require("./contracts-Do7L_d_L.cjs");
|
|
4
|
+
exports.PO_VAULT_CONTRACT = require_contracts.PO_VAULT_CONTRACT;
|
|
5
|
+
exports.PO_VAULT_SERVICE = require_vaultRules.PO_VAULT_SERVICE;
|
|
6
|
+
exports.VAULT_KEYRING_SERVICE = require_vaultRules.VAULT_KEYRING_SERVICE;
|
|
7
|
+
exports.VaultError = require_vaultRules.VaultError;
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { a as VaultRecord, i as VaultPutOptions, n as VaultDeleteOptions, o as VaultStatus, r as VaultErrorCode, s as VaultValue, t as Vault } from "./type-fwXKnI1F.mjs";
|
|
2
|
+
//#region src/constants/vault.d.ts
|
|
3
|
+
export declare const PO_VAULT_SERVICE = "po/vault";
|
|
4
|
+
/** The OS keyring service the data-encryption key is stored under, one account per vault id. */
|
|
5
|
+
export declare const VAULT_KEYRING_SERVICE = "ai.agimon.po-vault";
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/schemas/contracts.d.ts
|
|
8
|
+
/** Bound once at the global level; every workspace and session resolves the same vault. */
|
|
9
|
+
export declare const PO_VAULT_CONTRACT: import("@agimon-ai/po-core/serverPlugin").PoServiceContract<Vault>;
|
|
10
|
+
declare module '@deepseek-ai/cordis' {
|
|
11
|
+
interface Context {
|
|
12
|
+
'po/vault': Vault;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/services/vaultRules/index.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* Every vault failure. The message is fixed per code, so no key, value or upstream cause can
|
|
19
|
+
* reach a log or a response through it. Consumers match on `code`.
|
|
20
|
+
*/
|
|
21
|
+
export declare class VaultError extends Error {
|
|
22
|
+
readonly code: VaultErrorCode;
|
|
23
|
+
constructor(code: VaultErrorCode);
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export type { Vault, VaultDeleteOptions, VaultErrorCode, VaultPutOptions, VaultRecord, VaultStatus, VaultValue };
|
|
27
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/constants/vault.ts","../src/schemas/contracts.ts","../src/services/vaultRules/index.ts"],"mappings":";;qBAAa;;qBAEA;;;;qBCIA,6DAAiB,kBAAA;;YAGlB;IACR,YAAY;;;;;;;;;qBCGH,mBAAmB;WACrB,MAAM;EAEf,YAAY,MAAM"}
|
package/dist/index.mjs
ADDED
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_vaultRules = require("./vaultRules-CUudYo7G.cjs");
|
|
3
|
+
//#region src/services/memoryVault/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* An in-memory vault for tests, with the same names, limits, revisions, conflicts and lock
|
|
6
|
+
* ordering as the real one. Values are copied in and out, so a caller can't mutate a stored
|
|
7
|
+
* entry. The lock is in-process only and its signal aborts only when the caller's does.
|
|
8
|
+
*/
|
|
9
|
+
function createMemoryVault() {
|
|
10
|
+
const entries = /* @__PURE__ */ new Map();
|
|
11
|
+
const locks = /* @__PURE__ */ new Map();
|
|
12
|
+
const id = (namespace, key) => `${namespace}\0${key}`;
|
|
13
|
+
let clock = 0;
|
|
14
|
+
const tick = () => {
|
|
15
|
+
clock = Math.max(clock + 1, Date.now());
|
|
16
|
+
return clock;
|
|
17
|
+
};
|
|
18
|
+
const record = (entry) => ({
|
|
19
|
+
value: JSON.parse(entry.text),
|
|
20
|
+
revision: entry.revision,
|
|
21
|
+
updatedAt: entry.updatedAt
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
async get(namespace, key) {
|
|
25
|
+
require_vaultRules.assertEntryName(namespace, key);
|
|
26
|
+
const entry = entries.get(id(namespace, key));
|
|
27
|
+
return entry === void 0 ? void 0 : record(entry);
|
|
28
|
+
},
|
|
29
|
+
async put(namespace, key, value, options) {
|
|
30
|
+
require_vaultRules.assertEntryName(namespace, key);
|
|
31
|
+
const expectRevision = options?.expectRevision;
|
|
32
|
+
if (expectRevision !== void 0 && expectRevision !== null) require_vaultRules.assertRevision(expectRevision);
|
|
33
|
+
const text = require_vaultRules.serializeValue(value);
|
|
34
|
+
const name = id(namespace, key);
|
|
35
|
+
const current = entries.get(name);
|
|
36
|
+
if (expectRevision === null && current !== void 0) throw new require_vaultRules.VaultError("conflict");
|
|
37
|
+
if (typeof expectRevision === "number" && current?.revision !== expectRevision) throw new require_vaultRules.VaultError("conflict");
|
|
38
|
+
const next = {
|
|
39
|
+
text,
|
|
40
|
+
revision: (current?.revision ?? 0) + 1,
|
|
41
|
+
updatedAt: tick()
|
|
42
|
+
};
|
|
43
|
+
entries.set(name, next);
|
|
44
|
+
return record(next);
|
|
45
|
+
},
|
|
46
|
+
async delete(namespace, key, options) {
|
|
47
|
+
require_vaultRules.assertEntryName(namespace, key);
|
|
48
|
+
const expectRevision = options?.expectRevision;
|
|
49
|
+
if (expectRevision !== void 0) require_vaultRules.assertRevision(expectRevision);
|
|
50
|
+
const name = id(namespace, key);
|
|
51
|
+
if (expectRevision !== void 0 && entries.get(name)?.revision !== expectRevision) throw new require_vaultRules.VaultError("conflict");
|
|
52
|
+
entries.delete(name);
|
|
53
|
+
},
|
|
54
|
+
async withLock(namespace, key, op, signal) {
|
|
55
|
+
require_vaultRules.assertEntryName(namespace, key);
|
|
56
|
+
if (typeof op !== "function") throw new require_vaultRules.VaultError("invalid");
|
|
57
|
+
const name = id(namespace, key);
|
|
58
|
+
const previous = locks.get(name) ?? Promise.resolve();
|
|
59
|
+
let release = () => void 0;
|
|
60
|
+
const held = new Promise((resolve) => {
|
|
61
|
+
release = resolve;
|
|
62
|
+
});
|
|
63
|
+
const tail = previous.then(() => held);
|
|
64
|
+
locks.set(name, tail);
|
|
65
|
+
try {
|
|
66
|
+
await previous;
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
const controller = new AbortController();
|
|
69
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
70
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
71
|
+
try {
|
|
72
|
+
return await op(controller.signal);
|
|
73
|
+
} finally {
|
|
74
|
+
signal?.removeEventListener("abort", onAbort);
|
|
75
|
+
}
|
|
76
|
+
} finally {
|
|
77
|
+
release();
|
|
78
|
+
if (locks.get(name) === tail) locks.delete(name);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
status() {
|
|
82
|
+
return { backend: void 0 };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
exports.createMemoryVault = createMemoryVault;
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.cjs","names":["serializeValue","VaultError"],"sources":["../src/services/memoryVault/index.ts"],"sourcesContent":["import type { Vault, VaultDeleteOptions, VaultPutOptions, VaultRecord, VaultValue } from '../vault/type';\nimport { assertEntryName, assertRevision, serializeValue, VaultError } from '../vaultRules';\n\ninterface MemoryEntry {\n readonly text: string;\n readonly revision: number;\n readonly updatedAt: number;\n}\n\n/**\n * An in-memory vault for tests, with the same names, limits, revisions, conflicts and lock\n * ordering as the real one. Values are copied in and out, so a caller can't mutate a stored\n * entry. The lock is in-process only and its signal aborts only when the caller's does.\n */\nexport function createMemoryVault(): Vault {\n const entries = new Map<string, MemoryEntry>();\n const locks = new Map<string, Promise<void>>();\n const id = (namespace: string, key: string) => `${namespace}\\0${key}`;\n let clock = 0;\n const tick = () => {\n clock = Math.max(clock + 1, Date.now());\n return clock;\n };\n\n const record = (entry: MemoryEntry): VaultRecord => ({\n value: JSON.parse(entry.text) as VaultValue,\n revision: entry.revision,\n updatedAt: entry.updatedAt,\n });\n\n return {\n async get(namespace: string, key: string): Promise<VaultRecord | undefined> {\n assertEntryName(namespace, key);\n const entry = entries.get(id(namespace, key));\n return entry === undefined ? undefined : record(entry);\n },\n\n async put(namespace: string, key: string, value: VaultValue, options?: VaultPutOptions): Promise<VaultRecord> {\n assertEntryName(namespace, key);\n const expectRevision = options?.expectRevision;\n if (expectRevision !== undefined && expectRevision !== null) assertRevision(expectRevision);\n const text = serializeValue(value);\n const name = id(namespace, key);\n const current = entries.get(name);\n if (expectRevision === null && current !== undefined) throw new VaultError('conflict');\n if (typeof expectRevision === 'number' && current?.revision !== expectRevision) throw new VaultError('conflict');\n const next: MemoryEntry = { text, revision: (current?.revision ?? 0) + 1, updatedAt: tick() };\n entries.set(name, next);\n return record(next);\n },\n\n async delete(namespace: string, key: string, options?: VaultDeleteOptions): Promise<void> {\n assertEntryName(namespace, key);\n const expectRevision = options?.expectRevision;\n if (expectRevision !== undefined) assertRevision(expectRevision);\n const name = id(namespace, key);\n if (expectRevision !== undefined && entries.get(name)?.revision !== expectRevision)\n throw new VaultError('conflict');\n entries.delete(name);\n },\n\n async withLock<T>(\n namespace: string,\n key: string,\n op: (signal: AbortSignal) => Promise<T>,\n signal?: AbortSignal,\n ): Promise<T> {\n assertEntryName(namespace, key);\n if (typeof op !== 'function') throw new VaultError('invalid');\n const name = id(namespace, key);\n const previous = locks.get(name) ?? Promise.resolve();\n let release: () => void = () => undefined;\n const held = new Promise<void>((resolve) => {\n release = resolve;\n });\n const tail = previous.then(() => held);\n locks.set(name, tail);\n try {\n await previous;\n signal?.throwIfAborted();\n const controller = new AbortController();\n const onAbort = () => controller.abort(signal?.reason);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n return await op(controller.signal);\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n } finally {\n release();\n if (locks.get(name) === tail) locks.delete(name);\n }\n },\n\n status() {\n return { backend: undefined };\n },\n };\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,oBAA2B;CACzC,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,wBAAQ,IAAI,IAA2B;CAC7C,MAAM,MAAM,WAAmB,QAAgB,GAAG,UAAU,IAAI;CAChE,IAAI,QAAQ;CACZ,MAAM,aAAa;EACjB,QAAQ,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,CAAC;EACtC,OAAO;CACT;CAEA,MAAM,UAAU,WAAqC;EACnD,OAAO,KAAK,MAAM,MAAM,IAAI;EAC5B,UAAU,MAAM;EAChB,WAAW,MAAM;CACnB;CAEA,OAAO;EACL,MAAM,IAAI,WAAmB,KAA+C;GAC1E,mBAAA,gBAAgB,WAAW,GAAG;GAC9B,MAAM,QAAQ,QAAQ,IAAI,GAAG,WAAW,GAAG,CAAC;GAC5C,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,OAAO,KAAK;EACvD;EAEA,MAAM,IAAI,WAAmB,KAAa,OAAmB,SAAiD;GAC5G,mBAAA,gBAAgB,WAAW,GAAG;GAC9B,MAAM,iBAAiB,SAAS;GAChC,IAAI,mBAAmB,KAAA,KAAa,mBAAmB,MAAM,mBAAA,eAAe,cAAc;GAC1F,MAAM,OAAOA,mBAAAA,eAAe,KAAK;GACjC,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,MAAM,UAAU,QAAQ,IAAI,IAAI;GAChC,IAAI,mBAAmB,QAAQ,YAAY,KAAA,GAAW,MAAM,IAAIC,mBAAAA,WAAW,UAAU;GACrF,IAAI,OAAO,mBAAmB,YAAY,SAAS,aAAa,gBAAgB,MAAM,IAAIA,mBAAAA,WAAW,UAAU;GAC/G,MAAM,OAAoB;IAAE;IAAM,WAAW,SAAS,YAAY,KAAK;IAAG,WAAW,KAAK;GAAE;GAC5F,QAAQ,IAAI,MAAM,IAAI;GACtB,OAAO,OAAO,IAAI;EACpB;EAEA,MAAM,OAAO,WAAmB,KAAa,SAA6C;GACxF,mBAAA,gBAAgB,WAAW,GAAG;GAC9B,MAAM,iBAAiB,SAAS;GAChC,IAAI,mBAAmB,KAAA,GAAW,mBAAA,eAAe,cAAc;GAC/D,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,IAAI,mBAAmB,KAAA,KAAa,QAAQ,IAAI,IAAI,CAAC,EAAE,aAAa,gBAClE,MAAM,IAAIA,mBAAAA,WAAW,UAAU;GACjC,QAAQ,OAAO,IAAI;EACrB;EAEA,MAAM,SACJ,WACA,KACA,IACA,QACY;GACZ,mBAAA,gBAAgB,WAAW,GAAG;GAC9B,IAAI,OAAO,OAAO,YAAY,MAAM,IAAIA,mBAAAA,WAAW,SAAS;GAC5D,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,MAAM,WAAW,MAAM,IAAI,IAAI,KAAK,QAAQ,QAAQ;GACpD,IAAI,gBAA4B,KAAA;GAChC,MAAM,OAAO,IAAI,SAAe,YAAY;IAC1C,UAAU;GACZ,CAAC;GACD,MAAM,OAAO,SAAS,WAAW,IAAI;GACrC,MAAM,IAAI,MAAM,IAAI;GACpB,IAAI;IACF,MAAM;IACN,QAAQ,eAAe;IACvB,MAAM,aAAa,IAAI,gBAAgB;IACvC,MAAM,gBAAgB,WAAW,MAAM,QAAQ,MAAM;IACrD,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IACzD,IAAI;KACF,OAAO,MAAM,GAAG,WAAW,MAAM;IACnC,UAAU;KACR,QAAQ,oBAAoB,SAAS,OAAO;IAC9C;GACF,UAAU;IACR,QAAQ;IACR,IAAI,MAAM,IAAI,IAAI,MAAM,MAAM,MAAM,OAAO,IAAI;GACjD;EACF;EAEA,SAAS;GACP,OAAO,EAAE,SAAS,KAAA,EAAU;EAC9B;CACF;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as Vault } from "./type-fwXKnI1F.mjs";
|
|
2
|
+
//#region src/services/memoryVault/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* An in-memory vault for tests, with the same names, limits, revisions, conflicts and lock
|
|
5
|
+
* ordering as the real one. Values are copied in and out, so a caller can't mutate a stored
|
|
6
|
+
* entry. The lock is in-process only and its signal aborts only when the caller's does.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createMemoryVault(): Vault;
|
|
9
|
+
//#endregion
|
|
10
|
+
//# sourceMappingURL=testing.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.mts","names":[],"sources":["../src/services/memoryVault/index.ts"],"mappings":";;;;;;;wBAcgB,qBAAqB"}
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { a as serializeValue, i as assertRevision, r as assertEntryName, t as VaultError } from "./vaultRules-B84nuLpN.mjs";
|
|
2
|
+
//#region src/services/memoryVault/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* An in-memory vault for tests, with the same names, limits, revisions, conflicts and lock
|
|
5
|
+
* ordering as the real one. Values are copied in and out, so a caller can't mutate a stored
|
|
6
|
+
* entry. The lock is in-process only and its signal aborts only when the caller's does.
|
|
7
|
+
*/
|
|
8
|
+
function createMemoryVault() {
|
|
9
|
+
const entries = /* @__PURE__ */ new Map();
|
|
10
|
+
const locks = /* @__PURE__ */ new Map();
|
|
11
|
+
const id = (namespace, key) => `${namespace}\0${key}`;
|
|
12
|
+
let clock = 0;
|
|
13
|
+
const tick = () => {
|
|
14
|
+
clock = Math.max(clock + 1, Date.now());
|
|
15
|
+
return clock;
|
|
16
|
+
};
|
|
17
|
+
const record = (entry) => ({
|
|
18
|
+
value: JSON.parse(entry.text),
|
|
19
|
+
revision: entry.revision,
|
|
20
|
+
updatedAt: entry.updatedAt
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
async get(namespace, key) {
|
|
24
|
+
assertEntryName(namespace, key);
|
|
25
|
+
const entry = entries.get(id(namespace, key));
|
|
26
|
+
return entry === void 0 ? void 0 : record(entry);
|
|
27
|
+
},
|
|
28
|
+
async put(namespace, key, value, options) {
|
|
29
|
+
assertEntryName(namespace, key);
|
|
30
|
+
const expectRevision = options?.expectRevision;
|
|
31
|
+
if (expectRevision !== void 0 && expectRevision !== null) assertRevision(expectRevision);
|
|
32
|
+
const text = serializeValue(value);
|
|
33
|
+
const name = id(namespace, key);
|
|
34
|
+
const current = entries.get(name);
|
|
35
|
+
if (expectRevision === null && current !== void 0) throw new VaultError("conflict");
|
|
36
|
+
if (typeof expectRevision === "number" && current?.revision !== expectRevision) throw new VaultError("conflict");
|
|
37
|
+
const next = {
|
|
38
|
+
text,
|
|
39
|
+
revision: (current?.revision ?? 0) + 1,
|
|
40
|
+
updatedAt: tick()
|
|
41
|
+
};
|
|
42
|
+
entries.set(name, next);
|
|
43
|
+
return record(next);
|
|
44
|
+
},
|
|
45
|
+
async delete(namespace, key, options) {
|
|
46
|
+
assertEntryName(namespace, key);
|
|
47
|
+
const expectRevision = options?.expectRevision;
|
|
48
|
+
if (expectRevision !== void 0) assertRevision(expectRevision);
|
|
49
|
+
const name = id(namespace, key);
|
|
50
|
+
if (expectRevision !== void 0 && entries.get(name)?.revision !== expectRevision) throw new VaultError("conflict");
|
|
51
|
+
entries.delete(name);
|
|
52
|
+
},
|
|
53
|
+
async withLock(namespace, key, op, signal) {
|
|
54
|
+
assertEntryName(namespace, key);
|
|
55
|
+
if (typeof op !== "function") throw new VaultError("invalid");
|
|
56
|
+
const name = id(namespace, key);
|
|
57
|
+
const previous = locks.get(name) ?? Promise.resolve();
|
|
58
|
+
let release = () => void 0;
|
|
59
|
+
const held = new Promise((resolve) => {
|
|
60
|
+
release = resolve;
|
|
61
|
+
});
|
|
62
|
+
const tail = previous.then(() => held);
|
|
63
|
+
locks.set(name, tail);
|
|
64
|
+
try {
|
|
65
|
+
await previous;
|
|
66
|
+
signal?.throwIfAborted();
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
69
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
70
|
+
try {
|
|
71
|
+
return await op(controller.signal);
|
|
72
|
+
} finally {
|
|
73
|
+
signal?.removeEventListener("abort", onAbort);
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
release();
|
|
77
|
+
if (locks.get(name) === tail) locks.delete(name);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
status() {
|
|
81
|
+
return { backend: void 0 };
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { createMemoryVault };
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../src/services/memoryVault/index.ts"],"sourcesContent":["import type { Vault, VaultDeleteOptions, VaultPutOptions, VaultRecord, VaultValue } from '../vault/type';\nimport { assertEntryName, assertRevision, serializeValue, VaultError } from '../vaultRules';\n\ninterface MemoryEntry {\n readonly text: string;\n readonly revision: number;\n readonly updatedAt: number;\n}\n\n/**\n * An in-memory vault for tests, with the same names, limits, revisions, conflicts and lock\n * ordering as the real one. Values are copied in and out, so a caller can't mutate a stored\n * entry. The lock is in-process only and its signal aborts only when the caller's does.\n */\nexport function createMemoryVault(): Vault {\n const entries = new Map<string, MemoryEntry>();\n const locks = new Map<string, Promise<void>>();\n const id = (namespace: string, key: string) => `${namespace}\\0${key}`;\n let clock = 0;\n const tick = () => {\n clock = Math.max(clock + 1, Date.now());\n return clock;\n };\n\n const record = (entry: MemoryEntry): VaultRecord => ({\n value: JSON.parse(entry.text) as VaultValue,\n revision: entry.revision,\n updatedAt: entry.updatedAt,\n });\n\n return {\n async get(namespace: string, key: string): Promise<VaultRecord | undefined> {\n assertEntryName(namespace, key);\n const entry = entries.get(id(namespace, key));\n return entry === undefined ? undefined : record(entry);\n },\n\n async put(namespace: string, key: string, value: VaultValue, options?: VaultPutOptions): Promise<VaultRecord> {\n assertEntryName(namespace, key);\n const expectRevision = options?.expectRevision;\n if (expectRevision !== undefined && expectRevision !== null) assertRevision(expectRevision);\n const text = serializeValue(value);\n const name = id(namespace, key);\n const current = entries.get(name);\n if (expectRevision === null && current !== undefined) throw new VaultError('conflict');\n if (typeof expectRevision === 'number' && current?.revision !== expectRevision) throw new VaultError('conflict');\n const next: MemoryEntry = { text, revision: (current?.revision ?? 0) + 1, updatedAt: tick() };\n entries.set(name, next);\n return record(next);\n },\n\n async delete(namespace: string, key: string, options?: VaultDeleteOptions): Promise<void> {\n assertEntryName(namespace, key);\n const expectRevision = options?.expectRevision;\n if (expectRevision !== undefined) assertRevision(expectRevision);\n const name = id(namespace, key);\n if (expectRevision !== undefined && entries.get(name)?.revision !== expectRevision)\n throw new VaultError('conflict');\n entries.delete(name);\n },\n\n async withLock<T>(\n namespace: string,\n key: string,\n op: (signal: AbortSignal) => Promise<T>,\n signal?: AbortSignal,\n ): Promise<T> {\n assertEntryName(namespace, key);\n if (typeof op !== 'function') throw new VaultError('invalid');\n const name = id(namespace, key);\n const previous = locks.get(name) ?? Promise.resolve();\n let release: () => void = () => undefined;\n const held = new Promise<void>((resolve) => {\n release = resolve;\n });\n const tail = previous.then(() => held);\n locks.set(name, tail);\n try {\n await previous;\n signal?.throwIfAborted();\n const controller = new AbortController();\n const onAbort = () => controller.abort(signal?.reason);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n return await op(controller.signal);\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n } finally {\n release();\n if (locks.get(name) === tail) locks.delete(name);\n }\n },\n\n status() {\n return { backend: undefined };\n },\n };\n}\n"],"mappings":";;;;;;;AAcA,SAAgB,oBAA2B;CACzC,MAAM,0BAAU,IAAI,IAAyB;CAC7C,MAAM,wBAAQ,IAAI,IAA2B;CAC7C,MAAM,MAAM,WAAmB,QAAgB,GAAG,UAAU,IAAI;CAChE,IAAI,QAAQ;CACZ,MAAM,aAAa;EACjB,QAAQ,KAAK,IAAI,QAAQ,GAAG,KAAK,IAAI,CAAC;EACtC,OAAO;CACT;CAEA,MAAM,UAAU,WAAqC;EACnD,OAAO,KAAK,MAAM,MAAM,IAAI;EAC5B,UAAU,MAAM;EAChB,WAAW,MAAM;CACnB;CAEA,OAAO;EACL,MAAM,IAAI,WAAmB,KAA+C;GAC1E,gBAAgB,WAAW,GAAG;GAC9B,MAAM,QAAQ,QAAQ,IAAI,GAAG,WAAW,GAAG,CAAC;GAC5C,OAAO,UAAU,KAAA,IAAY,KAAA,IAAY,OAAO,KAAK;EACvD;EAEA,MAAM,IAAI,WAAmB,KAAa,OAAmB,SAAiD;GAC5G,gBAAgB,WAAW,GAAG;GAC9B,MAAM,iBAAiB,SAAS;GAChC,IAAI,mBAAmB,KAAA,KAAa,mBAAmB,MAAM,eAAe,cAAc;GAC1F,MAAM,OAAO,eAAe,KAAK;GACjC,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,MAAM,UAAU,QAAQ,IAAI,IAAI;GAChC,IAAI,mBAAmB,QAAQ,YAAY,KAAA,GAAW,MAAM,IAAI,WAAW,UAAU;GACrF,IAAI,OAAO,mBAAmB,YAAY,SAAS,aAAa,gBAAgB,MAAM,IAAI,WAAW,UAAU;GAC/G,MAAM,OAAoB;IAAE;IAAM,WAAW,SAAS,YAAY,KAAK;IAAG,WAAW,KAAK;GAAE;GAC5F,QAAQ,IAAI,MAAM,IAAI;GACtB,OAAO,OAAO,IAAI;EACpB;EAEA,MAAM,OAAO,WAAmB,KAAa,SAA6C;GACxF,gBAAgB,WAAW,GAAG;GAC9B,MAAM,iBAAiB,SAAS;GAChC,IAAI,mBAAmB,KAAA,GAAW,eAAe,cAAc;GAC/D,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,IAAI,mBAAmB,KAAA,KAAa,QAAQ,IAAI,IAAI,CAAC,EAAE,aAAa,gBAClE,MAAM,IAAI,WAAW,UAAU;GACjC,QAAQ,OAAO,IAAI;EACrB;EAEA,MAAM,SACJ,WACA,KACA,IACA,QACY;GACZ,gBAAgB,WAAW,GAAG;GAC9B,IAAI,OAAO,OAAO,YAAY,MAAM,IAAI,WAAW,SAAS;GAC5D,MAAM,OAAO,GAAG,WAAW,GAAG;GAC9B,MAAM,WAAW,MAAM,IAAI,IAAI,KAAK,QAAQ,QAAQ;GACpD,IAAI,gBAA4B,KAAA;GAChC,MAAM,OAAO,IAAI,SAAe,YAAY;IAC1C,UAAU;GACZ,CAAC;GACD,MAAM,OAAO,SAAS,WAAW,IAAI;GACrC,MAAM,IAAI,MAAM,IAAI;GACpB,IAAI;IACF,MAAM;IACN,QAAQ,eAAe;IACvB,MAAM,aAAa,IAAI,gBAAgB;IACvC,MAAM,gBAAgB,WAAW,MAAM,QAAQ,MAAM;IACrD,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;IACzD,IAAI;KACF,OAAO,MAAM,GAAG,WAAW,MAAM;IACnC,UAAU;KACR,QAAQ,oBAAoB,SAAS,OAAO;IAC9C;GACF,UAAU;IACR,QAAQ;IACR,IAAI,MAAM,IAAI,IAAI,MAAM,MAAM,MAAM,OAAO,IAAI;GACjD;EACF;EAEA,SAAS;GACP,OAAO,EAAE,SAAS,KAAA,EAAU;EAC9B;CACF;AACF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import "@agimon-ai/po-db";
|
|
2
|
+
//#region src/services/vault/type.d.ts
|
|
3
|
+
type VaultValue = null | boolean | number | string | VaultValue[] | {
|
|
4
|
+
[key: string]: VaultValue;
|
|
5
|
+
};
|
|
6
|
+
interface VaultRecord {
|
|
7
|
+
readonly value: VaultValue;
|
|
8
|
+
/** Starts at 1 and goes up by one on every write. */
|
|
9
|
+
readonly revision: number;
|
|
10
|
+
/** Milliseconds since the epoch. */
|
|
11
|
+
readonly updatedAt: number;
|
|
12
|
+
}
|
|
13
|
+
interface VaultStatus {
|
|
14
|
+
/** Where the data-encryption key lives; `undefined` until the vault has loaded it. */
|
|
15
|
+
readonly backend: 'keyring' | 'file' | undefined;
|
|
16
|
+
}
|
|
17
|
+
interface VaultPutOptions {
|
|
18
|
+
/** A number must match the stored revision, `null` means the entry must not exist, omitted means last write wins. */
|
|
19
|
+
readonly expectRevision?: number | null;
|
|
20
|
+
}
|
|
21
|
+
interface VaultDeleteOptions {
|
|
22
|
+
readonly expectRevision?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Po's only secret store.
|
|
26
|
+
*
|
|
27
|
+
* Every entry is encrypted at rest. Errors carry a code and a generic message, never a key,
|
|
28
|
+
* a value or an upstream cause.
|
|
29
|
+
*/
|
|
30
|
+
interface Vault {
|
|
31
|
+
get(namespace: string, key: string): Promise<VaultRecord | undefined>;
|
|
32
|
+
/** `expectRevision`: number = must match, null = must not exist, omitted = last write wins. One statement. */
|
|
33
|
+
put(namespace: string, key: string, value: VaultValue, options?: VaultPutOptions): Promise<VaultRecord>;
|
|
34
|
+
delete(namespace: string, key: string, options?: VaultDeleteOptions): Promise<void>;
|
|
35
|
+
/** Cross-process lease; may span network calls. The op's signal aborts if the lease is lost. */
|
|
36
|
+
withLock<T>(namespace: string, key: string, op: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
37
|
+
status(): VaultStatus;
|
|
38
|
+
}
|
|
39
|
+
type VaultErrorCode = 'unavailable' | 'invalid' | 'conflict';
|
|
40
|
+
//#endregion
|
|
41
|
+
export { VaultRecord as a, VaultPutOptions as i, VaultDeleteOptions as n, VaultStatus as o, VaultErrorCode as r, VaultValue as s, Vault as t };
|
|
42
|
+
//# sourceMappingURL=type-fwXKnI1F.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-fwXKnI1F.d.mts","names":[],"sources":["../src/services/vault/type.ts"],"mappings":";;KAEY,gDAAgD;GAAkB,cAAc;;UAE3E;WACN,OAAO;;WAEP;;WAEA;;UAGM;;WAEN;;UAGM;;WAEN;;UAGM;WACN;;;;;;;;UASM;EACf,IAAI,mBAAmB,cAAc,QAAQ;;EAE7C,IAAI,mBAAmB,aAAa,OAAO,YAAY,UAAU,kBAAkB,QAAQ;EAC3F,OAAO,mBAAmB,aAAa,UAAU,qBAAqB;;EAEtE,SAAS,GACP,mBACA,aACA,KAAK,QAAQ,gBAAgB,QAAQ,IACrC,SAAS,cACR,QAAQ;EACX,UAAU;;KAGA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//#region src/constants/vault.ts
|
|
2
|
+
const PO_VAULT_SERVICE = "po/vault";
|
|
3
|
+
/** The OS keyring service the data-encryption key is stored under, one account per vault id. */
|
|
4
|
+
const VAULT_KEYRING_SERVICE = "ai.agimon.po-vault";
|
|
5
|
+
/** The keyring account prefix; the account is `dek:<vault id in hex>`. */
|
|
6
|
+
const VAULT_KEYRING_ACCOUNT_PREFIX = "dek:";
|
|
7
|
+
/** The po-db table prefix the vault owns. */
|
|
8
|
+
const VAULT_DB_PREFIX = "vault";
|
|
9
|
+
/** The folder under the Po home that holds `key.bin` on hosts with no keyring. */
|
|
10
|
+
const VAULT_DIRECTORY = "vault";
|
|
11
|
+
const VAULT_KEY_FILE = "key.bin";
|
|
12
|
+
const VAULT_CHECK_PLAINTEXT = "po-vault-v1";
|
|
13
|
+
const VAULT_CHECK_AAD = "po-vault:check";
|
|
14
|
+
const VAULT_META_ID = "vault_id";
|
|
15
|
+
const VAULT_META_CHECK = "dek_check";
|
|
16
|
+
/** The lease that serializes first-time key creation across processes. */
|
|
17
|
+
const VAULT_INIT_LEASE = "vault:init";
|
|
18
|
+
const VAULT_NAMESPACE_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;
|
|
19
|
+
const VAULT_LEASE_TTL_MS = 3e4;
|
|
20
|
+
const VAULT_LEASE_RENEW_MS = 1e4;
|
|
21
|
+
const VAULT_LEASE_WAIT_MS = 35e3;
|
|
22
|
+
const VAULT_KEY_LOST_NOTICE = "Vault key was lost. Saved sign-ins were cleared; sign in again.";
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/services/vaultRules/index.ts
|
|
25
|
+
const MESSAGES = {
|
|
26
|
+
unavailable: "The vault is unavailable.",
|
|
27
|
+
invalid: "The vault request is invalid.",
|
|
28
|
+
conflict: "The vault entry changed."
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Every vault failure. The message is fixed per code, so no key, value or upstream cause can
|
|
32
|
+
* reach a log or a response through it. Consumers match on `code`.
|
|
33
|
+
*/
|
|
34
|
+
var VaultError = class extends Error {
|
|
35
|
+
code;
|
|
36
|
+
constructor(code) {
|
|
37
|
+
super(MESSAGES[code]);
|
|
38
|
+
this.name = "VaultError";
|
|
39
|
+
this.code = code;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/** Wraps any non-vault failure, typically sqlite, as `unavailable`, dropping its message. */
|
|
43
|
+
function asVaultError(error) {
|
|
44
|
+
return error instanceof VaultError ? error : new VaultError("unavailable");
|
|
45
|
+
}
|
|
46
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/u;
|
|
47
|
+
function assertEntryName(namespace, key) {
|
|
48
|
+
if (typeof namespace !== "string" || !VAULT_NAMESPACE_PATTERN.test(namespace)) throw new VaultError("invalid");
|
|
49
|
+
if (typeof key !== "string" || key.length === 0 || key.length > 512 || CONTROL_CHARACTERS.test(key)) throw new VaultError("invalid");
|
|
50
|
+
}
|
|
51
|
+
function assertRevision(revision) {
|
|
52
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) throw new VaultError("invalid");
|
|
53
|
+
}
|
|
54
|
+
function isPlainObject(value) {
|
|
55
|
+
const prototype = Object.getPrototypeOf(value);
|
|
56
|
+
return prototype === Object.prototype || prototype === null;
|
|
57
|
+
}
|
|
58
|
+
function assertJsonValue(value, seen) {
|
|
59
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return;
|
|
60
|
+
if (typeof value === "number") {
|
|
61
|
+
if (!Number.isFinite(value)) throw new VaultError("invalid");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (typeof value !== "object" || seen.has(value)) throw new VaultError("invalid");
|
|
65
|
+
seen.add(value);
|
|
66
|
+
if (Array.isArray(value)) for (const item of value) assertJsonValue(item, seen);
|
|
67
|
+
else {
|
|
68
|
+
if (!isPlainObject(value)) throw new VaultError("invalid");
|
|
69
|
+
for (const item of Object.values(value)) assertJsonValue(item, seen);
|
|
70
|
+
}
|
|
71
|
+
seen.delete(value);
|
|
72
|
+
}
|
|
73
|
+
/** The stored plaintext of a value: JSON, at most 64 KiB of UTF-8. */
|
|
74
|
+
function serializeValue(value) {
|
|
75
|
+
assertJsonValue(value, /* @__PURE__ */ new Set());
|
|
76
|
+
const text = JSON.stringify(value);
|
|
77
|
+
if (new TextEncoder().encode(text).length > 65536) throw new VaultError("invalid");
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { VAULT_LEASE_TTL_MS as _, serializeValue as a, VAULT_META_ID as b, VAULT_CHECK_PLAINTEXT as c, VAULT_INIT_LEASE as d, VAULT_KEYRING_ACCOUNT_PREFIX as f, VAULT_LEASE_RENEW_MS as g, VAULT_KEY_LOST_NOTICE as h, assertRevision as i, VAULT_DB_PREFIX as l, VAULT_KEY_FILE as m, asVaultError as n, PO_VAULT_SERVICE as o, VAULT_KEYRING_SERVICE as p, assertEntryName as r, VAULT_CHECK_AAD as s, VaultError as t, VAULT_DIRECTORY as u, VAULT_LEASE_WAIT_MS as v, VAULT_META_CHECK as y };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=vaultRules-B84nuLpN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vaultRules-B84nuLpN.mjs","names":[],"sources":["../src/constants/vault.ts","../src/services/vaultRules/index.ts"],"sourcesContent":["export const PO_VAULT_SERVICE = 'po/vault';\n/** The OS keyring service the data-encryption key is stored under, one account per vault id. */\nexport const VAULT_KEYRING_SERVICE = 'ai.agimon.po-vault';\n/** The keyring account prefix; the account is `dek:<vault id in hex>`. */\nexport const VAULT_KEYRING_ACCOUNT_PREFIX = 'dek:';\n/** The po-db table prefix the vault owns. */\nexport const VAULT_DB_PREFIX = 'vault';\n/** The folder under the Po home that holds `key.bin` on hosts with no keyring. */\nexport const VAULT_DIRECTORY = 'vault';\nexport const VAULT_KEY_FILE = 'key.bin';\nexport const VAULT_DIRECTORY_MODE = 0o700;\nexport const VAULT_KEY_FILE_MODE = 0o600;\n\nexport const VAULT_KEY_BYTES = 32;\nexport const VAULT_IV_BYTES = 12;\nexport const VAULT_TAG_BYTES = 16;\nexport const VAULT_ID_BYTES = 16;\nexport const VAULT_CHECK_PLAINTEXT = 'po-vault-v1';\nexport const VAULT_CHECK_AAD = 'po-vault:check';\nexport const VAULT_META_ID = 'vault_id';\nexport const VAULT_META_CHECK = 'dek_check';\n/** The lease that serializes first-time key creation across processes. */\nexport const VAULT_INIT_LEASE = 'vault:init';\n\nexport const VAULT_NAMESPACE_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/u;\nexport const VAULT_KEY_MAX_LENGTH = 512;\n/** The serialized value, in UTF-8 bytes. */\nexport const VAULT_VALUE_MAX_BYTES = 64 * 1024;\n\nexport const VAULT_LEASE_TTL_MS = 30_000;\nexport const VAULT_LEASE_RENEW_MS = 10_000;\nexport const VAULT_LEASE_WAIT_MS = 35_000;\nexport const VAULT_LEASE_POLL_MS = 50;\nexport const VAULT_LEASE_JITTER_MS = 50;\n\nexport const VAULT_KEY_LOST_NOTICE = 'Vault key was lost. Saved sign-ins were cleared; sign in again.';\n","import { VAULT_KEY_MAX_LENGTH, VAULT_NAMESPACE_PATTERN, VAULT_VALUE_MAX_BYTES } from '../../constants/vault';\nimport type { VaultErrorCode, VaultValue } from '../vault/type';\n\nconst MESSAGES: Readonly<Record<VaultErrorCode, string>> = {\n unavailable: 'The vault is unavailable.',\n invalid: 'The vault request is invalid.',\n conflict: 'The vault entry changed.',\n};\n\n/**\n * Every vault failure. The message is fixed per code, so no key, value or upstream cause can\n * reach a log or a response through it. Consumers match on `code`.\n */\nexport class VaultError extends Error {\n readonly code: VaultErrorCode;\n\n constructor(code: VaultErrorCode) {\n super(MESSAGES[code]);\n this.name = 'VaultError';\n this.code = code;\n }\n}\n\n/** Wraps any non-vault failure, typically sqlite, as `unavailable`, dropping its message. */\nexport function asVaultError(error: unknown): VaultError {\n return error instanceof VaultError ? error : new VaultError('unavailable');\n}\n\n// oxlint-disable-next-line no-control-regex -- the rule is exactly \"no control characters\"\nconst CONTROL_CHARACTERS = /[\\u0000-\\u001f\\u007f]/u;\n\nexport function assertEntryName(namespace: unknown, key: unknown): void {\n if (typeof namespace !== 'string' || !VAULT_NAMESPACE_PATTERN.test(namespace)) throw new VaultError('invalid');\n if (typeof key !== 'string' || key.length === 0 || key.length > VAULT_KEY_MAX_LENGTH || CONTROL_CHARACTERS.test(key))\n throw new VaultError('invalid');\n}\n\nexport function assertRevision(revision: unknown): void {\n if (typeof revision !== 'number' || !Number.isSafeInteger(revision) || revision < 1) throw new VaultError('invalid');\n}\n\nfunction isPlainObject(value: object): boolean {\n const prototype = Object.getPrototypeOf(value) as unknown;\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertJsonValue(value: unknown, seen: Set<object>): void {\n if (value === null || typeof value === 'string' || typeof value === 'boolean') return;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new VaultError('invalid');\n return;\n }\n if (typeof value !== 'object' || seen.has(value)) throw new VaultError('invalid');\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) assertJsonValue(item, seen);\n } else {\n if (!isPlainObject(value)) throw new VaultError('invalid');\n for (const item of Object.values(value)) assertJsonValue(item, seen);\n }\n seen.delete(value);\n}\n\n/** The stored plaintext of a value: JSON, at most 64 KiB of UTF-8. */\nexport function serializeValue(value: VaultValue): string {\n assertJsonValue(value, new Set());\n const text = JSON.stringify(value);\n if (new TextEncoder().encode(text).length > VAULT_VALUE_MAX_BYTES) throw new VaultError('invalid');\n return text;\n}\n"],"mappings":";AAAA,MAAa,mBAAmB;;AAEhC,MAAa,wBAAwB;;AAErC,MAAa,+BAA+B;;AAE5C,MAAa,kBAAkB;;AAE/B,MAAa,kBAAkB;AAC/B,MAAa,iBAAiB;AAQ9B,MAAa,wBAAwB;AACrC,MAAa,kBAAkB;AAC/B,MAAa,gBAAgB;AAC7B,MAAa,mBAAmB;;AAEhC,MAAa,mBAAmB;AAEhC,MAAa,0BAA0B;AAKvC,MAAa,qBAAqB;AAClC,MAAa,uBAAuB;AACpC,MAAa,sBAAsB;AAInC,MAAa,wBAAwB;;;AChCrC,MAAM,WAAqD;CACzD,aAAa;CACb,SAAS;CACT,UAAU;AACZ;;;;;AAMA,IAAa,aAAb,cAAgC,MAAM;CACpC;CAEA,YAAY,MAAsB;EAChC,MAAM,SAAS,KAAK;EACpB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;;AAGA,SAAgB,aAAa,OAA4B;CACvD,OAAO,iBAAiB,aAAa,QAAQ,IAAI,WAAW,aAAa;AAC3E;AAGA,MAAM,qBAAqB;AAE3B,SAAgB,gBAAgB,WAAoB,KAAoB;CACtE,IAAI,OAAO,cAAc,YAAY,CAAC,wBAAwB,KAAK,SAAS,GAAG,MAAM,IAAI,WAAW,SAAS;CAC7G,IAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,KAAK,IAAI,SAAA,OAAiC,mBAAmB,KAAK,GAAG,GACjH,MAAM,IAAI,WAAW,SAAS;AAClC;AAEA,SAAgB,eAAe,UAAyB;CACtD,IAAI,OAAO,aAAa,YAAY,CAAC,OAAO,cAAc,QAAQ,KAAK,WAAW,GAAG,MAAM,IAAI,WAAW,SAAS;AACrH;AAEA,SAAS,cAAc,OAAwB;CAC7C,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,OAAO,cAAc,OAAO,aAAa,cAAc;AACzD;AAEA,SAAS,gBAAgB,OAAgB,MAAyB;CAChE,IAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,WAAW;CAC/E,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,MAAM,IAAI,WAAW,SAAS;EAC3D;CACF;CACA,IAAI,OAAO,UAAU,YAAY,KAAK,IAAI,KAAK,GAAG,MAAM,IAAI,WAAW,SAAS;CAChF,KAAK,IAAI,KAAK;CACd,IAAI,MAAM,QAAQ,KAAK,GACrB,KAAK,MAAM,QAAQ,OAAO,gBAAgB,MAAM,IAAI;MAC/C;EACL,IAAI,CAAC,cAAc,KAAK,GAAG,MAAM,IAAI,WAAW,SAAS;EACzD,KAAK,MAAM,QAAQ,OAAO,OAAO,KAAK,GAAG,gBAAgB,MAAM,IAAI;CACrE;CACA,KAAK,OAAO,KAAK;AACnB;;AAGA,SAAgB,eAAe,OAA2B;CACxD,gBAAgB,uBAAO,IAAI,IAAI,CAAC;CAChC,MAAM,OAAO,KAAK,UAAU,KAAK;CACjC,IAAI,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAA,OAAgC,MAAM,IAAI,WAAW,SAAS;CACjG,OAAO;AACT"}
|