@vellumai/assistant 0.3.18 → 0.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/ARCHITECTURE.md +155 -15
  2. package/Dockerfile +1 -0
  3. package/README.md +40 -4
  4. package/docs/architecture/integrations.md +7 -11
  5. package/docs/architecture/security.md +80 -0
  6. package/package.json +1 -1
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -0
  8. package/src/__tests__/approval-primitive.test.ts +540 -0
  9. package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
  10. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
  12. package/src/__tests__/call-controller.test.ts +605 -104
  13. package/src/__tests__/channel-invite-transport.test.ts +264 -0
  14. package/src/__tests__/checker.test.ts +60 -0
  15. package/src/__tests__/cli.test.ts +42 -1
  16. package/src/__tests__/config-schema.test.ts +11 -127
  17. package/src/__tests__/config-watcher.test.ts +0 -8
  18. package/src/__tests__/daemon-lifecycle.test.ts +1 -0
  19. package/src/__tests__/daemon-server-session-init.test.ts +8 -2
  20. package/src/__tests__/diff.test.ts +22 -0
  21. package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
  22. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +779 -0
  23. package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
  24. package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
  25. package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
  26. package/src/__tests__/guardian-dispatch.test.ts +185 -1
  27. package/src/__tests__/guardian-grant-minting.test.ts +532 -0
  28. package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
  29. package/src/__tests__/invite-redemption-service.test.ts +306 -0
  30. package/src/__tests__/ipc-snapshot.test.ts +58 -0
  31. package/src/__tests__/notification-decision-fallback.test.ts +88 -0
  32. package/src/__tests__/remote-skill-policy.test.ts +215 -0
  33. package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
  34. package/src/__tests__/sandbox-host-parity.test.ts +6 -13
  35. package/src/__tests__/scoped-approval-grants.test.ts +521 -0
  36. package/src/__tests__/scoped-grant-security-matrix.test.ts +444 -0
  37. package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
  38. package/src/__tests__/session-load-history-repair.test.ts +169 -2
  39. package/src/__tests__/session-runtime-assembly.test.ts +33 -5
  40. package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
  41. package/src/__tests__/skill-feature-flags.test.ts +188 -0
  42. package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
  43. package/src/__tests__/skill-mirror-parity.test.ts +1 -0
  44. package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
  45. package/src/__tests__/system-prompt.test.ts +1 -1
  46. package/src/__tests__/terminal-sandbox.test.ts +142 -9
  47. package/src/__tests__/terminal-tools.test.ts +2 -93
  48. package/src/__tests__/thread-seed-composer.test.ts +18 -0
  49. package/src/__tests__/tool-approval-handler.test.ts +350 -0
  50. package/src/__tests__/trust-store.test.ts +2 -0
  51. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
  52. package/src/__tests__/voice-scoped-grant-consumer.test.ts +533 -0
  53. package/src/agent/loop.ts +36 -1
  54. package/src/approvals/approval-primitive.ts +381 -0
  55. package/src/approvals/guardian-decision-primitive.ts +191 -0
  56. package/src/calls/call-controller.ts +276 -212
  57. package/src/calls/call-domain.ts +56 -6
  58. package/src/calls/guardian-dispatch.ts +56 -0
  59. package/src/calls/relay-server.ts +13 -0
  60. package/src/calls/types.ts +1 -1
  61. package/src/calls/voice-session-bridge.ts +59 -4
  62. package/src/cli/core-commands.ts +0 -4
  63. package/src/cli.ts +76 -34
  64. package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
  65. package/src/config/assistant-feature-flags.ts +162 -0
  66. package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
  67. package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
  68. package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
  69. package/src/config/bundled-skills/notifications/SKILL.md +18 -0
  70. package/src/config/bundled-skills/reminder/SKILL.md +49 -2
  71. package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
  72. package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
  73. package/src/config/core-schema.ts +1 -1
  74. package/src/config/env-registry.ts +10 -0
  75. package/src/config/feature-flag-registry.json +61 -0
  76. package/src/config/loader.ts +22 -1
  77. package/src/config/sandbox-schema.ts +0 -39
  78. package/src/config/schema.ts +12 -2
  79. package/src/config/skill-state.ts +34 -0
  80. package/src/config/skills-schema.ts +26 -0
  81. package/src/config/skills.ts +9 -0
  82. package/src/config/system-prompt.ts +110 -46
  83. package/src/config/templates/SOUL.md +1 -1
  84. package/src/config/types.ts +19 -1
  85. package/src/config/vellum-skills/catalog.json +1 -1
  86. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
  87. package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
  88. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
  89. package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
  90. package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
  91. package/src/daemon/config-watcher.ts +0 -1
  92. package/src/daemon/daemon-control.ts +1 -1
  93. package/src/daemon/guardian-invite-intent.ts +124 -0
  94. package/src/daemon/handlers/avatar.ts +68 -0
  95. package/src/daemon/handlers/browser.ts +2 -2
  96. package/src/daemon/handlers/config-channels.ts +18 -0
  97. package/src/daemon/handlers/guardian-actions.ts +120 -0
  98. package/src/daemon/handlers/index.ts +4 -0
  99. package/src/daemon/handlers/sessions.ts +19 -0
  100. package/src/daemon/handlers/shared.ts +3 -1
  101. package/src/daemon/handlers/skills.ts +45 -2
  102. package/src/daemon/install-cli-launchers.ts +58 -13
  103. package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
  104. package/src/daemon/ipc-contract/sessions.ts +8 -2
  105. package/src/daemon/ipc-contract/settings.ts +25 -2
  106. package/src/daemon/ipc-contract/skills.ts +1 -0
  107. package/src/daemon/ipc-contract-inventory.json +10 -0
  108. package/src/daemon/ipc-contract.ts +4 -0
  109. package/src/daemon/lifecycle.ts +6 -2
  110. package/src/daemon/main.ts +1 -0
  111. package/src/daemon/server.ts +1 -0
  112. package/src/daemon/session-lifecycle.ts +52 -7
  113. package/src/daemon/session-memory.ts +45 -0
  114. package/src/daemon/session-process.ts +260 -422
  115. package/src/daemon/session-runtime-assembly.ts +12 -0
  116. package/src/daemon/session-skill-tools.ts +14 -1
  117. package/src/daemon/session-tool-setup.ts +5 -0
  118. package/src/daemon/session.ts +11 -0
  119. package/src/daemon/tool-side-effects.ts +35 -9
  120. package/src/index.ts +0 -2
  121. package/src/memory/conversation-display-order-migration.ts +44 -0
  122. package/src/memory/conversation-queries.ts +2 -0
  123. package/src/memory/conversation-store.ts +91 -0
  124. package/src/memory/db-init.ts +13 -1
  125. package/src/memory/embedding-local.ts +22 -8
  126. package/src/memory/guardian-action-store.ts +133 -2
  127. package/src/memory/guardian-verification.ts +1 -1
  128. package/src/memory/ingress-invite-store.ts +95 -1
  129. package/src/memory/migrations/033-scoped-approval-grants.ts +51 -0
  130. package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
  131. package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
  132. package/src/memory/migrations/index.ts +3 -0
  133. package/src/memory/schema.ts +35 -1
  134. package/src/memory/scoped-approval-grants.ts +518 -0
  135. package/src/messaging/providers/slack/client.ts +12 -0
  136. package/src/messaging/providers/slack/types.ts +5 -0
  137. package/src/notifications/decision-engine.ts +49 -12
  138. package/src/notifications/emit-signal.ts +7 -0
  139. package/src/notifications/signal.ts +7 -0
  140. package/src/notifications/thread-seed-composer.ts +2 -1
  141. package/src/permissions/checker.ts +27 -0
  142. package/src/runtime/channel-approval-types.ts +16 -6
  143. package/src/runtime/channel-approvals.ts +19 -15
  144. package/src/runtime/channel-invite-transport.ts +85 -0
  145. package/src/runtime/channel-invite-transports/telegram.ts +105 -0
  146. package/src/runtime/guardian-action-grant-minter.ts +154 -0
  147. package/src/runtime/guardian-action-message-composer.ts +30 -0
  148. package/src/runtime/guardian-decision-types.ts +91 -0
  149. package/src/runtime/http-server.ts +23 -1
  150. package/src/runtime/ingress-service.ts +22 -0
  151. package/src/runtime/invite-redemption-service.ts +181 -0
  152. package/src/runtime/invite-redemption-templates.ts +39 -0
  153. package/src/runtime/routes/call-routes.ts +2 -1
  154. package/src/runtime/routes/guardian-action-routes.ts +206 -0
  155. package/src/runtime/routes/guardian-approval-interception.ts +66 -74
  156. package/src/runtime/routes/inbound-message-handler.ts +568 -409
  157. package/src/runtime/routes/pairing-routes.ts +4 -0
  158. package/src/security/encrypted-store.ts +31 -17
  159. package/src/security/keychain.ts +176 -2
  160. package/src/security/secure-keys.ts +97 -0
  161. package/src/security/tool-approval-digest.ts +67 -0
  162. package/src/skills/remote-skill-policy.ts +131 -0
  163. package/src/tools/browser/browser-execution.ts +2 -2
  164. package/src/tools/browser/browser-manager.ts +46 -32
  165. package/src/tools/browser/browser-screencast.ts +2 -2
  166. package/src/tools/calls/call-start.ts +1 -1
  167. package/src/tools/executor.ts +22 -17
  168. package/src/tools/network/script-proxy/session-manager.ts +1 -5
  169. package/src/tools/skills/load.ts +22 -8
  170. package/src/tools/system/avatar-generator.ts +119 -0
  171. package/src/tools/system/navigate-settings.ts +65 -0
  172. package/src/tools/system/open-system-settings.ts +75 -0
  173. package/src/tools/system/voice-config.ts +121 -32
  174. package/src/tools/terminal/backends/native.ts +40 -19
  175. package/src/tools/terminal/backends/types.ts +3 -3
  176. package/src/tools/terminal/parser.ts +1 -1
  177. package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
  178. package/src/tools/terminal/sandbox.ts +1 -12
  179. package/src/tools/terminal/shell.ts +3 -31
  180. package/src/tools/tool-approval-handler.ts +141 -3
  181. package/src/tools/tool-manifest.ts +6 -0
  182. package/src/tools/types.ts +6 -0
  183. package/src/util/diff.ts +36 -13
  184. package/Dockerfile.sandbox +0 -5
  185. package/src/__tests__/doordash-client.test.ts +0 -187
  186. package/src/__tests__/doordash-session.test.ts +0 -154
  187. package/src/__tests__/signup-e2e.test.ts +0 -354
  188. package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
  189. package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
  190. package/src/cli/doordash.ts +0 -1057
  191. package/src/config/bundled-skills/doordash/SKILL.md +0 -163
  192. package/src/config/templates/LOOKS.md +0 -25
  193. package/src/doordash/cart-queries.ts +0 -787
  194. package/src/doordash/client.ts +0 -1016
  195. package/src/doordash/order-queries.ts +0 -85
  196. package/src/doordash/queries.ts +0 -13
  197. package/src/doordash/query-extractor.ts +0 -94
  198. package/src/doordash/search-queries.ts +0 -203
  199. package/src/doordash/session.ts +0 -84
  200. package/src/doordash/store-queries.ts +0 -246
  201. package/src/doordash/types.ts +0 -367
  202. package/src/tools/terminal/backends/docker.ts +0 -379
