@agent-native/core 0.100.0 → 0.100.2

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 (68) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AssistantChat.tsx +4 -0
  5. package/corpus/core/src/client/composer/PromptComposer.tsx +4 -0
  6. package/corpus/core/src/client/composer/TiptapComposer.tsx +30 -3
  7. package/corpus/core/src/integrations/webhook-handler.ts +4 -2
  8. package/corpus/core/src/secrets/crypto.ts +98 -47
  9. package/corpus/core/src/secrets/index.ts +2 -1
  10. package/corpus/core/src/secrets/schema.ts +3 -0
  11. package/corpus/core/src/secrets/storage.ts +164 -31
  12. package/corpus/templates/assets/app/components/layout/Layout.tsx +4 -0
  13. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
  14. package/corpus/templates/assets/changelog/2026-07-13-assets-keeps-the-right-chat-sidebar-closed-when-the-full-pag.md +6 -0
  15. package/corpus/templates/calendar/AGENTS.md +9 -0
  16. package/corpus/templates/calendar/actions/list-events.ts +659 -44
  17. package/corpus/templates/calendar/changelog/2026-07-13-calendar-inventory-reads-report-source-coverage.md +6 -0
  18. package/corpus/templates/calendar/server/lib/calendar-connector-catalog.ts +8 -0
  19. package/corpus/templates/calendar/server/lib/google-calendar.ts +173 -51
  20. package/corpus/templates/calendar/server/lib/ical-fetcher.ts +13 -2
  21. package/corpus/templates/calendar/server/plugins/agent-chat.ts +5 -3
  22. package/corpus/templates/calendar/shared/api.ts +2 -0
  23. package/corpus/templates/mail/AGENTS.md +12 -0
  24. package/corpus/templates/mail/actions/list-emails.ts +486 -4
  25. package/corpus/templates/mail/changelog/2026-07-13-coverage-aware-connected-inbox-inventory.md +6 -0
  26. package/corpus/templates/mail/server/db/schema.ts +17 -0
  27. package/corpus/templates/mail/server/lib/google-auth.ts +33 -5
  28. package/corpus/templates/mail/server/lib/inventory-cursor.ts +406 -0
  29. package/corpus/templates/mail/server/lib/list-inbox-emails.ts +21 -2
  30. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -0
  31. package/corpus/templates/mail/server/plugins/agent-chat.ts +4 -9
  32. package/corpus/templates/mail/server/plugins/db.ts +20 -0
  33. package/dist/client/AssistantChat.d.ts +2 -0
  34. package/dist/client/AssistantChat.d.ts.map +1 -1
  35. package/dist/client/AssistantChat.js +2 -2
  36. package/dist/client/AssistantChat.js.map +1 -1
  37. package/dist/client/composer/PromptComposer.d.ts +2 -0
  38. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  39. package/dist/client/composer/PromptComposer.js +2 -2
  40. package/dist/client/composer/PromptComposer.js.map +1 -1
  41. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  42. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  43. package/dist/client/composer/TiptapComposer.js +20 -5
  44. package/dist/client/composer/TiptapComposer.js.map +1 -1
  45. package/dist/collab/struct-routes.d.ts +1 -1
  46. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  47. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  48. package/dist/integrations/webhook-handler.js +3 -2
  49. package/dist/integrations/webhook-handler.js.map +1 -1
  50. package/dist/notifications/routes.d.ts +3 -3
  51. package/dist/resources/handlers.d.ts +2 -2
  52. package/dist/secrets/crypto.d.ts +35 -13
  53. package/dist/secrets/crypto.d.ts.map +1 -1
  54. package/dist/secrets/crypto.js +78 -41
  55. package/dist/secrets/crypto.js.map +1 -1
  56. package/dist/secrets/index.d.ts.map +1 -1
  57. package/dist/secrets/index.js +2 -1
  58. package/dist/secrets/index.js.map +1 -1
  59. package/dist/secrets/schema.d.ts +20 -1
  60. package/dist/secrets/schema.d.ts.map +1 -1
  61. package/dist/secrets/schema.js +3 -0
  62. package/dist/secrets/schema.js.map +1 -1
  63. package/dist/secrets/storage.d.ts +7 -7
  64. package/dist/secrets/storage.d.ts.map +1 -1
  65. package/dist/secrets/storage.js +121 -30
  66. package/dist/secrets/storage.js.map +1 -1
  67. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  68. package/package.json +1 -1
@@ -1,29 +1,51 @@
1
1
  /**
2
2
  * Shared AES-256-GCM encryption for secret values at rest.
3
3
  *
4
- * Used by both the framework secrets vault (`app_secrets`) and per-user/per-org
5
- * credentials (`resolveCredential` / `saveCredential`, stored in `settings`) so
6
- * there is a single crypto implementation and a single key story.
4
+ * Used by the framework secrets vault, per-user/per-org credentials
5
+ * (`resolveCredential` / `saveCredential`, stored in `settings`), and other
6
+ * column-level encrypted values. The `app_secrets` storage layer uses the
7
+ * shared-key variant below because workspace-scoped vault rows are readable by
8
+ * sibling apps in the same workspace. When a deployment has not been given
9
+ * shared key material yet, that variant falls back to the app-scoped key so
10
+ * existing single-app deployments can keep reading and writing secrets while
11
+ * they migrate to the shared key.
7
12
  *
8
- * The encryption key is derived from `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when
9
- * set, then `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. The app-scoped
10
- * key lets a local multi-app workspace read one app's encrypted production data
11
- * without replacing the shared local auth secret. In production we refuse to
12
- * start without configured key material a CWD-derived fallback would be
13
- * effectively static (e.g. `/var/task` on Lambda), so anyone with read access
14
- * to the DB could decrypt every secret.
13
+ * The default encryption key is derived from
14
+ * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,
15
+ * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app
16
+ * workspace read one app's encrypted production data without replacing the
17
+ * shared local auth secret. In production we refuse to start without
18
+ * configured key material a CWD-derived fallback would be effectively
19
+ * static (e.g. `/var/task` on Lambda), so anyone with read access to the DB
20
+ * could decrypt every secret.
15
21
  *
16
22
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
17
23
  * lets readers distinguish ciphertext from legacy plaintext during migration.
18
24
  */
