@agent-native/core 0.101.2 → 0.101.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/credentials/index.ts +41 -8
  5. package/corpus/core/src/extensions/fetch-tool.ts +33 -3
  6. package/corpus/core/src/notifications/channels.ts +20 -6
  7. package/corpus/core/src/secrets/crypto.ts +26 -10
  8. package/corpus/core/src/secrets/storage.ts +16 -3
  9. package/corpus/core/src/server/agent-chat-plugin.ts +60 -7
  10. package/corpus/core/src/server/derived-secret.ts +5 -1
  11. package/corpus/templates/analytics/changelog/2026-07-14-long-running-cross-app-analyses-now-continue-reliably-in-the.md +6 -0
  12. package/corpus/templates/analytics/server/plugins/agent-chat.ts +8 -4
  13. package/corpus/templates/clips/app/components/player/share-dialog.tsx +44 -81
  14. package/corpus/templates/clips/app/i18n/ar-SA.ts +0 -8
  15. package/corpus/templates/clips/app/i18n/de-DE.ts +0 -9
  16. package/corpus/templates/clips/app/i18n/en-US.ts +0 -8
  17. package/corpus/templates/clips/app/i18n/es-ES.ts +0 -9
  18. package/corpus/templates/clips/app/i18n/fr-FR.ts +0 -9
  19. package/corpus/templates/clips/app/i18n/hi-IN.ts +0 -8
  20. package/corpus/templates/clips/app/i18n/ja-JP.ts +0 -9
  21. package/corpus/templates/clips/app/i18n/ko-KR.ts +0 -9
  22. package/corpus/templates/clips/app/i18n/pt-BR.ts +0 -9
  23. package/corpus/templates/clips/app/i18n/zh-CN.ts +0 -7
  24. package/corpus/templates/clips/app/i18n/zh-TW.ts +0 -7
  25. package/corpus/templates/clips/changelog/2026-07-14-share-dialogs-show-separate-links-for-people-and-agents.md +6 -0
  26. package/corpus/templates/dispatch/changelog/2026-07-14-dispatch-now-distinguishes-connected-agents-from-locally-mou.md +6 -0
  27. package/dist/credentials/index.d.ts +12 -7
  28. package/dist/credentials/index.d.ts.map +1 -1
  29. package/dist/credentials/index.js +33 -8
  30. package/dist/credentials/index.js.map +1 -1
  31. package/dist/extensions/fetch-tool.d.ts +13 -1
  32. package/dist/extensions/fetch-tool.d.ts.map +1 -1
  33. package/dist/extensions/fetch-tool.js +9 -2
  34. package/dist/extensions/fetch-tool.js.map +1 -1
  35. package/dist/notifications/channels.js +20 -4
  36. package/dist/notifications/channels.js.map +1 -1
  37. package/dist/notifications/routes.d.ts +3 -3
  38. package/dist/observability/routes.d.ts +1 -1
  39. package/dist/resources/handlers.d.ts +1 -1
  40. package/dist/secrets/crypto.d.ts +15 -7
  41. package/dist/secrets/crypto.d.ts.map +1 -1
  42. package/dist/secrets/crypto.js +23 -10
  43. package/dist/secrets/crypto.js.map +1 -1
  44. package/dist/secrets/storage.d.ts.map +1 -1
  45. package/dist/secrets/storage.js +16 -3
  46. package/dist/secrets/storage.js.map +1 -1
  47. package/dist/server/agent-chat-plugin.d.ts +20 -0
  48. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  49. package/dist/server/agent-chat-plugin.js +38 -4
  50. package/dist/server/agent-chat-plugin.js.map +1 -1
  51. package/dist/server/derived-secret.d.ts +1 -1
  52. package/dist/server/derived-secret.d.ts.map +1 -1
  53. package/dist/server/derived-secret.js.map +1 -1
  54. package/package.json +2 -2
  55. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +0 -68
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage layer for the framework secrets registry.\n *\n * Values are encrypted at rest with AES-256-GCM. The workspace-shared\n * encryption key is derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the\n * existing `BETTER_AUTH_SECRET` env var (fallback so templates don't need a\n * second secret during development). Deployments that only have the legacy\n * app-scoped key continue to work until shared key material is configured;\n * once configured, new rows use the shared key and reads retain a legacy-key\n * fallback for existing rows.\n *\n * Secret values are NEVER logged and NEVER returned from any route handler.\n */\n\nimport { randomUUID } from \"node:crypto\";\n\nimport { getDbExec, isPostgres } from \"../db/client.js\";\nimport { ensureColumnExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport {\n encryptSecretValue as encryptLegacyValue,\n encryptSharedSecretValue as encryptValue,\n decryptSharedSecretValue as decryptValue,\n decryptSecretValue as decryptLegacyValue,\n hasSharedSecretEncryptionKeyMaterial,\n} from \"./crypto.js\";\nimport type { SecretScope } from \"./register.js\";\nimport { APP_SECRETS_CREATE_SQL } from \"./schema.js\";\n\n// ---------------------------------------------------------------------------\n// Table bootstrap\n// ---------------------------------------------------------------------------\n\nlet _initPromise: Promise<void> | undefined;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Postgres version of the CREATE TABLE — the generic `INTEGER` maps to\n // BIGINT on Postgres, which we need for millisecond timestamps.\n const createSql = isPostgres()\n ? APP_SECRETS_CREATE_SQL.replace(/\\bINTEGER\\b/g, \"BIGINT\")\n : APP_SECRETS_CREATE_SQL;\n\n if (isPostgres()) {\n // Hot path: in production the table and both additive columns are\n // virtually always already present. Issuing `CREATE`/`ALTER` would\n // still take an ACCESS EXCLUSIVE lock — which, in a fresh background\n // worker process behind a concurrent connection on the shared Neon DB,\n // can block ~indefinitely. `ensureTableExists` / `ensureColumnExists`\n // check `information_schema` first (a plain read, no lock) and run DDL\n // ONLY for what is actually missing, wrapping any DDL that must run in a\n // transaction-scoped `lock_timeout` so a contended lock fails fast. They\n // also re-probe after a swallowed lock-timeout and THROW if the schema\n // is still missing, so a timed-out DDL never poisons this init memo.\n await ensureTableExists(\"app_secrets\", createSql);\n await ensureColumnExists(\n \"app_secrets\",\n \"description\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS description TEXT`,\n );\n await ensureColumnExists(\n \"app_secrets\",\n \"url_allowlist\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,\n );\n await ensureColumnExists(\n \"app_secrets\",\n \"shared_encrypted_value\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS shared_encrypted_value TEXT`,\n );\n return;\n }\n\n // SQLite (local dev): no ACCESS EXCLUSIVE lock problem, keep the original\n // create-then-additive-alter behaviour. SQLite has no\n // `ADD COLUMN IF NOT EXISTS`, so the ALTERs stay wrapped in try/catch.\n await client.execute(createSql);\n\n // Additive migration: description column (for ad-hoc keys)\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN description TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n\n // Additive migration: url_allowlist column\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN url_allowlist TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n\n // Additive migration: workspace-shared ciphertext. Keep the legacy\n // encrypted_value column so older app versions remain readable.\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN shared_encrypted_value TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Encryption — see ./crypto.ts. Keep encrypted_value on the legacy app-key\n// format for mixed-version deployments, and add shared_encrypted_value when a\n// stable workspace key is configured so sibling apps can read the same row.\n// ---------------------------------------------------------------------------\n\n/**\n * Return the last 4 characters of a secret, with any leading characters\n * masked. Used to show a preview without leaking the value.\n */\nexport function last4(value: string): string {\n if (!value) return \"\";\n if (value.length <= 4) return \"••••\";\n return \"••••\" + value.slice(-4);\n}\n\n// ---------------------------------------------------------------------------\n// CRUD\n// ---------------------------------------------------------------------------\n\nexport interface SecretRef {\n key: string;\n scope: SecretScope;\n scopeId: string;\n}\n\nexport interface WriteSecretArgs extends SecretRef {\n value: string;\n /** Optional human-readable description (used for ad-hoc keys). */\n description?: string;\n /** Optional JSON-stringified array of allowed URL origins. */\n urlAllowlist?: string;\n}\n\n/**\n * Write (insert or update) a secret. The value is encrypted before being\n * stored — the caller's plaintext is never persisted. Returns the new\n * record's id.\n */\nexport async function writeAppSecret(args: WriteSecretArgs): Promise<string> {\n await ensureTable();\n const { key, value, scope, scopeId, description, urlAllowlist } = args;\n if (!key || !value || !scope || !scopeId) {\n throw new Error(\n \"writeAppSecret: key, value, scope, and scopeId are all required\",\n );\n }\n const client = getDbExec();\n const now = Date.now();\n // Dual-write during rollout: old readers continue using encrypted_value,\n // while new readers prefer the nullable shared ciphertext. An app-only\n // deployment leaves the shared column null; on an update, an existing\n // shared ciphertext is preserved by the upsert below.\n const encrypted = encryptLegacyValue(value);\n const sharedEncrypted = hasSharedSecretEncryptionKeyMaterial()\n ? encryptValue(value)\n : null;\n const id = randomUUID();\n\n // Atomic upsert by (scope, scope_id, key). Previously this was a\n // SELECT-then-branch (UPDATE if found, else INSERT): under concurrent\n // writers for the same key both could see \"no row\" and both attempt\n // INSERT, and the loser threw a raw UNIQUE(scope, scope_id, key)\n // constraint violation (a user-facing 500) instead of updating. A single\n // `INSERT ... ON CONFLICT DO UPDATE` closes that window — it's one\n // statement, so there's no gap between \"check\" and \"act\". `id` is\n // deliberately left out of the `DO UPDATE SET` list so an existing row\n // keeps its original id (any stored references stay stable); only a\n // genuinely new row gets the freshly generated `id`. This syntax is\n // portable across SQLite (UPSERT since 3.24) and Postgres.\n const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (scope, scope_id, key) DO UPDATE SET\n encrypted_value = excluded.encrypted_value,\n shared_encrypted_value = COALESCE(excluded.shared_encrypted_value, app_secrets.shared_encrypted_value),\n description = excluded.description,\n url_allowlist = excluded.url_allowlist,\n updated_at = excluded.updated_at`;\n const upsertArgs = [\n id,\n scope,\n scopeId,\n key,\n encrypted,\n sharedEncrypted,\n description ?? null,\n urlAllowlist ?? null,\n now,\n now,\n ];\n\n if (isPostgres()) {\n const { rows } = await client.execute({\n sql: `${upsertSql} RETURNING id`,\n args: upsertArgs,\n });\n return String(rows[0]?.id ?? id);\n }\n\n // SQLite: RETURNING support varies across better-sqlite3/libsql builds, so\n // (matching the convention elsewhere in this codebase, e.g.\n // integrations/pending-tasks-store.ts) re-read the id afterward instead of\n // relying on it. The row is guaranteed to exist at this point, so this is\n // a plain lookup rather than a TOCTOU-prone gate on the write itself.\n await client.execute({ sql: upsertSql, args: upsertArgs });\n const { rows } = await client.execute({\n sql: `SELECT id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n return String(rows[0]?.id ?? id);\n}\n\nexport interface ReadSecretResult {\n value: string;\n last4: string;\n updatedAt: number;\n}\n\n/**\n * Read the shared-key format and retain compatibility with rows written before\n * app_secrets moved to its workspace-shared encryption boundary. The legacy\n * fallback is only useful when the current app owns the old row; sibling apps\n * will receive shared-key ciphertext after the next vault sync or update.\n */\ninterface DecryptedAppSecretValue {\n value: string;\n /** True when the app-scoped fallback decrypted encrypted_value. */\n usedLegacyKey: boolean;\n /** True when shared_encrypted_value was not the source of the plaintext. */\n needsSharedCiphertext: boolean;\n}\n\nfunction decryptAppSecretValue(\n encrypted: string,\n sharedEncrypted?: string | null,\n): DecryptedAppSecretValue {\n if (sharedEncrypted) {\n try {\n return {\n value: decryptValue(sharedEncrypted),\n usedLegacyKey: false,\n needsSharedCiphertext: false,\n };\n } catch {\n // Fall through to the legacy column. A partially migrated row may have\n // a stale shared ciphertext while the old app-key value is valid.\n }\n }\n try {\n return {\n value: decryptValue(encrypted),\n usedLegacyKey: false,\n needsSharedCiphertext: true,\n };\n } catch {\n return {\n value: decryptLegacyValue(encrypted),\n usedLegacyKey: true,\n needsSharedCiphertext: true,\n };\n }\n}\n\n/**\n * Populate the shared column without touching encrypted_value. This is\n * intentionally best-effort: a read must still succeed if a deployment's DB\n * role cannot update the row. The NULL predicate prevents a concurrent writer\n * from overwriting newly-populated shared ciphertext, and leaving updated_at\n * untouched preserves the row's user-visible ordering/metadata.\n */\nasync function populateSharedAppSecret(\n id: unknown,\n value: string,\n): Promise<void> {\n if (\n id === undefined ||\n id === null ||\n !hasSharedSecretEncryptionKeyMaterial()\n ) {\n return;\n }\n try {\n await getDbExec().execute({\n sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,\n args: [encryptValue(value), id],\n });\n } catch {\n // Migration is opportunistic. Preserve the successful read if the update\n // is unavailable due to a read-only role, transient DB failure, or race.\n }\n}\n\ntype AppSecretsReadQuery = { sql: string; args: unknown[] };\n\n/**\n * True only when the database says the app_secrets table itself is missing.\n * Other query failures must propagate unchanged: attempting schema bootstrap\n * for connectivity, permission, or syntax errors both hides the real failure\n * and can introduce DDL onto a latency-sensitive read path.\n */\nfunction isMissingAppSecretsTableError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const code = String((error as Error & { code?: unknown }).code ?? \"\");\n const message = error.message.toLowerCase();\n const namesAppSecrets = /(?:app_secrets|\"app_secrets\")/.test(message);\n\n if (code === \"42P01\") return namesAppSecrets;\n return (\n namesAppSecrets &&\n (message.includes(\"no such table\") ||\n (message.includes(\"relation\") && message.includes(\"does not exist\")))\n );\n}\n\n/** Execute a read without schema probes on the normal path. */\nasync function executeAppSecretsRead(query: AppSecretsReadQuery) {\n const client = getDbExec();\n try {\n return await client.execute(query);\n } catch (error) {\n if (!isMissingAppSecretsTableError(error)) throw error;\n await ensureTable();\n return client.execute(query);\n }\n}\n\n/**\n * Read a secret's plaintext value. Returns null when not found. The caller\n * is responsible for never logging the returned value.\n */\nexport async function readAppSecret(\n ref: SecretRef,\n): Promise<ReadSecretResult | null> {\n const { key, scope, scopeId } = ref;\n const { rows } = await executeAppSecretsRead({\n sql: `SELECT encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n if (rows.length === 0) return null;\n try {\n const encrypted = rows[0].encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n rows[0].shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(rows[0].id, decrypted.value);\n }\n return {\n value: decrypted.value,\n last4: last4(decrypted.value),\n updatedAt: Number(rows[0].updated_at ?? 0),\n };\n } catch {\n // Decryption failure — key rotated, tampered row, etc. Don't throw up the\n // stack in a way that could leak the ciphertext; just report missing.\n return null;\n }\n}\n\n/** Read several keys from one scope in a single database round trip. */\nexport async function readAppSecrets(args: {\n keys: readonly string[];\n scope: SecretScope;\n scopeId: string;\n}): Promise<Map<string, ReadSecretResult>> {\n const keys = [...new Set(args.keys.filter(Boolean))];\n if (keys.length === 0) return new Map();\n\n const placeholders = keys.map(() => \"?\").join(\", \");\n const { rows } = await executeAppSecretsRead({\n sql: `SELECT key, encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key IN (${placeholders})`,\n args: [args.scope, args.scopeId, ...keys],\n });\n const results = new Map<string, ReadSecretResult>();\n for (const row of rows) {\n const key = String(row.key ?? \"\");\n if (!key) continue;\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n results.set(key, {\n value: decrypted.value,\n last4: last4(decrypted.value),\n updatedAt: Number(row.updated_at ?? 0),\n });\n } catch {\n // Match readAppSecret: corrupted or stale ciphertext behaves as missing.\n }\n }\n return results;\n}\n\n/**\n * Return just the metadata for a secret (no value). Used by the list route so\n * the UI can show the \"Set\" pill and last-4 without the decrypted value going\n * over the wire.\n */\nexport async function getAppSecretMeta(\n ref: SecretRef,\n): Promise<{ last4: string; updatedAt: number } | null> {\n const result = await readAppSecret(ref);\n if (!result) return null;\n return { last4: result.last4, updatedAt: result.updatedAt };\n}\n\nexport interface SecretMeta {\n key: string;\n scope: SecretScope;\n scopeId: string;\n last4: string;\n description: string | null;\n urlAllowlist: string[] | null;\n createdAt: number;\n updatedAt: number;\n}\n\n/**\n * Read a secret's metadata, including ad-hoc fields (description, allowlist),\n * without ever decrypting or returning the plaintext value. Used by the\n * ad-hoc list route and any UI that wants to render a key tile.\n */\nexport async function readAppSecretMeta(\n ref: SecretRef,\n): Promise<SecretMeta | null> {\n await ensureTable();\n const { key, scope, scopeId } = ref;\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n if (rows.length === 0) return null;\n const row = rows[0];\n let last4Value = \"\";\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n last4Value = last4(decrypted.value);\n } catch {\n last4Value = \"\";\n }\n return {\n key,\n scope,\n scopeId,\n last4: last4Value,\n description: (row.description as string | null) ?? null,\n urlAllowlist: parseAllowlist(row.url_allowlist as string | null),\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n };\n}\n\n/**\n * List all secrets for a given scope. Returns metadata only — values are\n * never decrypted or returned. Used by the ad-hoc list route to surface\n * user-created keys.\n */\nexport async function listAppSecretsForScope(\n scope: SecretScope,\n scopeId: string,\n): Promise<SecretMeta[]> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? ORDER BY updated_at DESC`,\n args: [scope, scopeId],\n });\n const results: SecretMeta[] = [];\n for (const row of rows) {\n let last4Value = \"\";\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n last4Value = last4(decrypted.value);\n } catch {\n last4Value = \"\";\n }\n results.push({\n key: row.key as string,\n scope,\n scopeId,\n last4: last4Value,\n description: (row.description as string | null) ?? null,\n urlAllowlist: parseAllowlist(row.url_allowlist as string | null),\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n });\n }\n return results;\n}\n\nfunction parseAllowlist(raw: string | null): string[] | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every((v) => typeof v === \"string\")) {\n return parsed;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function deleteAppSecret(ref: SecretRef): Promise<boolean> {\n await ensureTable();\n const { key, scope, scopeId } = ref;\n const client = getDbExec();\n const { rowsAffected } = await client.execute({\n sql: `DELETE FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ?`,\n args: [scope, scopeId, key],\n });\n return rowsAffected > 0;\n}\n"]}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Storage layer for the framework secrets registry.\n *\n * Values are encrypted at rest with AES-256-GCM. The workspace-shared\n * encryption key is derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the\n * existing `BETTER_AUTH_SECRET` env var (fallback so templates don't need a\n * second secret during development). Deployments that only have the legacy\n * app-scoped key continue to work until shared key material is configured;\n * once configured, new rows use the shared key and reads retain a legacy-key\n * fallback for existing rows.\n *\n * Secret values are NEVER logged and NEVER returned from any route handler.\n */\n\nimport { randomUUID } from \"node:crypto\";\n\nimport { getDbExec, isPostgres } from \"../db/client.js\";\nimport { ensureColumnExists, ensureTableExists } from \"../db/ddl-guard.js\";\nimport {\n encryptSecretValue as encryptLegacyValue,\n encryptSharedSecretValue as encryptValue,\n decryptSharedSecretValue as decryptValue,\n decryptSecretValue as decryptLegacyValue,\n hasSharedSecretEncryptionKeyMaterial,\n} from \"./crypto.js\";\nimport type { SecretScope } from \"./register.js\";\nimport { APP_SECRETS_CREATE_SQL } from \"./schema.js\";\n\n// ---------------------------------------------------------------------------\n// Table bootstrap\n// ---------------------------------------------------------------------------\n\nlet _initPromise: Promise<void> | undefined;\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n // Postgres version of the CREATE TABLE — the generic `INTEGER` maps to\n // BIGINT on Postgres, which we need for millisecond timestamps.\n const createSql = isPostgres()\n ? APP_SECRETS_CREATE_SQL.replace(/\\bINTEGER\\b/g, \"BIGINT\")\n : APP_SECRETS_CREATE_SQL;\n\n if (isPostgres()) {\n // Hot path: in production the table and both additive columns are\n // virtually always already present. Issuing `CREATE`/`ALTER` would\n // still take an ACCESS EXCLUSIVE lock — which, in a fresh background\n // worker process behind a concurrent connection on the shared Neon DB,\n // can block ~indefinitely. `ensureTableExists` / `ensureColumnExists`\n // check `information_schema` first (a plain read, no lock) and run DDL\n // ONLY for what is actually missing, wrapping any DDL that must run in a\n // transaction-scoped `lock_timeout` so a contended lock fails fast. They\n // also re-probe after a swallowed lock-timeout and THROW if the schema\n // is still missing, so a timed-out DDL never poisons this init memo.\n await ensureTableExists(\"app_secrets\", createSql);\n await ensureColumnExists(\n \"app_secrets\",\n \"description\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS description TEXT`,\n );\n await ensureColumnExists(\n \"app_secrets\",\n \"url_allowlist\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS url_allowlist TEXT`,\n );\n await ensureColumnExists(\n \"app_secrets\",\n \"shared_encrypted_value\",\n `ALTER TABLE app_secrets ADD COLUMN IF NOT EXISTS shared_encrypted_value TEXT`,\n );\n return;\n }\n\n // SQLite (local dev): no ACCESS EXCLUSIVE lock problem, keep the original\n // create-then-additive-alter behaviour. SQLite has no\n // `ADD COLUMN IF NOT EXISTS`, so the ALTERs stay wrapped in try/catch.\n await client.execute(createSql);\n\n // Additive migration: description column (for ad-hoc keys)\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN description TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n\n // Additive migration: url_allowlist column\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN url_allowlist TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n\n // Additive migration: workspace-shared ciphertext. Keep the legacy\n // encrypted_value column so older app versions remain readable.\n try {\n await client.execute(\n `ALTER TABLE app_secrets ADD COLUMN shared_encrypted_value TEXT`,\n );\n } catch {\n // Column already exists — expected\n }\n })().catch((err) => {\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\n// ---------------------------------------------------------------------------\n// Encryption — see ./crypto.ts. Keep encrypted_value on the legacy app-key\n// format for mixed-version deployments, and add shared_encrypted_value when a\n// stable workspace key is configured so sibling apps can read the same row.\n// ---------------------------------------------------------------------------\n\n/**\n * Return the last 4 characters of a secret, with any leading characters\n * masked. Used to show a preview without leaking the value.\n */\nexport function last4(value: string): string {\n if (!value) return \"\";\n if (value.length <= 4) return \"••••\";\n return \"••••\" + value.slice(-4);\n}\n\n// ---------------------------------------------------------------------------\n// CRUD\n// ---------------------------------------------------------------------------\n\nexport interface SecretRef {\n key: string;\n scope: SecretScope;\n scopeId: string;\n}\n\nexport interface WriteSecretArgs extends SecretRef {\n value: string;\n /** Optional human-readable description (used for ad-hoc keys). */\n description?: string;\n /** Optional JSON-stringified array of allowed URL origins. */\n urlAllowlist?: string;\n}\n\n/**\n * Write (insert or update) a secret. The value is encrypted before being\n * stored — the caller's plaintext is never persisted. Returns the new\n * record's id.\n */\nexport async function writeAppSecret(args: WriteSecretArgs): Promise<string> {\n await ensureTable();\n const { key, value, scope, scopeId, description, urlAllowlist } = args;\n if (!key || !value || !scope || !scopeId) {\n throw new Error(\n \"writeAppSecret: key, value, scope, and scopeId are all required\",\n );\n }\n const client = getDbExec();\n const now = Date.now();\n // Dual-write during rollout: old readers continue using encrypted_value,\n // while new readers prefer the nullable shared ciphertext. An app-only\n // deployment leaves the shared column null; on an update, a writer without\n // shared key material clears any existing shared ciphertext rather than\n // preserving it (see the upsert SQL below for why).\n const encrypted = encryptLegacyValue(value);\n const sharedEncrypted = hasSharedSecretEncryptionKeyMaterial()\n ? encryptValue(value)\n : null;\n const id = randomUUID();\n\n // Atomic upsert by (scope, scope_id, key). Previously this was a\n // SELECT-then-branch (UPDATE if found, else INSERT): under concurrent\n // writers for the same key both could see \"no row\" and both attempt\n // INSERT, and the loser threw a raw UNIQUE(scope, scope_id, key)\n // constraint violation (a user-facing 500) instead of updating. A single\n // `INSERT ... ON CONFLICT DO UPDATE` closes that window — it's one\n // statement, so there's no gap between \"check\" and \"act\". `id` is\n // deliberately left out of the `DO UPDATE SET` list so an existing row\n // keeps its original id (any stored references stay stable); only a\n // genuinely new row gets the freshly generated `id`. This syntax is\n // portable across SQLite (UPSERT since 3.24) and Postgres.\n //\n // shared_encrypted_value is overwritten with `excluded.shared_encrypted_value`\n // (NULL when this writer lacks shared key material) rather than preserved\n // via COALESCE. Preserving an existing shared ciphertext across a value\n // update would let a sibling app silently decrypt a STALE value after the\n // owner rotates it — a material-less writer has no way to produce the new\n // shared ciphertext, so it must clear the old one instead of leaving it\n // pointing at data that's no longer current. Siblings then get an honest\n // cache miss (falling back to the legacy column or reporting missing) until\n // the owning app's next read repopulates shared_encrypted_value via\n // `populateSharedAppSecret`, which only ever fills a NULL column. A\n // temporary miss is safer than serving rotated-away plaintext.\n const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (scope, scope_id, key) DO UPDATE SET\n encrypted_value = excluded.encrypted_value,\n shared_encrypted_value = excluded.shared_encrypted_value,\n description = excluded.description,\n url_allowlist = excluded.url_allowlist,\n updated_at = excluded.updated_at`;\n const upsertArgs = [\n id,\n scope,\n scopeId,\n key,\n encrypted,\n sharedEncrypted,\n description ?? null,\n urlAllowlist ?? null,\n now,\n now,\n ];\n\n if (isPostgres()) {\n const { rows } = await client.execute({\n sql: `${upsertSql} RETURNING id`,\n args: upsertArgs,\n });\n return String(rows[0]?.id ?? id);\n }\n\n // SQLite: RETURNING support varies across better-sqlite3/libsql builds, so\n // (matching the convention elsewhere in this codebase, e.g.\n // integrations/pending-tasks-store.ts) re-read the id afterward instead of\n // relying on it. The row is guaranteed to exist at this point, so this is\n // a plain lookup rather than a TOCTOU-prone gate on the write itself.\n await client.execute({ sql: upsertSql, args: upsertArgs });\n const { rows } = await client.execute({\n sql: `SELECT id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n return String(rows[0]?.id ?? id);\n}\n\nexport interface ReadSecretResult {\n value: string;\n last4: string;\n updatedAt: number;\n}\n\n/**\n * Read the shared-key format and retain compatibility with rows written before\n * app_secrets moved to its workspace-shared encryption boundary. The legacy\n * fallback is only useful when the current app owns the old row; sibling apps\n * will receive shared-key ciphertext after the next vault sync or update.\n */\ninterface DecryptedAppSecretValue {\n value: string;\n /** True when the app-scoped fallback decrypted encrypted_value. */\n usedLegacyKey: boolean;\n /** True when shared_encrypted_value was not the source of the plaintext. */\n needsSharedCiphertext: boolean;\n}\n\nfunction decryptAppSecretValue(\n encrypted: string,\n sharedEncrypted?: string | null,\n): DecryptedAppSecretValue {\n if (sharedEncrypted) {\n try {\n return {\n value: decryptValue(sharedEncrypted),\n usedLegacyKey: false,\n needsSharedCiphertext: false,\n };\n } catch {\n // Fall through to the legacy column. A partially migrated row may have\n // a stale shared ciphertext while the old app-key value is valid.\n }\n }\n try {\n return {\n value: decryptValue(encrypted),\n usedLegacyKey: false,\n needsSharedCiphertext: true,\n };\n } catch {\n return {\n value: decryptLegacyValue(encrypted),\n usedLegacyKey: true,\n needsSharedCiphertext: true,\n };\n }\n}\n\n/**\n * Populate the shared column without touching encrypted_value. This is\n * intentionally best-effort: a read must still succeed if a deployment's DB\n * role cannot update the row. The NULL predicate prevents a concurrent writer\n * from overwriting newly-populated shared ciphertext, and leaving updated_at\n * untouched preserves the row's user-visible ordering/metadata.\n */\nasync function populateSharedAppSecret(\n id: unknown,\n value: string,\n): Promise<void> {\n if (\n id === undefined ||\n id === null ||\n !hasSharedSecretEncryptionKeyMaterial()\n ) {\n return;\n }\n try {\n await getDbExec().execute({\n sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,\n args: [encryptValue(value), id],\n });\n } catch {\n // Migration is opportunistic. Preserve the successful read if the update\n // is unavailable due to a read-only role, transient DB failure, or race.\n }\n}\n\ntype AppSecretsReadQuery = { sql: string; args: unknown[] };\n\n/**\n * True only when the database says the app_secrets table itself is missing.\n * Other query failures must propagate unchanged: attempting schema bootstrap\n * for connectivity, permission, or syntax errors both hides the real failure\n * and can introduce DDL onto a latency-sensitive read path.\n */\nfunction isMissingAppSecretsTableError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const code = String((error as Error & { code?: unknown }).code ?? \"\");\n const message = error.message.toLowerCase();\n const namesAppSecrets = /(?:app_secrets|\"app_secrets\")/.test(message);\n\n if (code === \"42P01\") return namesAppSecrets;\n return (\n namesAppSecrets &&\n (message.includes(\"no such table\") ||\n (message.includes(\"relation\") && message.includes(\"does not exist\")))\n );\n}\n\n/** Execute a read without schema probes on the normal path. */\nasync function executeAppSecretsRead(query: AppSecretsReadQuery) {\n const client = getDbExec();\n try {\n return await client.execute(query);\n } catch (error) {\n if (!isMissingAppSecretsTableError(error)) throw error;\n await ensureTable();\n return client.execute(query);\n }\n}\n\n/**\n * Read a secret's plaintext value. Returns null when not found. The caller\n * is responsible for never logging the returned value.\n */\nexport async function readAppSecret(\n ref: SecretRef,\n): Promise<ReadSecretResult | null> {\n const { key, scope, scopeId } = ref;\n const { rows } = await executeAppSecretsRead({\n sql: `SELECT encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n if (rows.length === 0) return null;\n try {\n const encrypted = rows[0].encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n rows[0].shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(rows[0].id, decrypted.value);\n }\n return {\n value: decrypted.value,\n last4: last4(decrypted.value),\n updatedAt: Number(rows[0].updated_at ?? 0),\n };\n } catch {\n // Decryption failure — key rotated, tampered row, etc. Don't throw up the\n // stack in a way that could leak the ciphertext; just report missing.\n return null;\n }\n}\n\n/** Read several keys from one scope in a single database round trip. */\nexport async function readAppSecrets(args: {\n keys: readonly string[];\n scope: SecretScope;\n scopeId: string;\n}): Promise<Map<string, ReadSecretResult>> {\n const keys = [...new Set(args.keys.filter(Boolean))];\n if (keys.length === 0) return new Map();\n\n const placeholders = keys.map(() => \"?\").join(\", \");\n const { rows } = await executeAppSecretsRead({\n sql: `SELECT key, encrypted_value, shared_encrypted_value, updated_at, id FROM app_secrets WHERE scope = ? AND scope_id = ? AND key IN (${placeholders})`,\n args: [args.scope, args.scopeId, ...keys],\n });\n const results = new Map<string, ReadSecretResult>();\n for (const row of rows) {\n const key = String(row.key ?? \"\");\n if (!key) continue;\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n results.set(key, {\n value: decrypted.value,\n last4: last4(decrypted.value),\n updatedAt: Number(row.updated_at ?? 0),\n });\n } catch {\n // Match readAppSecret: corrupted or stale ciphertext behaves as missing.\n }\n }\n return results;\n}\n\n/**\n * Return just the metadata for a secret (no value). Used by the list route so\n * the UI can show the \"Set\" pill and last-4 without the decrypted value going\n * over the wire.\n */\nexport async function getAppSecretMeta(\n ref: SecretRef,\n): Promise<{ last4: string; updatedAt: number } | null> {\n const result = await readAppSecret(ref);\n if (!result) return null;\n return { last4: result.last4, updatedAt: result.updatedAt };\n}\n\nexport interface SecretMeta {\n key: string;\n scope: SecretScope;\n scopeId: string;\n last4: string;\n description: string | null;\n urlAllowlist: string[] | null;\n createdAt: number;\n updatedAt: number;\n}\n\n/**\n * Read a secret's metadata, including ad-hoc fields (description, allowlist),\n * without ever decrypting or returning the plaintext value. Used by the\n * ad-hoc list route and any UI that wants to render a key tile.\n */\nexport async function readAppSecretMeta(\n ref: SecretRef,\n): Promise<SecretMeta | null> {\n await ensureTable();\n const { key, scope, scopeId } = ref;\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ? LIMIT 1`,\n args: [scope, scopeId, key],\n });\n if (rows.length === 0) return null;\n const row = rows[0];\n let last4Value = \"\";\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n last4Value = last4(decrypted.value);\n } catch {\n last4Value = \"\";\n }\n return {\n key,\n scope,\n scopeId,\n last4: last4Value,\n description: (row.description as string | null) ?? null,\n urlAllowlist: parseAllowlist(row.url_allowlist as string | null),\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n };\n}\n\n/**\n * List all secrets for a given scope. Returns metadata only — values are\n * never decrypted or returned. Used by the ad-hoc list route to surface\n * user-created keys.\n */\nexport async function listAppSecretsForScope(\n scope: SecretScope,\n scopeId: string,\n): Promise<SecretMeta[]> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at FROM app_secrets WHERE scope = ? AND scope_id = ? ORDER BY updated_at DESC`,\n args: [scope, scopeId],\n });\n const results: SecretMeta[] = [];\n for (const row of rows) {\n let last4Value = \"\";\n try {\n const encrypted = row.encrypted_value as string;\n const decrypted = decryptAppSecretValue(\n encrypted,\n row.shared_encrypted_value as string | null,\n );\n if (decrypted.needsSharedCiphertext) {\n await populateSharedAppSecret(row.id, decrypted.value);\n }\n last4Value = last4(decrypted.value);\n } catch {\n last4Value = \"\";\n }\n results.push({\n key: row.key as string,\n scope,\n scopeId,\n last4: last4Value,\n description: (row.description as string | null) ?? null,\n urlAllowlist: parseAllowlist(row.url_allowlist as string | null),\n createdAt: Number(row.created_at ?? 0),\n updatedAt: Number(row.updated_at ?? 0),\n });\n }\n return results;\n}\n\nfunction parseAllowlist(raw: string | null): string[] | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw);\n if (Array.isArray(parsed) && parsed.every((v) => typeof v === \"string\")) {\n return parsed;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport async function deleteAppSecret(ref: SecretRef): Promise<boolean> {\n await ensureTable();\n const { key, scope, scopeId } = ref;\n const client = getDbExec();\n const { rowsAffected } = await client.execute({\n sql: `DELETE FROM app_secrets WHERE scope = ? AND scope_id = ? AND key = ?`,\n args: [scope, scopeId, key],\n });\n return rowsAffected > 0;\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import type { ResolvedKeyReference } from "../secrets/substitution.js";
1
2
  import { handleSharedThreadRequest, type SharedThreadRouteDependencies } from "./agent-chat/shared-thread.js";
2
3
  export { handleSharedThreadRequest };
3
4
  export type { SharedThreadRouteDependencies };
@@ -44,6 +45,25 @@ export declare function buildLeanRunPolicyPrompt(codeEditingSurfaceRestriction:
44
45
  * long after they'd completed personalization once.
45
46
  */
46
47
  export declare function hasCompletedFirstSessionPersonalization(owner: string): Promise<boolean>;
48
+ /**
49
+ * Pick the URL allowlist to enforce for a `${keys.NAME}` reference used by
50
+ * the agent fetch tool.
51
+ *
52
+ * SECURITY (audit 05 H2 alignment): the allowlist check must stay
53
+ * consistent with the scope a key actually resolved at (see the
54
+ * `getKeyAllowlist` docstring in secrets/substitution.ts). Since the fetch
55
+ * tool now resolves keys via `resolveKeyReferencesWithRequestScopes` — which
56
+ * cascades user → org → workspace scope — a plain user-scope-only allowlist
57
+ * lookup would silently miss an allowlist configured on the org/workspace
58
+ * row that actually supplied the value. When `resolvedKeys` reports which
59
+ * scope a key resolved at, look up the allowlist there; only fall back to
60
+ * the legacy user-scope lookup for a key the resolver didn't report (should
61
+ * not normally happen — every key the cascade resolves reports a ref).
62
+ */
63
+ export declare function resolveFetchToolKeyAllowlist(keyName: string, resolvedKeys: ResolvedKeyReference[] | undefined, owner: string, deps: {
64
+ getKeyAllowlist: (name: string, scope: "user", scopeId: string) => Promise<string[] | null>;
65
+ getResolvedKeyAllowlist: (ref: ResolvedKeyReference) => Promise<string[] | null>;
66
+ }): Promise<string[] | null>;
47
67
  export declare function createAgentChatPlugin(options?: AgentChatPluginOptions): NitroPluginDef;
48
68
  /**
49
69
  * Default agent chat plugin with no template-specific actions.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"AAyIA,OAAO,EACL,yBAAyB,EACzB,KAAK,6BAA6B,EACnC,MAAM,+BAA+B,CAAC;AAqCvC,OAAO,EAAE,yBAAyB,EAAE,CAAC;AACrC,YAAY,EAAE,6BAA6B,EAAE,CAAC;AAuB9C,OAAO,EACL,0BAA0B,EAK1B,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EAE1B,MAAM,oCAAoC,CAAC;AAW5C,OAAO,EACL,gCAAgC,EAOjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,yCAAyC,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,EACL,sBAAsB,EAGvB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAC3F,OAAO,EAEL,sCAAsC,EACvC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAYxE,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,gCAAgC,EAAE,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,YAAY,EAAE,sBAAsB,EAAE,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACtC,OAAO,EAAE,sCAAsC,EAAE,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,iCAAiC,EAAE,CAAC;AAC7C,OAAO,EAAE,yCAAyC,EAAE,CAAC;AAErD,wBAAgB,wBAAwB,CACtC,6BAA6B,EAAE,MAAM,EACrC,sBAAsB,EAAE,MAAM,GAC7B,MAAM,CAER;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uCAAuC,CAC3D,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAUlB;AAaD,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,cAAc,CA8nKhB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAAwC,CAAC;AAE9E,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EAGxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,CAAC"}
1
+ {"version":3,"file":"agent-chat-plugin.d.ts","sourceRoot":"","sources":["../../src/server/agent-chat-plugin.ts"],"names":[],"mappings":"AAwIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAEvE,OAAO,EACL,yBAAyB,EACzB,KAAK,6BAA6B,EACnC,MAAM,+BAA+B,CAAC;AAqCvC,OAAO,EAAE,yBAAyB,EAAE,CAAC;AACrC,YAAY,EAAE,6BAA6B,EAAE,CAAC;AAuB9C,OAAO,EACL,0BAA0B,EAK1B,eAAe,EACf,eAAe,EACf,wBAAwB,EACxB,yBAAyB,EAE1B,MAAM,oCAAoC,CAAC;AAW5C,OAAO,EACL,gCAAgC,EAOjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,yCAAyC,EAAE,MAAM,qCAAqC,CAAC;AAChG,OAAO,EACL,sBAAsB,EAGvB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAC3F,OAAO,EAEL,sCAAsC,EACvC,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAYxE,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,gCAAgC,EAAE,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,CAAC;AACrC,OAAO,EAAE,wBAAwB,EAAE,CAAC;AACpC,YAAY,EAAE,sBAAsB,EAAE,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACtC,OAAO,EAAE,sCAAsC,EAAE,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,CAAC;AAClC,OAAO,EAAE,iCAAiC,EAAE,CAAC;AAC7C,OAAO,EAAE,yCAAyC,EAAE,CAAC;AAErD,wBAAgB,wBAAwB,CACtC,6BAA6B,EAAE,MAAM,EACrC,sBAAsB,EAAE,MAAM,GAC7B,MAAM,CAER;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,uCAAuC,CAC3D,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,OAAO,CAAC,CAUlB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,oBAAoB,EAAE,GAAG,SAAS,EAChD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,eAAe,EAAE,CACf,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9B,uBAAuB,EAAE,CACvB,GAAG,EAAE,oBAAoB,KACtB,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;CAC/B,GACA,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAI1B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,cAAc,CA+oKhB;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,cAAwC,CAAC;AAE9E,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EAGxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,OAAO,EAAE,uBAAuB,EAAE,CAAC"}
@@ -122,6 +122,27 @@ const generateTitleRateLimit = new Map();
122
122
  /** Only sweep drained rate-limit entries once the map grows past this size,
123
123
  * so the common small-map case stays O(1). */
124
124
  const RATE_LIMIT_SWEEP_THRESHOLD = 1000;
125
+ /**
126
+ * Pick the URL allowlist to enforce for a `${keys.NAME}` reference used by
127
+ * the agent fetch tool.
128
+ *
129
+ * SECURITY (audit 05 H2 alignment): the allowlist check must stay
130
+ * consistent with the scope a key actually resolved at (see the
131
+ * `getKeyAllowlist` docstring in secrets/substitution.ts). Since the fetch
132
+ * tool now resolves keys via `resolveKeyReferencesWithRequestScopes` — which
133
+ * cascades user → org → workspace scope — a plain user-scope-only allowlist
134
+ * lookup would silently miss an allowlist configured on the org/workspace
135
+ * row that actually supplied the value. When `resolvedKeys` reports which
136
+ * scope a key resolved at, look up the allowlist there; only fall back to
137
+ * the legacy user-scope lookup for a key the resolver didn't report (should
138
+ * not normally happen — every key the cascade resolves reports a ref).
139
+ */
140
+ export async function resolveFetchToolKeyAllowlist(keyName, resolvedKeys, owner, deps) {
141
+ const ref = resolvedKeys?.find((candidate) => candidate.name === keyName);
142
+ if (ref)
143
+ return deps.getResolvedKeyAllowlist(ref);
144
+ return deps.getKeyAllowlist(keyName, "user", owner);
145
+ }
125
146
  export function createAgentChatPlugin(options) {
126
147
  return (nitroApp) => {
127
148
  markDefaultPluginProvided(nitroApp, "agent-chat");
@@ -533,12 +554,25 @@ export function createAgentChatPlugin(options) {
533
554
  let fetchTool = {};
534
555
  try {
535
556
  const { createFetchToolEntry } = await import("../extensions/fetch-tool.js");
536
- const { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist } = await import("../secrets/substitution.js");
557
+ // Resolve `${keys.NAME}` through the same request-scope cascade
558
+ // already used by extension fetches (extensions/routes.ts) and
559
+ // automation connector headers (automation/index.ts): user scope
560
+ // first (personal overrides win), then the active org scope (the
561
+ // Dispatch vault syncs workspace secrets here), then workspace
562
+ // scope. Org/workspace vault rows are write-gated (org-admin +
563
+ // Dispatch vault UI), so this is safe to read by default — unlike
564
+ // the opt-in-only user→workspace fallback in resolveKeyReferences
565
+ // (see audit 05 H2 in secrets/substitution.ts), which stays off.
566
+ // Previously this tool only looked at scope "user", so a
567
+ // ${keys.NAME} reference to a key synced into the org/workspace
568
+ // vault could never resolve here even though the same key already
569
+ // worked for extension fetches and automations.
570
+ const { resolveKeyReferencesWithRequestScopes, validateUrlAllowlist, getKeyAllowlist, getResolvedKeyAllowlist, } = await import("../secrets/substitution.js");
537
571
  fetchTool = createFetchToolEntry({
538
- resolveKeys: async (text) => resolveKeyReferences(text, "user", requireCurrentRunOwner("resolve key references")),
539
- validateUrl: async (url, usedKeys) => {
572
+ resolveKeys: async (text) => resolveKeyReferencesWithRequestScopes(text, requireCurrentRunOwner("resolve key references")),
573
+ validateUrl: async (url, usedKeys, resolvedKeys) => {
540
574
  for (const keyName of usedKeys) {
541
- const allowlist = await getKeyAllowlist(keyName, "user", requireCurrentRunOwner("validate URL allowlist"));
575
+ const allowlist = await resolveFetchToolKeyAllowlist(keyName, resolvedKeys, requireCurrentRunOwner("validate URL allowlist"), { getKeyAllowlist, getResolvedKeyAllowlist });
542
576
  if (allowlist && !validateUrlAllowlist(url, allowlist)) {
543
577
  return false;
544
578
  }