@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.
- package/ARCHITECTURE.md +155 -15
- package/Dockerfile +1 -0
- package/README.md +40 -4
- package/docs/architecture/integrations.md +7 -11
- package/docs/architecture/security.md +80 -0
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +58 -0
- package/src/__tests__/approval-primitive.test.ts +540 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +206 -0
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +198 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +272 -0
- package/src/__tests__/call-controller.test.ts +605 -104
- package/src/__tests__/channel-invite-transport.test.ts +264 -0
- package/src/__tests__/checker.test.ts +60 -0
- package/src/__tests__/cli.test.ts +42 -1
- package/src/__tests__/config-schema.test.ts +11 -127
- package/src/__tests__/config-watcher.test.ts +0 -8
- package/src/__tests__/daemon-lifecycle.test.ts +1 -0
- package/src/__tests__/daemon-server-session-init.test.ts +8 -2
- package/src/__tests__/diff.test.ts +22 -0
- package/src/__tests__/guardian-action-copy-generator.test.ts +5 -0
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +779 -0
- package/src/__tests__/guardian-action-late-reply.test.ts +546 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +774 -0
- package/src/__tests__/guardian-control-plane-policy.test.ts +36 -3
- package/src/__tests__/guardian-dispatch.test.ts +185 -1
- package/src/__tests__/guardian-grant-minting.test.ts +532 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +367 -0
- package/src/__tests__/invite-redemption-service.test.ts +306 -0
- package/src/__tests__/ipc-snapshot.test.ts +58 -0
- package/src/__tests__/notification-decision-fallback.test.ts +88 -0
- package/src/__tests__/remote-skill-policy.test.ts +215 -0
- package/src/__tests__/sandbox-diagnostics.test.ts +6 -249
- package/src/__tests__/sandbox-host-parity.test.ts +6 -13
- package/src/__tests__/scoped-approval-grants.test.ts +521 -0
- package/src/__tests__/scoped-grant-security-matrix.test.ts +444 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +1 -19
- package/src/__tests__/session-load-history-repair.test.ts +169 -2
- package/src/__tests__/session-runtime-assembly.test.ts +33 -5
- package/src/__tests__/skill-feature-flags-integration.test.ts +171 -0
- package/src/__tests__/skill-feature-flags.test.ts +188 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +141 -0
- package/src/__tests__/skill-mirror-parity.test.ts +1 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +363 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-sandbox.test.ts +142 -9
- package/src/__tests__/terminal-tools.test.ts +2 -93
- package/src/__tests__/thread-seed-composer.test.ts +18 -0
- package/src/__tests__/tool-approval-handler.test.ts +350 -0
- package/src/__tests__/trust-store.test.ts +2 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +8 -10
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +533 -0
- package/src/agent/loop.ts +36 -1
- package/src/approvals/approval-primitive.ts +381 -0
- package/src/approvals/guardian-decision-primitive.ts +191 -0
- package/src/calls/call-controller.ts +276 -212
- package/src/calls/call-domain.ts +56 -6
- package/src/calls/guardian-dispatch.ts +56 -0
- package/src/calls/relay-server.ts +13 -0
- package/src/calls/types.ts +1 -1
- package/src/calls/voice-session-bridge.ts +59 -4
- package/src/cli/core-commands.ts +0 -4
- package/src/cli.ts +76 -34
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +179 -0
- package/src/config/assistant-feature-flags.ts +162 -0
- package/src/config/bundled-skills/api-mapping/icon.svg +18 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +30 -0
- package/src/config/bundled-skills/messaging/tools/slack-delete-message.ts +24 -0
- package/src/config/bundled-skills/notifications/SKILL.md +18 -0
- package/src/config/bundled-skills/reminder/SKILL.md +49 -2
- package/src/config/bundled-skills/time-based-actions/SKILL.md +49 -2
- package/src/config/bundled-skills/voice-setup/SKILL.md +122 -0
- package/src/config/core-schema.ts +1 -1
- package/src/config/env-registry.ts +10 -0
- package/src/config/feature-flag-registry.json +61 -0
- package/src/config/loader.ts +22 -1
- package/src/config/sandbox-schema.ts +0 -39
- package/src/config/schema.ts +12 -2
- package/src/config/skill-state.ts +34 -0
- package/src/config/skills-schema.ts +26 -0
- package/src/config/skills.ts +9 -0
- package/src/config/system-prompt.ts +110 -46
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +19 -1
- package/src/config/vellum-skills/catalog.json +1 -1
- package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +1 -0
- package/src/config/vellum-skills/sms-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -1
- package/src/config/vellum-skills/trusted-contacts/SKILL.md +104 -3
- package/src/config/vellum-skills/twilio-setup/SKILL.md +1 -1
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/guardian-invite-intent.ts +124 -0
- package/src/daemon/handlers/avatar.ts +68 -0
- package/src/daemon/handlers/browser.ts +2 -2
- package/src/daemon/handlers/config-channels.ts +18 -0
- package/src/daemon/handlers/guardian-actions.ts +120 -0
- package/src/daemon/handlers/index.ts +4 -0
- package/src/daemon/handlers/sessions.ts +19 -0
- package/src/daemon/handlers/shared.ts +3 -1
- package/src/daemon/handlers/skills.ts +45 -2
- package/src/daemon/install-cli-launchers.ts +58 -13
- package/src/daemon/ipc-contract/guardian-actions.ts +53 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -2
- package/src/daemon/ipc-contract/settings.ts +25 -2
- package/src/daemon/ipc-contract/skills.ts +1 -0
- package/src/daemon/ipc-contract-inventory.json +10 -0
- package/src/daemon/ipc-contract.ts +4 -0
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/main.ts +1 -0
- package/src/daemon/server.ts +1 -0
- package/src/daemon/session-lifecycle.ts +52 -7
- package/src/daemon/session-memory.ts +45 -0
- package/src/daemon/session-process.ts +260 -422
- package/src/daemon/session-runtime-assembly.ts +12 -0
- package/src/daemon/session-skill-tools.ts +14 -1
- package/src/daemon/session-tool-setup.ts +5 -0
- package/src/daemon/session.ts +11 -0
- package/src/daemon/tool-side-effects.ts +35 -9
- package/src/index.ts +0 -2
- package/src/memory/conversation-display-order-migration.ts +44 -0
- package/src/memory/conversation-queries.ts +2 -0
- package/src/memory/conversation-store.ts +91 -0
- package/src/memory/db-init.ts +13 -1
- package/src/memory/embedding-local.ts +22 -8
- package/src/memory/guardian-action-store.ts +133 -2
- package/src/memory/guardian-verification.ts +1 -1
- package/src/memory/ingress-invite-store.ts +95 -1
- package/src/memory/migrations/033-scoped-approval-grants.ts +51 -0
- package/src/memory/migrations/034-guardian-action-tool-metadata.ts +12 -0
- package/src/memory/migrations/035-guardian-action-supersession.ts +23 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/schema.ts +35 -1
- package/src/memory/scoped-approval-grants.ts +518 -0
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/notifications/decision-engine.ts +49 -12
- package/src/notifications/emit-signal.ts +7 -0
- package/src/notifications/signal.ts +7 -0
- package/src/notifications/thread-seed-composer.ts +2 -1
- package/src/permissions/checker.ts +27 -0
- package/src/runtime/channel-approval-types.ts +16 -6
- package/src/runtime/channel-approvals.ts +19 -15
- package/src/runtime/channel-invite-transport.ts +85 -0
- package/src/runtime/channel-invite-transports/telegram.ts +105 -0
- package/src/runtime/guardian-action-grant-minter.ts +154 -0
- package/src/runtime/guardian-action-message-composer.ts +30 -0
- package/src/runtime/guardian-decision-types.ts +91 -0
- package/src/runtime/http-server.ts +23 -1
- package/src/runtime/ingress-service.ts +22 -0
- package/src/runtime/invite-redemption-service.ts +181 -0
- package/src/runtime/invite-redemption-templates.ts +39 -0
- package/src/runtime/routes/call-routes.ts +2 -1
- package/src/runtime/routes/guardian-action-routes.ts +206 -0
- package/src/runtime/routes/guardian-approval-interception.ts +66 -74
- package/src/runtime/routes/inbound-message-handler.ts +568 -409
- package/src/runtime/routes/pairing-routes.ts +4 -0
- package/src/security/encrypted-store.ts +31 -17
- package/src/security/keychain.ts +176 -2
- package/src/security/secure-keys.ts +97 -0
- package/src/security/tool-approval-digest.ts +67 -0
- package/src/skills/remote-skill-policy.ts +131 -0
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/browser-manager.ts +46 -32
- package/src/tools/browser/browser-screencast.ts +2 -2
- package/src/tools/calls/call-start.ts +1 -1
- package/src/tools/executor.ts +22 -17
- package/src/tools/network/script-proxy/session-manager.ts +1 -5
- package/src/tools/skills/load.ts +22 -8
- package/src/tools/system/avatar-generator.ts +119 -0
- package/src/tools/system/navigate-settings.ts +65 -0
- package/src/tools/system/open-system-settings.ts +75 -0
- package/src/tools/system/voice-config.ts +121 -32
- package/src/tools/terminal/backends/native.ts +40 -19
- package/src/tools/terminal/backends/types.ts +3 -3
- package/src/tools/terminal/parser.ts +1 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +6 -87
- package/src/tools/terminal/sandbox.ts +1 -12
- package/src/tools/terminal/shell.ts +3 -31
- package/src/tools/tool-approval-handler.ts +141 -3
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +6 -0
- package/src/util/diff.ts +36 -13
- package/Dockerfile.sandbox +0 -5
- package/src/__tests__/doordash-client.test.ts +0 -187
- package/src/__tests__/doordash-session.test.ts +0 -154
- package/src/__tests__/signup-e2e.test.ts +0 -354
- package/src/__tests__/terminal-sandbox-docker.test.ts +0 -1065
- package/src/__tests__/terminal-sandbox.integration.test.ts +0 -180
- package/src/cli/doordash.ts +0 -1057
- package/src/config/bundled-skills/doordash/SKILL.md +0 -163
- package/src/config/templates/LOOKS.md +0 -25
- package/src/doordash/cart-queries.ts +0 -787
- package/src/doordash/client.ts +0 -1016
- package/src/doordash/order-queries.ts +0 -85
- package/src/doordash/queries.ts +0 -13
- package/src/doordash/query-extractor.ts +0 -94
- package/src/doordash/search-queries.ts +0 -203
- package/src/doordash/session.ts +0 -84
- package/src/doordash/store-queries.ts +0 -246
- package/src/doordash/types.ts +0 -367
- 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
|
-
});
|