19
25
  /**
20
- * Derive a 32-byte AES key from the configured secret material via SHA-256.
21
- * Re-derived per call (cheap, stateless, and makes rotation easy).
26
+ * Derive the key used by generic encrypted values such as credentials and
27
+ * OAuth tokens. App-specific key material is allowed for these app-local
28
+ * values.
22
29
  */
23
30
  export declare function getSecretEncryptionKey(): Buffer;
24
- /** Encrypt a plain-text value. Returns `v1:<iv-hex>:<ct-hex>:<tag-hex>`. */
31
+ /**
32
+ * Derive the preferred workspace-shared key used by `app_secrets` rows.
33
+ * Unlike generic column-level encryption, workspace vault data should decrypt
34
+ * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take
35
+ * precedence. The app-scoped key remains a compatibility fallback for
36
+ * deployments that have not configured shared material yet; once the shared
37
+ * key is configured, writes use it and reads still fall back to old rows.
38
+ */
39
+ export declare function getSharedSecretEncryptionKey(): Buffer;
40
+ /** Whether this deployment has stable workspace-shared key material. */
41
+ export declare function hasSharedSecretEncryptionKeyMaterial(): boolean;
42
+ /** Encrypt a plain-text value with the generic app-local key. */
25
43
  export declare function encryptSecretValue(plaintext: string): string;
26
44
  /** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */
27
45
  export declare function decryptSecretValue(encrypted: string): string;
46
+ /** Encrypt a workspace-shared `app_secrets` value. */
47
+ export declare function encryptSharedSecretValue(plaintext: string): string;
48
+ /** Decrypt a workspace-shared `app_secrets` value. */
49
+ export declare function decryptSharedSecretValue(encrypted: string): string;
28
50
  export declare function isEncryptedSecretValue(value: unknown): value is string;
29
51
  //# sourceMappingURL=crypto.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAoDH;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAwC/C;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ5D;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAqB5D;AAUD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtE"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA+EH;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAkB/C;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,IAAI,MAAM,CAQrD;AAED,wEAAwE;AACxE,wBAAgB,oCAAoC,IAAI,OAAO,CAE9D;AAiCD,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,sDAAsD;AACtD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,sDAAsD;AACtD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAElE;AAUD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAEtE"}