@@ -1,354 +0,0 @@
1
- import { mkdirSync,mkdtempSync, rmSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
-
5
- import { afterAll, beforeAll, beforeEach, describe, expect, mock,test } from 'bun:test';
6
-
7
- // ── Mocks (before any app imports) ──────────────────────────────────
8
-
9
- const testDir = mkdtempSync(join(tmpdir(), 'signup-e2e-'));
10
-
11
- mock.module('../util/logger.js', () => ({
12
- getLogger: () =>
13
- new Proxy({} as Record<string, unknown>, {
14
- get: () => () => {},
15
- }),
16
- }));
17
-
18
- mock.module('../util/platform.js', () => ({
19
- getDataDir: () => testDir,
20
- isMacOS: () => process.platform === 'darwin',
21
- isLinux: () => process.platform === 'linux',
22
- isWindows: () => process.platform === 'win32',
23
- getSocketPath: () => join(testDir, 'test.sock'),
24
- getPidPath: () => join(testDir, 'test.pid'),
25
- getDbPath: () => join(testDir, 'test.db'),
26
- getLogPath: () => join(testDir, 'test.log'),
27
- ensureDataDir: () => {},
28
- getPlatformName: () => process.platform,
29
- }));
30
-
31
- mock.module('../tools/registry.js', () => ({
32
- registerTool: () => {},
33
- }));
34
-
35
- // Force encrypted backend (no keychain) with temp store path
36
- import { _overrideDeps, _resetDeps } from '../security/keychain.js';
37
-
38
- _overrideDeps({
39
- isMacOS: () => false,
40
- isLinux: () => false,
41
- execFileSync: (() => '') as unknown as typeof import('node:child_process').execFileSync,
42
- });
43
-
44
- import { _setStorePath } from '../security/encrypted-store.js';
45
- import { _resetBackend } from '../security/secure-keys.js';
46
-
47
- const STORE_PATH = join(testDir, 'keys.enc');
48
-
49
- // ── Imports (after mocks) ───────────────────────────────────────────
50
-
51
- import {
52
- createAccount,
53
- listAccounts,
54
- } from '../memory/account-store.js';
55
- import { getDb, initializeDb, resetDb } from '../memory/db.js';
56
- import {
57
- deleteSecureKey,
58
- getSecureKey,
59
- listSecureKeys,
60
- setSecureKey,
61
- } from '../security/secure-keys.js';
62
- import {
63
- executeBrowserClick,
64
- executeBrowserClose,
65
- executeBrowserExtract,
66
- executeBrowserFillCredential,
67
- executeBrowserNavigate,
68
- executeBrowserType,
69
- } from '../tools/browser/headless-browser.js';
70
- import { _setMetadataPath,upsertCredentialMetadata } from '../tools/credentials/metadata-store.js';
71
- import type { ToolContext } from '../tools/types.js';
72
- import { createMockSignupServer, type MockSignupServer } from './fixtures/mock-signup-server.js';
73
-
74
- // ── Setup ───────────────────────────────────────────────────────────
75
-
76
- initializeDb();
77
-
78
- const ctx: ToolContext = {
79
- sessionId: 'e2e-test',
80
- conversationId: 'e2e-conv',
81
- workingDir: '/tmp',
82
- };
83
-
84
- // Test-only password (assembled to avoid pre-commit false positives)
85
- const TEST_PASSWORD = ['S3cure', '!Pass', '789'].join('');
86
-
87
- let server: MockSignupServer;
88
- let url: string;
89
-
90
- beforeAll(async () => {
91
- _resetBackend();
92
- mkdirSync(join(testDir, 'browser-profile'), { recursive: true });
93
- _setStorePath(STORE_PATH);
94
- _setMetadataPath(join(testDir, 'metadata.json'));
95
-
96
- server = createMockSignupServer();
97
- ({ url } = await server.start());
98
- });
99
-
100
- afterAll(async () => {
101
- resetDb();
102
- await executeBrowserClose({ close_all_pages: true }, ctx);
103
- await server.stop();
104
- _setMetadataPath(null);
105
- _setStorePath(null);
106
- _resetBackend();
107
- _resetDeps();
108
- mock.restore();
109
- try {
110
- rmSync(testDir, { recursive: true });
111
- } catch {
112
- /* best effort */
113
- }
114
- });
115
-
116
- beforeEach(() => {
117
- server.reset();
118
- // Clear accounts table
119
- const db = getDb();
120
- db.run('DELETE FROM accounts');
121
- // Clear credentials
122
- for (const key of listSecureKeys()) {
123
- deleteSecureKey(key);
124
- }
125
- });
126
-
127
- // ── Tests ───────────────────────────────────────────────────────────
128
-
129
- describe('end-to-end signup flow', () => {
130
- test('happy path: full signup with credential fill', async () => {
131
- // Store credential in vault with metadata (broker requires metadata)
132
- const storeOk = setSecureKey(`credential:mockservice:password`, TEST_PASSWORD);
133
- expect(storeOk).toBe(true);
134
- upsertCredentialMetadata('mockservice', 'password', { allowedTools: ['browser_fill_credential'] });
135
- expect(getSecureKey('credential:mockservice:password')).toBe(TEST_PASSWORD);
136
-
137
- // Navigate to signup
138
- const navResult = await executeBrowserNavigate(
139
- { url: `${url}/signup`, allow_private_network: true },
140
- ctx,
141
- );
142
- expect(navResult.isError).toBe(false);
143
- expect(navResult.content).toContain('Status: 200');
144
-
145
- // Step 1: Name
146
- await executeBrowserType(
147
- { selector: 'input[name="first_name"]', text: 'Jane' },
148
- ctx,
149
- );
150
- await executeBrowserType(
151
- { selector: 'input[name="last_name"]', text: 'Doe' },
152
- ctx,
153
- );
154
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
155
-
156
- // Step 2: Username + password (via credential fill)
157
- await executeBrowserType(
158
- { selector: 'input[name="username"]', text: 'janedoe' },
159
- ctx,
160
- );
161
- const fillResult = await executeBrowserFillCredential(
162
- {
163
- service: 'mockservice',
164
- field: 'password',
165
- selector: 'input[name="password"]',
166
- },
167
- ctx,
168
- );
169
- expect(fillResult.isError).toBe(false);
170
- // Credential value must NEVER appear in output
171
- expect(fillResult.content).not.toContain(TEST_PASSWORD);
172
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
173
-
174
- // Step 3: Verification code
175
- const code = server.getVerificationCode();
176
- expect(code).toMatch(/^\d{6}$/);
177
- await executeBrowserType(
178
- { selector: 'input[name="code"]', text: code },
179
- ctx,
180
- );
181
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
182
-
183
- // Step 4: Solve CAPTCHA checkbox and submit
184
- await executeBrowserClick(
185
- { selector: 'input[name="captcha_solved"]' },
186
- ctx,
187
- );
188
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
189
-
190
- // Verify success page
191
- const extractResult = await executeBrowserExtract({}, ctx);
192
- expect(extractResult.content).toContain('Account created successfully');
193
-
194
- // Register account in the account registry
195
- const acct = createAccount({
196
- service: 'mockservice',
197
- username: 'janedoe',
198
- credentialRef: 'mockservice',
199
- status: 'active',
200
- });
201
- expect(acct.id).toBeTruthy();
202
-
203
- // List accounts
204
- const accounts = listAccounts();
205
- expect(accounts).toHaveLength(1);
206
- expect(accounts[0].username).toBe('janedoe');
207
-
208
- // Verify server recorded the account
209
- const serverAccounts = server.getAccounts();
210
- expect(serverAccounts).toHaveLength(1);
211
- expect(serverAccounts[0].username).toBe('janedoe');
212
- }, 60_000);
213
-
214
- test('credential not found produces helpful error', async () => {
215
- await executeBrowserNavigate(
216
- { url: `${url}/signup`, allow_private_network: true },
217
- ctx,
218
- );
219
- const result = await executeBrowserFillCredential(
220
- {
221
- service: 'nonexistent',
222
- field: 'password',
223
- selector: 'input[name="first_name"]',
224
- },
225
- ctx,
226
- );
227
- expect(result.isError).toBe(true);
228
- expect(result.content).toContain('No credential stored');
229
- expect(result.content).toContain('credential_store');
230
- }, 30_000);
231
-
232
- test('taken username shows validation error', async () => {
233
- // Store credential so fill works
234
- setSecureKey(`credential:mockservice:password`, TEST_PASSWORD);
235
- upsertCredentialMetadata('mockservice', 'password', { allowedTools: ['browser_fill_credential'] });
236
-
237
- await executeBrowserNavigate(
238
- { url: `${url}/signup`, allow_private_network: true },
239
- ctx,
240
- );
241
-
242
- // Complete step 1
243
- await executeBrowserType(
244
- { selector: 'input[name="first_name"]', text: 'Test' },
245
- ctx,
246
- );
247
- await executeBrowserType(
248
- { selector: 'input[name="last_name"]', text: 'User' },
249
- ctx,
250
- );
251
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
252
-
253
- // Try taken username
254
- await executeBrowserType(
255
- { selector: 'input[name="username"]', text: 'taken' },
256
- ctx,
257
- );
258
- await executeBrowserFillCredential(
259
- {
260
- service: 'mockservice',
261
- field: 'password',
262
- selector: 'input[name="password"]',
263
- },
264
- ctx,
265
- );
266
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
267
-
268
- // Should see error
269
- const extract = await executeBrowserExtract({}, ctx);
270
- expect(extract.content).toContain('taken');
271
- }, 30_000);
272
-
273
- test('wrong verification code shows error', async () => {
274
- // Store credential so fill works
275
- setSecureKey(`credential:mockservice:password`, TEST_PASSWORD);
276
- upsertCredentialMetadata('mockservice', 'password', { allowedTools: ['browser_fill_credential'] });
277
-
278
- await executeBrowserNavigate(
279
- { url: `${url}/signup`, allow_private_network: true },
280
- ctx,
281
- );
282
-
283
- // Step 1: name
284
- await executeBrowserType(
285
- { selector: 'input[name="first_name"]', text: 'Test' },
286
- ctx,
287
- );
288
- await executeBrowserType(
289
- { selector: 'input[name="last_name"]', text: 'User' },
290
- ctx,
291
- );
292
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
293
-
294
- // Step 2: username/password
295
- await executeBrowserType(
296
- { selector: 'input[name="username"]', text: 'testuser' },
297
- ctx,
298
- );
299
- await executeBrowserFillCredential(
300
- {
301
- service: 'mockservice',
302
- field: 'password',
303
- selector: 'input[name="password"]',
304
- },
305
- ctx,
306
- );
307
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
308
-
309
- // Step 3: wrong code
310
- await executeBrowserType(
311
- { selector: 'input[name="code"]', text: '000000' },
312
- ctx,
313
- );
314
- await executeBrowserClick({ selector: 'button[type="submit"]' }, ctx);
315
-
316
- // Verify error message
317
- const extract = await executeBrowserExtract({}, ctx);
318
- expect(extract.content).toContain('Invalid verification code');
319
- }, 30_000);
320
-
321
- test('credential value never leaks into any tool output', async () => {
322
- const secret = ['MyS3cret', '!Value', '42'].join('');
323
- setSecureKey(`credential:leak-test:password`, secret);
324
- upsertCredentialMetadata('leak-test', 'password', { allowedTools: ['browser_fill_credential'] });
325
-
326
- await executeBrowserNavigate(
327
- { url: `${url}/signup`, allow_private_network: true },
328
- ctx,
329
- );
330
-
331
- // Fill credential
332
- const fillResult = await executeBrowserFillCredential(
333
- {
334
- service: 'leak-test',
335
- field: 'password',
336
- selector: 'input[name="first_name"]',
337
- },
338
- ctx,
339
- );
340
-
341
- // Secret must not appear in fill output
342
- expect(fillResult.content).not.toContain(secret);
343
-
344
- // List credentials — should only show metadata
345
- const allKeys = listSecureKeys();
346
- const credentialKeys = allKeys.filter((k) => k.startsWith('credential:'));
347
- const entries = credentialKeys.map((k) => {
348
- const parts = k.split(':');
349
- return { service: parts[1], field: parts.slice(2).join(':') };
350
- });
351
- const listOutput = JSON.stringify(entries);
352
- expect(listOutput).not.toContain(secret);
353
- }, 30_000);
354
- });