@@ -1,17 +1,23 @@
1
1
  /**
2
2
  * Shared AES-256-GCM encryption for secret values at rest.
3
3
  *
4
- * Used by both the framework secrets vault (`app_secrets`) and per-user/per-org
5
- * credentials (`resolveCredential` / `saveCredential`, stored in `settings`) so
6
- * there is a single crypto implementation and a single key story.
4
+ * Used by the framework secrets vault, per-user/per-org credentials
5
+ * (`resolveCredential` / `saveCredential`, stored in `settings`), and other
6
+ * column-level encrypted values. The `app_secrets` storage layer uses the
7
+ * shared-key variant below because workspace-scoped vault rows are readable by
8
+ * sibling apps in the same workspace. When a deployment has not been given
9
+ * shared key material yet, that variant falls back to the app-scoped key so
10
+ * existing single-app deployments can keep reading and writing secrets while
11
+ * they migrate to the shared key.
7
12
  *
8
- * The encryption key is derived from `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when
9
- * set, then `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. The app-scoped
10
- * key lets a local multi-app workspace read one app's encrypted production data
11
- * without replacing the shared local auth secret. In production we refuse to
12
- * start without configured key material a CWD-derived fallback would be
13
- * effectively static (e.g. `/var/task` on Lambda), so anyone with read access
14
- * to the DB could decrypt every secret.
13
+ * The default encryption key is derived from
14
+ * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,
15
+ * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app
16
+ * workspace read one app's encrypted production data without replacing the
17
+ * shared local auth secret. In production we refuse to start without
18
+ * configured key material a CWD-derived fallback would be effectively
19
+ * static (e.g. `/var/task` on Lambda), so anyone with read access to the DB
20
+ * could decrypt every secret.
15
21
  *
16
22
  * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
17
23
  * lets readers distinguish ciphertext from legacy plaintext during migration.
@@ -54,55 +60,71 @@ function appScopedEncryptionKey() {
54
60
  ? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.
55
61
  : undefined;
56
62
  }
57
- /**
58
- * Derive a 32-byte AES key from the configured secret material via SHA-256.
59
- * Re-derived per call (cheap, stateless, and makes rotation easy).
60
- */
61
- export function getSecretEncryptionKey() {
63
+ function sharedEncryptionKeyMaterial() {
64
+ if (typeof process === "undefined")
65
+ return undefined;
66
+ return process.env.SECRETS_ENCRYPTION_KEY || process.env.BETTER_AUTH_SECRET;
67
+ }
68
+ function deriveSecretEncryptionKey(explicit, errorMessage, warningMessage) {
62
69
  const { createHash } = requireNodeCrypto();
63
- const explicit = appScopedEncryptionKey() ||
64
- (typeof process === "undefined"
65
- ? undefined
66
- : process.env.SECRETS_ENCRYPTION_KEY) ||
67
- (typeof process === "undefined"
68
- ? undefined
69
- : process.env.BETTER_AUTH_SECRET);
70
70
  if (!explicit) {
71
71
  if (processNodeEnv() === "production") {
72
- const appName = typeof process === "undefined"
73
- ? undefined
74
- : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.
75
- .toUpperCase()
76
- .replace(/[^A-Z0-9]+/g, "_")
77
- .replace(/^_+|_+$/g, "");
78
- throw new Error("[agent-native/secrets] Refusing to start in production without an encryption key. " +
79
- `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
80
- "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
81
- "which means anyone with read access to the secrets table could decrypt every user's secrets.");
72
+ throw new Error(errorMessage);
82
73
  }
83
74
  if (!_warnedFallback) {
84
75
  _warnedFallback = true;
85
76
  // eslint-disable-next-line no-console
86
- console.warn("[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. " +
87
- "Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. " +
88
- "Production deploys without one of these env vars now hard-fail.");
77
+ console.warn(warningMessage);
89
78
  }
90
79
  }
91
80
  const material = explicit || `agent-native-secrets:${processCwd()}`;
92
81
  return createHash("sha256").update(material).digest();
93
82
  }
94
- /** Encrypt a plain-text value. Returns `v1:<iv-hex>:<ct-hex>:<tag-hex>`. */
95
- export function encryptSecretValue(plaintext) {
83
+ /**
84
+ * Derive the key used by generic encrypted values such as credentials and
85
+ * OAuth tokens. App-specific key material is allowed for these app-local
86
+ * values.
87
+ */
88
+ export function getSecretEncryptionKey() {
89
+ const appName = typeof process === "undefined"
90
+ ? undefined
91
+ : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.
92
+ .toUpperCase()
93
+ .replace(/[^A-Z0-9]+/g, "_")
94
+ .replace(/^_+|_+$/g, "");
95
+ return deriveSecretEncryptionKey(appScopedEncryptionKey() || sharedEncryptionKeyMaterial(), "[agent-native/secrets] Refusing to start in production without an encryption key. " +
96
+ `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
97
+ "The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
98
+ "which means anyone with read access to the DB could decrypt every secret.", "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. " +
99
+ "Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. " +
100
+ "Production deploys without one of these env vars now hard-fail.");
101
+ }
102
+ /**
103
+ * Derive the preferred workspace-shared key used by `app_secrets` rows.
104
+ * Unlike generic column-level encryption, workspace vault data should decrypt
105
+ * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take
106
+ * precedence. The app-scoped key remains a compatibility fallback for
107
+ * deployments that have not configured shared material yet; once the shared
108
+ * key is configured, writes use it and reads still fall back to old rows.
109
+ */
110
+ export function getSharedSecretEncryptionKey() {
111
+ return deriveSecretEncryptionKey(sharedEncryptionKeyMaterial() || appScopedEncryptionKey(), "[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
112
+ "Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment.", "[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using app-scoped or machine-local fallback for workspace secrets. " +
113
+ "Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows.");
114
+ }
115
+ /** Whether this deployment has stable workspace-shared key material. */
116
+ export function hasSharedSecretEncryptionKeyMaterial() {
117
+ return Boolean(sharedEncryptionKeyMaterial());
118
+ }
119
+ function encryptWithKey(plaintext, key) {
96
120
  const { createCipheriv, randomBytes } = requireNodeCrypto();
97
- const key = getSecretEncryptionKey();
98
121
  const iv = randomBytes(12);
99
122
  const cipher = createCipheriv("aes-256-gcm", key, iv);
100
123
  const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
101
124
  const tag = cipher.getAuthTag();
102
125
  return `v1:${iv.toString("hex")}:${ct.toString("hex")}:${tag.toString("hex")}`;
103
126
  }
104
- /** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */
105
- export function decryptSecretValue(encrypted) {
127
+ function decryptWithKey(encrypted, key) {
106
128
  const { createDecipheriv } = requireNodeCrypto();
107
129
  if (!encrypted.startsWith("v1:")) {
108
130
  throw new Error("Unrecognised secret encoding");
@@ -111,7 +133,6 @@ export function decryptSecretValue(encrypted) {
111
133
  if (!ivHex || !ctHex || !tagHex) {
112
134
  throw new Error("Corrupt secret payload");
113
135
  }
114
- const key = getSecretEncryptionKey();
115
136
  const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(ivHex, "hex"));
116
137
  decipher.setAuthTag(Buffer.from(tagHex, "hex"));
117
138
  const pt = Buffer.concat([
@@ -120,6 +141,22 @@ export function decryptSecretValue(encrypted) {
120
141
  ]);
121
142
  return pt.toString("utf8");
122
143
  }
144
+ /** Encrypt a plain-text value with the generic app-local key. */
145
+ export function encryptSecretValue(plaintext) {
146
+ return encryptWithKey(plaintext, getSecretEncryptionKey());
147
+ }
148
+ /** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */
149
+ export function decryptSecretValue(encrypted) {
150
+ return decryptWithKey(encrypted, getSecretEncryptionKey());
151
+ }
152
+ /** Encrypt a workspace-shared `app_secrets` value. */
153
+ export function encryptSharedSecretValue(plaintext) {
154
+ return encryptWithKey(plaintext, getSharedSecretEncryptionKey());
155
+ }
156
+ /** Decrypt a workspace-shared `app_secrets` value. */
157
+ export function decryptSharedSecretValue(encrypted) {
158
+ return decryptWithKey(encrypted, getSharedSecretEncryptionKey());
159
+ }
123
160
  /**
124
161
  * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by
125
162
  * three hex segments. Intentionally strict so a legacy plaintext credential
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared AES-256-GCM encryption for secret values at rest.\n *\n * Used by both the framework secrets vault (`app_secrets`) and per-user/per-org\n * credentials (`resolveCredential` / `saveCredential`, stored in `settings`) so\n * there is a single crypto implementation and a single key story.\n *\n * The encryption key is derived from `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when\n * set, then `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. The app-scoped\n * key lets a local multi-app workspace read one app's encrypted production data\n * without replacing the shared local auth secret. In production we refuse to\n * start without configured key material — a CWD-derived fallback would be\n * effectively static (e.g. `/var/task` on Lambda), so anyone with read access\n * to the DB could decrypt every secret.\n *\n * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix\n * lets readers distinguish ciphertext from legacy plaintext during migration.\n */\n\ntype NodeCryptoModule = typeof import(\"node:crypto\");\n\nfunction getNodeCrypto(): NodeCryptoModule | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:crypto\") as\n | NodeCryptoModule\n | undefined;\n}\n\nconst nodeCrypto = getNodeCrypto();\n\nlet _warnedFallback = false;\n\nfunction requireNodeCrypto(): NonNullable<typeof nodeCrypto> {\n if (!nodeCrypto) {\n throw new Error(\n \"[agent-native/secrets] Secret encryption is only available in server/runtime code.\",\n );\n }\n return nodeCrypto;\n}\n\nfunction processNodeEnv(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.NODE_ENV;\n}\n\nfunction processCwd(): string {\n if (typeof process === \"undefined\") return \".\";\n return process.cwd();\n}\n\nfunction appScopedEncryptionKey(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n const appName = process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return appName\n ? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.\n : undefined;\n}\n\n/**\n * Derive a 32-byte AES key from the configured secret material via SHA-256.\n * Re-derived per call (cheap, stateless, and makes rotation easy).\n */\nexport function getSecretEncryptionKey(): Buffer {\n const { createHash } = requireNodeCrypto();\n const explicit =\n appScopedEncryptionKey() ||\n (typeof process === \"undefined\"\n ? undefined\n : process.env.SECRETS_ENCRYPTION_KEY) ||\n (typeof process === \"undefined\"\n ? undefined\n : process.env.BETTER_AUTH_SECRET);\n\n if (!explicit) {\n if (processNodeEnv() === \"production\") {\n const appName =\n typeof process === \"undefined\"\n ? undefined\n : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n throw new Error(\n \"[agent-native/secrets] Refusing to start in production without an encryption key. \" +\n `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : \"\"}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +\n \"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), \" +\n \"which means anyone with read access to the secrets table could decrypt every user's secrets.\",\n );\n }\n if (!_warnedFallback) {\n _warnedFallback = true;\n // eslint-disable-next-line no-console\n console.warn(\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. \" +\n \"Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. \" +\n \"Production deploys without one of these env vars now hard-fail.\",\n );\n }\n }\n\n const material = explicit || `agent-native-secrets:${processCwd()}`;\n return createHash(\"sha256\").update(material).digest();\n}\n\n/** Encrypt a plain-text value. Returns `v1:<iv-hex>:<ct-hex>:<tag-hex>`. */\nexport function encryptSecretValue(plaintext: string): string {\n const { createCipheriv, randomBytes } = requireNodeCrypto();\n const key = getSecretEncryptionKey();\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const ct = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return `v1:${iv.toString(\"hex\")}:${ct.toString(\"hex\")}:${tag.toString(\"hex\")}`;\n}\n\n/** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */\nexport function decryptSecretValue(encrypted: string): string {\n const { createDecipheriv } = requireNodeCrypto();\n if (!encrypted.startsWith(\"v1:\")) {\n throw new Error(\"Unrecognised secret encoding\");\n }\n const [, ivHex, ctHex, tagHex] = encrypted.split(\":\");\n if (!ivHex || !ctHex || !tagHex) {\n throw new Error(\"Corrupt secret payload\");\n }\n const key = getSecretEncryptionKey();\n const decipher = createDecipheriv(\n \"aes-256-gcm\",\n key,\n Buffer.from(ivHex, \"hex\"),\n );\n decipher.setAuthTag(Buffer.from(tagHex, \"hex\"));\n const pt = Buffer.concat([\n decipher.update(Buffer.from(ctHex, \"hex\")),\n decipher.final(),\n ]);\n return pt.toString(\"utf8\");\n}\n\n/**\n * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by\n * three hex segments. Intentionally strict so a legacy plaintext credential\n * that merely happens to start with `v1:` is treated as plaintext (and read via\n * the legacy fallback) rather than mis-decrypted.\n */\nconst ENCRYPTED_VALUE_RE = /^v1:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/;\n\nexport function isEncryptedSecretValue(value: unknown): value is string {\n return typeof value === \"string\" && ENCRYPTED_VALUE_RE.test(value);\n}\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/secrets/crypto.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared AES-256-GCM encryption for secret values at rest.\n *\n * Used by the framework secrets vault, per-user/per-org credentials\n * (`resolveCredential` / `saveCredential`, stored in `settings`), and other\n * column-level encrypted values. The `app_secrets` storage layer uses the\n * shared-key variant below because workspace-scoped vault rows are readable by\n * sibling apps in the same workspace. When a deployment has not been given\n * shared key material yet, that variant falls back to the app-scoped key so\n * existing single-app deployments can keep reading and writing secrets while\n * they migrate to the shared key.\n *\n * The default encryption key is derived from\n * `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then `SECRETS_ENCRYPTION_KEY`,\n * then `BETTER_AUTH_SECRET`. The app-scoped key lets a local multi-app\n * workspace read one app's encrypted production data without replacing the\n * shared local auth secret. In production we refuse to start without\n * configured key material — a CWD-derived fallback would be effectively\n * static (e.g. `/var/task` on Lambda), so anyone with read access to the DB\n * could decrypt every secret.\n *\n * Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix\n * lets readers distinguish ciphertext from legacy plaintext during migration.\n */\n\ntype NodeCryptoModule = typeof import(\"node:crypto\");\n\nfunction getNodeCrypto(): NodeCryptoModule | undefined {\n if (\n typeof window !== \"undefined\" ||\n typeof process === \"undefined\" ||\n !process.versions?.node ||\n typeof process.getBuiltinModule !== \"function\"\n ) {\n return undefined;\n }\n return process.getBuiltinModule(\"node:crypto\") as\n | NodeCryptoModule\n | undefined;\n}\n\nconst nodeCrypto = getNodeCrypto();\n\nlet _warnedFallback = false;\n\nfunction requireNodeCrypto(): NonNullable<typeof nodeCrypto> {\n if (!nodeCrypto) {\n throw new Error(\n \"[agent-native/secrets] Secret encryption is only available in server/runtime code.\",\n );\n }\n return nodeCrypto;\n}\n\nfunction processNodeEnv(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.NODE_ENV;\n}\n\nfunction processCwd(): string {\n if (typeof process === \"undefined\") return \".\";\n return process.cwd();\n}\n\nfunction appScopedEncryptionKey(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n const appName = process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return appName\n ? process.env[`${appName}_SECRETS_ENCRYPTION_KEY`] // guard:allow-env-credential — deploy-level app encryption material, never a user credential.\n : undefined;\n}\n\nfunction sharedEncryptionKeyMaterial(): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env.SECRETS_ENCRYPTION_KEY || process.env.BETTER_AUTH_SECRET;\n}\n\nfunction deriveSecretEncryptionKey(\n explicit: string | undefined,\n errorMessage: string,\n warningMessage: string,\n): Buffer {\n const { createHash } = requireNodeCrypto();\n\n if (!explicit) {\n if (processNodeEnv() === \"production\") {\n throw new Error(errorMessage);\n }\n if (!_warnedFallback) {\n _warnedFallback = true;\n // eslint-disable-next-line no-console\n console.warn(warningMessage);\n }\n }\n\n const material = explicit || `agent-native-secrets:${processCwd()}`;\n return createHash(\"sha256\").update(material).digest();\n}\n\n/**\n * Derive the key used by generic encrypted values such as credentials and\n * OAuth tokens. App-specific key material is allowed for these app-local\n * values.\n */\nexport function getSecretEncryptionKey(): Buffer {\n const appName =\n typeof process === \"undefined\"\n ? undefined\n : process.env.APP_NAME?.trim() // guard:allow-env-credential — deploy-level app configuration selects the scoped encryption key.\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\");\n return deriveSecretEncryptionKey(\n appScopedEncryptionKey() || sharedEncryptionKeyMaterial(),\n \"[agent-native/secrets] Refusing to start in production without an encryption key. \" +\n `Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : \"\"}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +\n \"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), \" +\n \"which means anyone with read access to the DB could decrypt every secret.\",\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using a machine-local fallback. \" +\n \"Set an app-scoped *_SECRETS_ENCRYPTION_KEY, SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET for production. \" +\n \"Production deploys without one of these env vars now hard-fail.\",\n );\n}\n\n/**\n * Derive the preferred workspace-shared key used by `app_secrets` rows.\n * Unlike generic column-level encryption, workspace vault data should decrypt\n * in sibling apps, so `SECRETS_ENCRYPTION_KEY` / `BETTER_AUTH_SECRET` take\n * precedence. The app-scoped key remains a compatibility fallback for\n * deployments that have not configured shared material yet; once the shared\n * key is configured, writes use it and reads still fall back to old rows.\n */\nexport function getSharedSecretEncryptionKey(): Buffer {\n return deriveSecretEncryptionKey(\n sharedEncryptionKeyMaterial() || appScopedEncryptionKey(),\n \"[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. \" +\n \"Set SECRETS_ENCRYPTION_KEY, BETTER_AUTH_SECRET, or the app-scoped *_SECRETS_ENCRYPTION_KEY in the deploy environment.\",\n \"[agent-native/secrets] SECRETS_ENCRYPTION_KEY not set — using app-scoped or machine-local fallback for workspace secrets. \" +\n \"Set SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET in every workspace app so sibling apps share vault rows.\",\n );\n}\n\n/** Whether this deployment has stable workspace-shared key material. */\nexport function hasSharedSecretEncryptionKeyMaterial(): boolean {\n return Boolean(sharedEncryptionKeyMaterial());\n}\n\nfunction encryptWithKey(plaintext: string, key: Buffer): string {\n const { createCipheriv, randomBytes } = requireNodeCrypto();\n const iv = randomBytes(12);\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n const ct = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n return `v1:${iv.toString(\"hex\")}:${ct.toString(\"hex\")}:${tag.toString(\"hex\")}`;\n}\n\nfunction decryptWithKey(encrypted: string, key: Buffer): string {\n const { createDecipheriv } = requireNodeCrypto();\n if (!encrypted.startsWith(\"v1:\")) {\n throw new Error(\"Unrecognised secret encoding\");\n }\n const [, ivHex, ctHex, tagHex] = encrypted.split(\":\");\n if (!ivHex || !ctHex || !tagHex) {\n throw new Error(\"Corrupt secret payload\");\n }\n const decipher = createDecipheriv(\n \"aes-256-gcm\",\n key,\n Buffer.from(ivHex, \"hex\"),\n );\n decipher.setAuthTag(Buffer.from(tagHex, \"hex\"));\n const pt = Buffer.concat([\n decipher.update(Buffer.from(ctHex, \"hex\")),\n decipher.final(),\n ]);\n return pt.toString(\"utf8\");\n}\n\n/** Encrypt a plain-text value with the generic app-local key. */\nexport function encryptSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSecretEncryptionKey());\n}\n\n/** Decrypt a value produced by `encryptSecretValue`. Throws on tampering. */\nexport function decryptSecretValue(encrypted: string): string {\n return decryptWithKey(encrypted, getSecretEncryptionKey());\n}\n\n/** Encrypt a workspace-shared `app_secrets` value. */\nexport function encryptSharedSecretValue(plaintext: string): string {\n return encryptWithKey(plaintext, getSharedSecretEncryptionKey());\n}\n\n/** Decrypt a workspace-shared `app_secrets` value. */\nexport function decryptSharedSecretValue(encrypted: string): string {\n return decryptWithKey(encrypted, getSharedSecretEncryptionKey());\n}\n\n/**\n * Strict check for a value produced by `encryptSecretValue`: `v1:` followed by\n * three hex segments. Intentionally strict so a legacy plaintext credential\n * that merely happens to start with `v1:` is treated as plaintext (and read via\n * the legacy fallback) rather than mis-decrypted.\n */\nconst ENCRYPTED_VALUE_RE = /^v1:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+$/;\n\nexport function isEncryptedSecretValue(value: unknown): value is string {\n return typeof value === \"string\" && ENCRYPTED_VALUE_RE.test(value);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,EACL,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAKjE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iCAAiC,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,EACL,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMjE,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iCAAiC,EAAE,MAAM,iBAAiB,CAAC"}
@@ -12,7 +12,8 @@ export { writeAppSecret, readAppSecret, readAppSecretMeta, deleteAppSecret, getA
12
12
  export { APP_SECRETS_CREATE_SQL, appSecrets } from "./schema.js";
13
13
  // AES-256-GCM helpers for column-level encryption of per-row secret values
14
14
  // (e.g. a per-record share password) that don't fit the keyed app_secrets /
15
- // credentials stores. Same key story as the vault.
15
+ // credentials stores. These use the app-local key, unlike the workspace-shared
16
+ // app_secrets storage layer.
16
17
  export { encryptSecretValue, decryptSecretValue, isEncryptedSecretValue, } from "./crypto.js";
17
18
  export { createListSecretsHandler, createWriteSecretHandler, createTestSecretHandler, createAdHocSecretHandler, } from "./routes.js";
18
19
  export { resolveKeyReferences, validateUrlAllowlist, getKeyAllowlist, } from "./substitution.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,GAMvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,GAKN,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjE,2EAA2E;AAC3E,4EAA4E;AAC5E,mDAAmD;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,GAGzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iCAAiC,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * Framework secrets registry.\n *\n * Lets templates declaratively register required API keys / credentials so\n * they appear in the sidebar settings UI and the onboarding checklist, and\n * so actions can read them by a stable key.\n *\n * See `.agents/skills/secrets/SKILL.md` for usage.\n */\n\nexport {\n registerRequiredSecret,\n listRequiredSecrets,\n getRequiredSecret,\n __resetSecretsRegistry,\n type RegisteredSecret,\n type SecretScope,\n type SecretKind,\n type SecretValidator,\n type ValidatorResult,\n} from \"./register.js\";\n\nexport {\n writeAppSecret,\n readAppSecret,\n readAppSecretMeta,\n deleteAppSecret,\n getAppSecretMeta,\n listAppSecretsForScope,\n last4,\n type SecretRef,\n type WriteSecretArgs,\n type ReadSecretResult,\n type SecretMeta,\n} from \"./storage.js\";\n\nexport { APP_SECRETS_CREATE_SQL, appSecrets } from \"./schema.js\";\n\n// AES-256-GCM helpers for column-level encryption of per-row secret values\n// (e.g. a per-record share password) that don't fit the keyed app_secrets /\n// credentials stores. Same key story as the vault.\nexport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"./crypto.js\";\n\nexport {\n createListSecretsHandler,\n createWriteSecretHandler,\n createTestSecretHandler,\n createAdHocSecretHandler,\n type SecretStatusPayload,\n type AdHocSecretPayload,\n} from \"./routes.js\";\n\nexport {\n resolveKeyReferences,\n validateUrlAllowlist,\n getKeyAllowlist,\n type ResolveKeyReferencesResult,\n} from \"./substitution.js\";\n\nexport { maybeRegisterSecretOnboardingStep } from \"./onboarding.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/secrets/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,GAMvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,GAKN,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjE,2EAA2E;AAC3E,4EAA4E;AAC5E,+EAA+E;AAC/E,6BAA6B;AAC7B,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,wBAAwB,GAGzB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,iCAAiC,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * Framework secrets registry.\n *\n * Lets templates declaratively register required API keys / credentials so\n * they appear in the sidebar settings UI and the onboarding checklist, and\n * so actions can read them by a stable key.\n *\n * See `.agents/skills/secrets/SKILL.md` for usage.\n */\n\nexport {\n registerRequiredSecret,\n listRequiredSecrets,\n getRequiredSecret,\n __resetSecretsRegistry,\n type RegisteredSecret,\n type SecretScope,\n type SecretKind,\n type SecretValidator,\n type ValidatorResult,\n} from \"./register.js\";\n\nexport {\n writeAppSecret,\n readAppSecret,\n readAppSecretMeta,\n deleteAppSecret,\n getAppSecretMeta,\n listAppSecretsForScope,\n last4,\n type SecretRef,\n type WriteSecretArgs,\n type ReadSecretResult,\n type SecretMeta,\n} from \"./storage.js\";\n\nexport { APP_SECRETS_CREATE_SQL, appSecrets } from \"./schema.js\";\n\n// AES-256-GCM helpers for column-level encryption of per-row secret values\n// (e.g. a per-record share password) that don't fit the keyed app_secrets /\n// credentials stores. These use the app-local key, unlike the workspace-shared\n// app_secrets storage layer.\nexport {\n encryptSecretValue,\n decryptSecretValue,\n isEncryptedSecretValue,\n} from \"./crypto.js\";\n\nexport {\n createListSecretsHandler,\n createWriteSecretHandler,\n createTestSecretHandler,\n createAdHocSecretHandler,\n type SecretStatusPayload,\n type AdHocSecretPayload,\n} from \"./routes.js\";\n\nexport {\n resolveKeyReferences,\n validateUrlAllowlist,\n getKeyAllowlist,\n type ResolveKeyReferencesResult,\n} from \"./substitution.js\";\n\nexport { maybeRegisterSecretOnboardingStep } from \"./onboarding.js\";\n"]}
@@ -108,6 +108,25 @@ export declare const appSecrets: import("drizzle-orm/sqlite-core").SQLiteTableWi
108
108
  }, {}, {
109
109
  length: number;
110
110
  }>;
111
+ sharedEncryptedValue: import("drizzle-orm/sqlite-core").SQLiteColumn<{
112
+ name: "shared_encrypted_value";
113
+ tableName: "app_secrets";
114
+ dataType: "string";
115
+ columnType: "SQLiteText";
116
+ data: string;
117
+ driverParam: string;
118
+ notNull: false;
119
+ hasDefault: false;
120
+ isPrimaryKey: false;
121
+ isAutoincrement: false;
122
+ hasRuntimeDefault: false;
123
+ enumValues: [string, ...string[]];
124
+ baseColumn: never;
125
+ identity: undefined;
126
+ generated: undefined;
127
+ }, {}, {
128
+ length: number;
129
+ }>;
111
130
  description: import("drizzle-orm/sqlite-core").SQLiteColumn<{
112
131
  name: "description";
113
132
  tableName: "app_secrets";
@@ -188,5 +207,5 @@ export declare const appSecrets: import("drizzle-orm/sqlite-core").SQLiteTableWi
188
207
  * `ensureTable()` path in `storage.ts` and by any template-level migration
189
208
  * that wants to create the table up-front.
190
209
  */
191
- export declare const APP_SECRETS_CREATE_SQL = "CREATE TABLE IF NOT EXISTS app_secrets (\n id TEXT PRIMARY KEY,\n scope TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n key TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n description TEXT,\n url_allowlist TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n UNIQUE(scope, scope_id, key)\n)";
210
+ export declare const APP_SECRETS_CREATE_SQL = "CREATE TABLE IF NOT EXISTS app_secrets (\n id TEXT PRIMARY KEY,\n scope TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n key TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n shared_encrypted_value TEXT,\n description TEXT,\n url_allowlist TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n UNIQUE(scope, scope_id, key)\n)";
192
211
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/secrets/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,+TAWjC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/secrets/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBrB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,+VAYjC,CAAC"}
@@ -20,6 +20,8 @@ export const appSecrets = table("app_secrets", {
20
20
  key: text("key").notNull(),
21
21
  /** Encrypted value — never return this through any API. */
22
22
  encryptedValue: text("encrypted_value").notNull(),
23
+ /** Preferred workspace-shared ciphertext; nullable during key migration. */
24
+ sharedEncryptedValue: text("shared_encrypted_value"),
23
25
  /** Optional human-readable description (used for ad-hoc keys). */
24
26
  description: text("description"),
25
27
  /** JSON array of allowed URL origins. Null = allow all. */
@@ -38,6 +40,7 @@ export const APP_SECRETS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS app_secrets (
38
40
  scope_id TEXT NOT NULL,
39
41
  key TEXT NOT NULL,
40
42
  encrypted_value TEXT NOT NULL,
43
+ shared_encrypted_value TEXT,
41
44
  description TEXT,
42
45
  url_allowlist TEXT,
43
46
  created_at INTEGER NOT NULL,
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/secrets/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,EAAE;IAC7C,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,+DAA+D;IAC/D,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACnC,yDAAyD;IACzD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE;IAC1B,2DAA2D;IAC3D,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;IACjD,kEAAkE;IAClE,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;IAChC,2DAA2D;IAC3D,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;EAWpC,CAAC","sourcesContent":["/**\n * Drizzle schema for the framework secrets registry.\n *\n * The `app_secrets` table stores API keys and service credentials that\n * templates register via `registerRequiredSecret()`. Values are always\n * stored encrypted at rest — see `storage.ts` for the crypto layer.\n *\n * Rows are scoped either to a user (by email) or a workspace / organization\n * (by orgId). OAuth-kind secrets never create a row here — they surface via\n * `@agent-native/core/oauth-tokens` instead.\n */\n\nimport { table, text, integer } from \"../db/schema.js\";\n\nexport const appSecrets = table(\"app_secrets\", {\n id: text(\"id\").primaryKey(),\n /** \"user\" or \"workspace\" — who the secret is scoped to. */\n scope: text(\"scope\").notNull(),\n /** Session email for user-scope, orgId for workspace-scope. */\n scopeId: text(\"scope_id\").notNull(),\n /** The registered secret key (e.g. \"OPENAI_API_KEY\"). */\n key: text(\"key\").notNull(),\n /** Encrypted value — never return this through any API. */\n encryptedValue: text(\"encrypted_value\").notNull(),\n /** Optional human-readable description (used for ad-hoc keys). */\n description: text(\"description\"),\n /** JSON array of allowed URL origins. Null = allow all. */\n urlAllowlist: text(\"url_allowlist\"),\n createdAt: integer(\"created_at\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/**\n * Raw SQL for creating the app_secrets table. Used by the on-demand\n * `ensureTable()` path in `storage.ts` and by any template-level migration\n * that wants to create the table up-front.\n */\nexport const APP_SECRETS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS app_secrets (\n id TEXT PRIMARY KEY,\n scope TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n key TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n description TEXT,\n url_allowlist TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n UNIQUE(scope, scope_id, key)\n)`;\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/secrets/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,aAAa,EAAE;IAC7C,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,+DAA+D;IAC/D,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACnC,yDAAyD;IACzD,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE;IAC1B,2DAA2D;IAC3D,cAAc,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;IACjD,4EAA4E;IAC5E,oBAAoB,EAAE,IAAI,CAAC,wBAAwB,CAAC;IACpD,kEAAkE;IAClE,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;IAChC,2DAA2D;IAC3D,YAAY,EAAE,IAAI,CAAC,eAAe,CAAC;IACnC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IAC1C,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CAC3C,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;;;;;;;;;;;EAYpC,CAAC","sourcesContent":["/**\n * Drizzle schema for the framework secrets registry.\n *\n * The `app_secrets` table stores API keys and service credentials that\n * templates register via `registerRequiredSecret()`. Values are always\n * stored encrypted at rest — see `storage.ts` for the crypto layer.\n *\n * Rows are scoped either to a user (by email) or a workspace / organization\n * (by orgId). OAuth-kind secrets never create a row here — they surface via\n * `@agent-native/core/oauth-tokens` instead.\n */\n\nimport { table, text, integer } from \"../db/schema.js\";\n\nexport const appSecrets = table(\"app_secrets\", {\n id: text(\"id\").primaryKey(),\n /** \"user\" or \"workspace\" — who the secret is scoped to. */\n scope: text(\"scope\").notNull(),\n /** Session email for user-scope, orgId for workspace-scope. */\n scopeId: text(\"scope_id\").notNull(),\n /** The registered secret key (e.g. \"OPENAI_API_KEY\"). */\n key: text(\"key\").notNull(),\n /** Encrypted value — never return this through any API. */\n encryptedValue: text(\"encrypted_value\").notNull(),\n /** Preferred workspace-shared ciphertext; nullable during key migration. */\n sharedEncryptedValue: text(\"shared_encrypted_value\"),\n /** Optional human-readable description (used for ad-hoc keys). */\n description: text(\"description\"),\n /** JSON array of allowed URL origins. Null = allow all. */\n urlAllowlist: text(\"url_allowlist\"),\n createdAt: integer(\"created_at\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/**\n * Raw SQL for creating the app_secrets table. Used by the on-demand\n * `ensureTable()` path in `storage.ts` and by any template-level migration\n * that wants to create the table up-front.\n */\nexport const APP_SECRETS_CREATE_SQL = `CREATE TABLE IF NOT EXISTS app_secrets (\n id TEXT PRIMARY KEY,\n scope TEXT NOT NULL,\n scope_id TEXT NOT NULL,\n key TEXT NOT NULL,\n encrypted_value TEXT NOT NULL,\n shared_encrypted_value TEXT,\n description TEXT,\n url_allowlist TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n UNIQUE(scope, scope_id, key)\n)`;\n"]}
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Storage layer for the framework secrets registry.
3
3
  *
4
- * Values are encrypted at rest with AES-256-GCM. The encryption key is
5
- * derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the existing
6
- * `BETTER_AUTH_SECRET` env var (fallback so templates don't need a second
7
- * secret during development). If neither is set in production we fall back
8
- * to a machine-local key derived from the cwd the secret is still only
9
- * readable on this machine, but consider setting `SECRETS_ENCRYPTION_KEY`
10
- * for a stable, rotatable key.
4
+ * Values are encrypted at rest with AES-256-GCM. The workspace-shared
5
+ * encryption key is derived from `SECRETS_ENCRYPTION_KEY` (preferred) or the
6
+ * existing `BETTER_AUTH_SECRET` env var (fallback so templates don't need a
7
+ * second secret during development). Deployments that only have the legacy
8
+ * app-scoped key continue to work until shared key material is configured;
9
+ * once configured, new rows use the shared key and reads retain a legacy-key
10
+ * fallback for existing rows.
11
11
  *
12
12
  * Secret values are NEVER logged and NEVER returned from any route handler.
13
13
  */
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA8EjD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3C;AAMD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA8D3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAoCD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAmBlC;AAED,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAyBzC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAItD;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA2B5B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CA0BvB;AAeD,wBAAsB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAStE"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/secrets/storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAaH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AA+FjD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI3C;AAMD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAuE3E;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AA8GD;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA0BlC;AAED,wEAAwE;AACxE,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAgCzC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAItD;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,SAAS,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAkC5B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,UAAU,EAAE,CAAC,CAmCvB;AAeD,wBAAsB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAStE"}