auramaxx 0.0.13 → 0.0.15
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +37 -37
- package/.next/app-path-routes-manifest.json +8 -8
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +34 -34
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/api/[...doc]/page.js.nft.json +1 -1
- package/.next/server/app/api/[...doc]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent-requests/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/install/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/manifests/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/apps/static/[...path]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/docs/plain/route.js.nft.json +1 -1
- package/.next/server/app/api/docs/plain/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/events/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/[channel]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/import-from-openclaw/validate/[channel]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/page.js.nft.json +1 -1
- package/.next/server/app/api/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route.js +7 -8
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/apps/[wid]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/apps/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/export/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/config/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/import/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/workspace/route_client-reference-manifest.js +1 -1
- package/.next/server/app/app-legacy-do-not-use/page_client-reference-manifest.js +1 -1
- package/.next/server/app/app-legacy-do-not-use.html +1 -1
- package/.next/server/app/app-legacy-do-not-use.rsc +1 -1
- package/.next/server/app/approve/[actionId]/page.js +1 -1
- package/.next/server/app/approve/[actionId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/docs/[...doc]/page.js.nft.json +1 -1
- package/.next/server/app/docs/[...doc]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/docs/page.js.nft.json +1 -1
- package/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/health/page_client-reference-manifest.js +1 -1
- package/.next/server/app/health.html +1 -1
- package/.next/server/app/health.rsc +1 -1
- package/.next/server/app/hello/page_client-reference-manifest.js +1 -1
- package/.next/server/app/hello.html +1 -1
- package/.next/server/app/hello.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/privacy/page_client-reference-manifest.js +1 -1
- package/.next/server/app/privacy.html +1 -1
- package/.next/server/app/privacy.rsc +1 -1
- package/.next/server/app/share/[token]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/terms/page_client-reference-manifest.js +1 -1
- package/.next/server/app/terms.html +1 -1
- package/.next/server/app/terms.rsc +1 -1
- package/.next/server/app/yo/page.js +2 -2
- package/.next/server/app/yo/page_client-reference-manifest.js +1 -1
- package/.next/server/app/yo.html +1 -1
- package/.next/server/app/yo.rsc +3 -3
- package/.next/server/app-paths-manifest.json +8 -8
- package/.next/server/functions-config-manifest.json +2 -2
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/approve/[actionId]/page-c862645e19371cea.js +1 -0
- package/.next/static/chunks/app/{page-16dfcd1c7cc88bcc.js → page-9cd218b297b9c7bf.js} +1 -1
- package/.next/static/chunks/app/yo/page-fceb03605805cb44.js +1 -0
- package/.next/trace +28 -28
- package/README.md +1 -1
- package/docs/AGENT_SETUP.md +1 -1
- package/docs/AUTH.md +1 -1
- package/docs/CLI.md +0 -3
- package/docs/MCP.md +14 -7
- package/docs/SKILLS.md +1 -1
- package/docs/api/secrets/credentials.md +9 -0
- package/docs/external/HOW_TO_AURAMAXX/WORKING_WITH_SECRETS.md +2 -1
- package/docs/external/POLICY.md +2 -2
- package/package.json +1 -1
- package/public/opengraph.webp +0 -0
- package/skills/auramaxx/HEARTBEAT.md +3 -0
- package/skills/auramaxx/SKILL.md +13 -31
- package/skills/auramaxx/docs/AGENT_SETUP.md +1 -1
- package/src/app/UnlockPageClient.tsx +10 -10
- package/src/app/api/update/route.ts +9 -10
- package/src/app/approve/[actionId]/page.tsx +2 -1
- package/src/app/page.tsx +9 -0
- package/src/app/yo/layout.tsx +9 -0
- package/src/app/yo/page.tsx +1 -1
- package/src/components/layout/SettingsDrawer.tsx +1 -1
- package/src/server/cli/commands/agent.ts +75 -18
- package/src/server/cli/commands/init.ts +9 -9
- package/src/server/cli/commands/skill.ts +5 -2
- package/src/server/cli/lib/credential-resolve.ts +20 -1
- package/src/server/cli/lib/local-agent-trust.ts +4 -3
- package/src/server/lib/agent-profiles.ts +4 -3
- package/src/server/lib/update-check.ts +4 -0
- package/src/server/mcp/server.ts +88 -19
- package/src/server/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/server/routes/actions.ts +2 -1
- package/src/server/routes/credentials.ts +48 -5
- package/src/server/tests/cli/agent-auth.test.ts +190 -0
- package/src/server/tests/cli/local-agent-trust.test.ts +11 -6
- package/src/server/tests/endpoints/credentials.test.ts +40 -18
- package/src/server/tests/endpoints/escalation-migration-gate.test.ts +1 -1
- package/src/server/tests/lib/agent-profiles.test.ts +6 -0
- package/src/server/tests/lib/update-check.test.ts +9 -1
- package/src/server/tests/mcp/server.test.ts +142 -0
- package/src/server/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/app/approve/[actionId]/page-2acca1f490424f21.js +0 -1
- package/.next/static/chunks/app/yo/page-719dc5f213fdfb30.js +0 -1
- /package/.next/static/{WshFGr6RxGYP6AbWuT9OG → 7S943hv6A_w-7tx0a47LZ}/_buildManifest.js +0 -0
- /package/.next/static/{WshFGr6RxGYP6AbWuT9OG → 7S943hv6A_w-7tx0a47LZ}/_ssgManifest.js +0 -0
|
@@ -23,6 +23,15 @@ function hasClaimSecret(init: RequestInit | undefined, expected: string): boolea
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function makeTestAgentToken(permissions: string[]): string {
|
|
27
|
+
const payload = Buffer.from(JSON.stringify({
|
|
28
|
+
agentId: 'cli-agent',
|
|
29
|
+
permissions,
|
|
30
|
+
exp: Date.now() + 60_000,
|
|
31
|
+
}), 'utf8').toString('base64url');
|
|
32
|
+
return `${payload}.sig`;
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
const mocks = vi.hoisted(() => ({
|
|
27
36
|
bootstrapViaSocket: vi.fn(),
|
|
28
37
|
bootstrapViaAuthRequest: vi.fn(),
|
|
@@ -204,6 +213,82 @@ describe('agent CLI auth behavior', () => {
|
|
|
204
213
|
expect(mocks.bootstrapViaAuthRequest).not.toHaveBeenCalled();
|
|
205
214
|
});
|
|
206
215
|
|
|
216
|
+
it('inject honors --field by requesting and injecting that exact field', async () => {
|
|
217
|
+
mocks.bootstrapViaSocket.mockResolvedValueOnce('socket-token');
|
|
218
|
+
mocks.createReadToken.mockResolvedValueOnce('read-token');
|
|
219
|
+
mocks.decryptWithPrivateKey.mockImplementation((v: string) => v);
|
|
220
|
+
|
|
221
|
+
const readRequestBodies: Array<{ requestedFields?: string[] }> = [];
|
|
222
|
+
|
|
223
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
224
|
+
const url = new URL(typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url);
|
|
225
|
+
const method = init?.method || 'GET';
|
|
226
|
+
|
|
227
|
+
if (url.pathname === '/credentials' && method === 'GET' && url.searchParams.get('q') === 'visa_7890') {
|
|
228
|
+
return new Response(JSON.stringify({
|
|
229
|
+
credentials: [{
|
|
230
|
+
id: 'cred-card',
|
|
231
|
+
name: 'visa_7890',
|
|
232
|
+
type: 'card',
|
|
233
|
+
agentId: 'primary',
|
|
234
|
+
meta: {},
|
|
235
|
+
}],
|
|
236
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (url.pathname === '/setup/agents' && method === 'GET') {
|
|
240
|
+
return new Response(JSON.stringify({
|
|
241
|
+
agents: [{ id: 'primary', name: 'primary', isPrimary: true }],
|
|
242
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (url.pathname === '/setup' && method === 'GET') {
|
|
246
|
+
return new Response(JSON.stringify({ projectScopeMode: 'auto' }), {
|
|
247
|
+
status: 200,
|
|
248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (url.pathname === '/credentials/cred-card/read' && method === 'POST') {
|
|
253
|
+
if (typeof init?.body === 'string') {
|
|
254
|
+
readRequestBodies.push(JSON.parse(init.body) as { requestedFields?: string[] });
|
|
255
|
+
} else {
|
|
256
|
+
readRequestBodies.push({});
|
|
257
|
+
}
|
|
258
|
+
return new Response(JSON.stringify({
|
|
259
|
+
encrypted: JSON.stringify({
|
|
260
|
+
id: 'cred-card',
|
|
261
|
+
agentId: 'primary',
|
|
262
|
+
type: 'card',
|
|
263
|
+
fields: [
|
|
264
|
+
{ key: 'number', value: '1234567890', sensitive: true },
|
|
265
|
+
{ key: 'cvv', value: '123', sensitive: true },
|
|
266
|
+
],
|
|
267
|
+
}),
|
|
268
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
throw new Error(`Unexpected fetch path: ${method} ${url.pathname}${url.search}`);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const exitCode = await runAgentCli([
|
|
275
|
+
'inject',
|
|
276
|
+
'visa_7890',
|
|
277
|
+
'--field',
|
|
278
|
+
'cvv',
|
|
279
|
+
'--env',
|
|
280
|
+
'VISA_CVV',
|
|
281
|
+
'--',
|
|
282
|
+
'node',
|
|
283
|
+
'-e',
|
|
284
|
+
"process.exit(process.env.VISA_CVV === '123' ? 0 : 9)",
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
expect(exitCode).toBe(0);
|
|
288
|
+
expect(readRequestBodies).toHaveLength(1);
|
|
289
|
+
expect(readRequestBodies[0]?.requestedFields).toEqual(['cvv']);
|
|
290
|
+
});
|
|
291
|
+
|
|
207
292
|
it('list supports --agent filter by name or id', async () => {
|
|
208
293
|
mocks.bootstrapViaSocket.mockResolvedValue('socket-token');
|
|
209
294
|
|
|
@@ -1234,6 +1319,44 @@ describe('agent CLI auth behavior', () => {
|
|
|
1234
1319
|
expect(getApprovalContext('req-rejected')).not.toBeNull();
|
|
1235
1320
|
});
|
|
1236
1321
|
|
|
1322
|
+
it('auth claim maps 200 rejected status to claim_rejected and clears context', async () => {
|
|
1323
|
+
process.env.WALLET_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'auramaxx-agent-auth-'));
|
|
1324
|
+
putApprovalContext({
|
|
1325
|
+
reqId: 'req-rejected-status',
|
|
1326
|
+
secret: 'sec-rejected-status',
|
|
1327
|
+
privateKeyPem: 'mock-private-key',
|
|
1328
|
+
approvalScope: 'session_token',
|
|
1329
|
+
ttlSeconds: 300,
|
|
1330
|
+
retryCommandTemplate: 'npx auramaxx agent get "github" --reqId <reqId>',
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1334
|
+
const url = new URL(typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url);
|
|
1335
|
+
const method = init?.method || 'GET';
|
|
1336
|
+
if (url.pathname === '/auth/req-rejected-status' && method === 'GET' && hasClaimSecret(init, 'sec-rejected-status')) {
|
|
1337
|
+
return new Response(JSON.stringify({ success: true, status: 'rejected' }), {
|
|
1338
|
+
status: 200,
|
|
1339
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
throw new Error(`Unexpected fetch path: ${method} ${url.pathname}${url.search}`);
|
|
1343
|
+
});
|
|
1344
|
+
|
|
1345
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
1346
|
+
const exitCode = await runAuthCli(['claim', 'req-rejected-status', '--json']);
|
|
1347
|
+
expect(exitCode).toBe(1);
|
|
1348
|
+
|
|
1349
|
+
const payload = JSON.parse(String(logSpy.mock.calls[0][0])) as {
|
|
1350
|
+
claimStatus?: string;
|
|
1351
|
+
errorCode?: string;
|
|
1352
|
+
retryReady?: boolean;
|
|
1353
|
+
};
|
|
1354
|
+
expect(payload.claimStatus).toBe('rejected');
|
|
1355
|
+
expect(payload.errorCode).toBe('claim_rejected');
|
|
1356
|
+
expect(payload.retryReady).toBe(false);
|
|
1357
|
+
expect(getApprovalContext('req-rejected-status')).toBeNull();
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1237
1360
|
it('auth request --help exits without creating an auth request', async () => {
|
|
1238
1361
|
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
|
|
1239
1362
|
throw new Error('fetch should not run for --help');
|
|
@@ -1489,4 +1612,71 @@ describe('agent CLI auth behavior', () => {
|
|
|
1489
1612
|
expect(mocks.createReadToken.mock.calls[0]?.[1]).toBe('fallback-auth-token');
|
|
1490
1613
|
expect(logSpy).toHaveBeenCalledWith('enc(gh-secret)');
|
|
1491
1614
|
});
|
|
1615
|
+
|
|
1616
|
+
it('skips delegated read token mint when fallback token is parseable non-admin', async () => {
|
|
1617
|
+
const nonAdminToken = makeTestAgentToken(['secret:read']);
|
|
1618
|
+
mocks.bootstrapViaSocket.mockRejectedValueOnce(new Error('Cannot connect to AuraMaxx: connect ENOENT /tmp/aura-cli.sock'));
|
|
1619
|
+
mocks.bootstrapViaAuthRequest.mockResolvedValueOnce(nonAdminToken);
|
|
1620
|
+
mocks.generateEphemeralKeypair.mockReturnValueOnce({
|
|
1621
|
+
publicKeyPem: 'ephemeral-public-key',
|
|
1622
|
+
privateKeyPem: 'ephemeral-private-key',
|
|
1623
|
+
publicKeyBase64: Buffer.from('ephemeral-public-key', 'utf8').toString('base64'),
|
|
1624
|
+
});
|
|
1625
|
+
mocks.decryptWithPrivateKey.mockImplementation((encrypted: string, privateKeyPem: string) => {
|
|
1626
|
+
expect(privateKeyPem).toBe('ephemeral-private-key');
|
|
1627
|
+
return encrypted;
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
1631
|
+
const url = new URL(typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url);
|
|
1632
|
+
const method = init?.method || 'GET';
|
|
1633
|
+
|
|
1634
|
+
if (url.pathname === '/credentials' && method === 'GET' && url.searchParams.get('q') === 'github') {
|
|
1635
|
+
return new Response(JSON.stringify({
|
|
1636
|
+
credentials: [{
|
|
1637
|
+
id: 'cred-github',
|
|
1638
|
+
name: 'github',
|
|
1639
|
+
type: 'note',
|
|
1640
|
+
agentId: 'primary',
|
|
1641
|
+
meta: {},
|
|
1642
|
+
}],
|
|
1643
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
if (url.pathname === '/setup/agents' && method === 'GET') {
|
|
1647
|
+
return new Response(JSON.stringify({
|
|
1648
|
+
agents: [{ id: 'primary', name: 'primary', isPrimary: true }],
|
|
1649
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (url.pathname === '/setup' && method === 'GET') {
|
|
1653
|
+
return new Response(JSON.stringify({ projectScopeMode: 'auto' }), {
|
|
1654
|
+
status: 200,
|
|
1655
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
if (url.pathname === '/credentials/cred-github/read' && method === 'POST') {
|
|
1660
|
+
const headers = init?.headers as Record<string, string>;
|
|
1661
|
+
expect(headers.Authorization).toBe(`Bearer ${nonAdminToken}`);
|
|
1662
|
+
return new Response(JSON.stringify({
|
|
1663
|
+
encrypted: JSON.stringify({
|
|
1664
|
+
id: 'cred-github',
|
|
1665
|
+
agentId: 'primary',
|
|
1666
|
+
type: 'note',
|
|
1667
|
+
fields: [{ key: 'value', value: 'gh-secret', sensitive: true }],
|
|
1668
|
+
}),
|
|
1669
|
+
}), { status: 200, headers: { 'Content-Type': 'application/json' } });
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
throw new Error(`Unexpected fetch path: ${method} ${url.pathname}${url.search}`);
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
1676
|
+
const exitCode = await runAgentCli(['get', 'github']);
|
|
1677
|
+
|
|
1678
|
+
expect(exitCode).toBe(0);
|
|
1679
|
+
expect(mocks.createReadToken).not.toHaveBeenCalled();
|
|
1680
|
+
expect(logSpy).toHaveBeenCalledWith('enc(gh-secret)');
|
|
1681
|
+
});
|
|
1492
1682
|
});
|
|
@@ -19,15 +19,20 @@ describe('local agent trust helpers', () => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('resolves mode selection for numbered and named inputs', () => {
|
|
22
|
-
expect(resolveLocalAgentModeChoice('')).toBe('
|
|
23
|
-
expect(resolveLocalAgentModeChoice('1')).toBe('
|
|
22
|
+
expect(resolveLocalAgentModeChoice('')).toBe('admin');
|
|
23
|
+
expect(resolveLocalAgentModeChoice('1')).toBe('admin');
|
|
24
|
+
expect(resolveLocalAgentModeChoice('maxx')).toBe('admin');
|
|
25
|
+
expect(resolveLocalAgentModeChoice('work')).toBe('admin');
|
|
26
|
+
expect(resolveLocalAgentModeChoice('admin')).toBe('admin');
|
|
27
|
+
|
|
28
|
+
expect(resolveLocalAgentModeChoice('2')).toBe('dev');
|
|
29
|
+
expect(resolveLocalAgentModeChoice('mid')).toBe('dev');
|
|
24
30
|
expect(resolveLocalAgentModeChoice('dev')).toBe('dev');
|
|
25
31
|
|
|
26
|
-
expect(resolveLocalAgentModeChoice('
|
|
32
|
+
expect(resolveLocalAgentModeChoice('3')).toBe('strict');
|
|
33
|
+
expect(resolveLocalAgentModeChoice('sus')).toBe('strict');
|
|
34
|
+
expect(resolveLocalAgentModeChoice('local')).toBe('strict');
|
|
27
35
|
expect(resolveLocalAgentModeChoice('strict')).toBe('strict');
|
|
28
|
-
|
|
29
|
-
expect(resolveLocalAgentModeChoice('3')).toBe('admin');
|
|
30
|
-
expect(resolveLocalAgentModeChoice('admin')).toBe('admin');
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
it('derives auto-approve defaults per profile mode', () => {
|
|
@@ -329,7 +329,7 @@ describe('Credential Endpoints', () => {
|
|
|
329
329
|
expect(getDeletedRes.status).toBe(404);
|
|
330
330
|
});
|
|
331
331
|
|
|
332
|
-
it('should request human approval when
|
|
332
|
+
it('should only request human approval when excluded fields are explicitly requested', async () => {
|
|
333
333
|
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
334
334
|
modulusLength: 2048,
|
|
335
335
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
@@ -391,21 +391,39 @@ describe('Credential Endpoints', () => {
|
|
|
391
391
|
.post(`/credentials/${loginId}/read`)
|
|
392
392
|
.set('Authorization', `Bearer ${token}`)
|
|
393
393
|
.send({});
|
|
394
|
-
expect(loginRead.status).toBe(
|
|
395
|
-
|
|
396
|
-
expect(
|
|
397
|
-
|
|
394
|
+
expect(loginRead.status).toBe(200);
|
|
395
|
+
const loginDecrypted = decryptCredentialPayload(loginRead.body.encrypted, privateKey);
|
|
396
|
+
expect(loginDecrypted.fields.map((field) => field.key)).not.toContain('password');
|
|
397
|
+
expect(loginDecrypted.fields.map((field) => field.key)).toContain('notes');
|
|
398
|
+
|
|
399
|
+
const cardRead = await request(app)
|
|
400
|
+
.post(`/credentials/${cardId}/read`)
|
|
401
|
+
.set('Authorization', `Bearer ${token}`)
|
|
402
|
+
.send({});
|
|
403
|
+
expect(cardRead.status).toBe(200);
|
|
404
|
+
const cardDecrypted = decryptCredentialPayload(cardRead.body.encrypted, privateKey);
|
|
405
|
+
expect(cardDecrypted.fields.map((field) => field.key)).toContain('number');
|
|
406
|
+
expect(cardDecrypted.fields.map((field) => field.key)).not.toContain('cvv');
|
|
407
|
+
|
|
408
|
+
const loginExplicitRead = await request(app)
|
|
409
|
+
.post(`/credentials/${loginId}/read`)
|
|
410
|
+
.set('Authorization', `Bearer ${token}`)
|
|
411
|
+
.send({ requestedFields: ['password'] });
|
|
412
|
+
expect(loginExplicitRead.status).toBe(403);
|
|
413
|
+
expect(loginExplicitRead.body.reasonCode).toBe('DENY_EXCLUDED_FIELD');
|
|
414
|
+
expect(loginExplicitRead.body.requiresHumanApproval).toBe(true);
|
|
415
|
+
const loginReqId = (loginExplicitRead.body.reqId ?? loginExplicitRead.body.requestId) as string | undefined;
|
|
398
416
|
expect(typeof loginReqId).toBe('string');
|
|
399
|
-
expect(typeof
|
|
417
|
+
expect(typeof loginExplicitRead.body.secret).toBe('string');
|
|
400
418
|
const expectedPollUrl = `http://127.0.0.1:4242/auth/${encodeURIComponent(loginReqId as string)}`;
|
|
401
|
-
expect(
|
|
402
|
-
expect(
|
|
419
|
+
expect(loginExplicitRead.body.pollUrl).toBe(expectedPollUrl);
|
|
420
|
+
expect(loginExplicitRead.body.claim).toMatchObject({
|
|
403
421
|
method: 'GET',
|
|
404
422
|
endpoint: `/auth/${encodeURIComponent(loginReqId as string)}`,
|
|
405
423
|
});
|
|
406
|
-
expect(
|
|
407
|
-
expect(
|
|
408
|
-
expect(
|
|
424
|
+
expect(loginExplicitRead.body.approvalFlow?.mode).toBe('one_time_scoped_read');
|
|
425
|
+
expect(loginExplicitRead.body.requestedFields).toEqual(expect.arrayContaining(['password']));
|
|
426
|
+
expect(loginExplicitRead.body.effectiveExcludeFields).toEqual(expect.arrayContaining(['password']));
|
|
409
427
|
|
|
410
428
|
const loginApproval = await testPrisma.humanAction.findUnique({
|
|
411
429
|
where: { id: loginReqId as string },
|
|
@@ -426,13 +444,13 @@ describe('Credential Endpoints', () => {
|
|
|
426
444
|
expect(loginApprovalMeta.credentialAccess?.maxReads).toBe(1);
|
|
427
445
|
expect(loginApprovalMeta.credentialAccess?.ttl).toBe(300);
|
|
428
446
|
|
|
429
|
-
const
|
|
447
|
+
const cardExplicitRead = await request(app)
|
|
430
448
|
.post(`/credentials/${cardId}/read`)
|
|
431
449
|
.set('Authorization', `Bearer ${token}`)
|
|
432
|
-
.send({});
|
|
433
|
-
expect(
|
|
434
|
-
expect(
|
|
435
|
-
expect(
|
|
450
|
+
.send({ requestedFields: ['cvv'] });
|
|
451
|
+
expect(cardExplicitRead.status).toBe(403);
|
|
452
|
+
expect(cardExplicitRead.body.reasonCode).toBe('DENY_EXCLUDED_FIELD');
|
|
453
|
+
expect(cardExplicitRead.body.requestedFields).toEqual(expect.arrayContaining(['cvv']));
|
|
436
454
|
});
|
|
437
455
|
|
|
438
456
|
it('should issue scoped temporary read access after excluded-field approval', async () => {
|
|
@@ -487,7 +505,7 @@ describe('Credential Endpoints', () => {
|
|
|
487
505
|
const deniedRead = await request(app)
|
|
488
506
|
.post(`/credentials/${credentialId}/read`)
|
|
489
507
|
.set('Authorization', `Bearer ${token}`)
|
|
490
|
-
.send({});
|
|
508
|
+
.send({ requestedFields: ['password'] });
|
|
491
509
|
expect(deniedRead.status).toBe(403);
|
|
492
510
|
expect(deniedRead.body.reasonCode).toBe('DENY_EXCLUDED_FIELD');
|
|
493
511
|
expect(deniedRead.body.routeId).toBe(ESCALATION_ROUTE_IDS.CREDENTIALS_READ_EXCLUDED_FIELD);
|
|
@@ -594,6 +612,7 @@ describe('Credential Endpoints', () => {
|
|
|
594
612
|
.post(`/credentials/${credentialId}/read`)
|
|
595
613
|
.set('Authorization', `Bearer ${token}`)
|
|
596
614
|
.send({
|
|
615
|
+
requestedFields: ['password'],
|
|
597
616
|
requestedPolicySource: 'derived_403',
|
|
598
617
|
requestedPolicy: {
|
|
599
618
|
permissions: ['admin:*'],
|
|
@@ -642,6 +661,7 @@ describe('Credential Endpoints', () => {
|
|
|
642
661
|
.post(`/credentials/${credentialId}/read`)
|
|
643
662
|
.set('Authorization', `Bearer ${token}`)
|
|
644
663
|
.send({
|
|
664
|
+
requestedFields: ['password'],
|
|
645
665
|
requestedPolicySource: 'derived_403',
|
|
646
666
|
requestedPolicy: 'bad-value',
|
|
647
667
|
});
|
|
@@ -688,6 +708,7 @@ describe('Credential Endpoints', () => {
|
|
|
688
708
|
.post(`/credentials/${credentialId}/read`)
|
|
689
709
|
.set('Authorization', `Bearer ${token}`)
|
|
690
710
|
.send({
|
|
711
|
+
requestedFields: ['password'],
|
|
691
712
|
requestedPolicySource: 'agent',
|
|
692
713
|
requestedPolicy: {
|
|
693
714
|
permissions: ['secret:read'],
|
|
@@ -711,6 +732,7 @@ describe('Credential Endpoints', () => {
|
|
|
711
732
|
.post(`/credentials/${credentialId}/read`)
|
|
712
733
|
.set('Authorization', `Bearer ${token}`)
|
|
713
734
|
.send({
|
|
735
|
+
requestedFields: ['password'],
|
|
714
736
|
requestedPolicySource: 'agent',
|
|
715
737
|
requestedPolicy: {
|
|
716
738
|
permissions: ['admin:*'],
|
|
@@ -864,7 +886,7 @@ describe('Credential Endpoints', () => {
|
|
|
864
886
|
const deniedRead = await request(app)
|
|
865
887
|
.post(`/credentials/${credentialId}/read`)
|
|
866
888
|
.set('Authorization', `Bearer ${token}`)
|
|
867
|
-
.send({});
|
|
889
|
+
.send({ requestedFields: ['password'] });
|
|
868
890
|
expect(deniedRead.status).toBe(403);
|
|
869
891
|
expect(deniedRead.body.reasonCode).toBe('DENY_EXCLUDED_FIELD');
|
|
870
892
|
expect(deniedRead.body.routeId).toBe(ESCALATION_ROUTE_IDS.CREDENTIALS_READ_EXCLUDED_FIELD);
|
|
@@ -226,7 +226,7 @@ describe('Escalation rollout migration gates', () => {
|
|
|
226
226
|
const readExcludedFieldRes = await request(app)
|
|
227
227
|
.post(`/credentials/${credentialId}/read`)
|
|
228
228
|
.set('Authorization', `Bearer ${readExcludedFieldToken}`)
|
|
229
|
-
.send({});
|
|
229
|
+
.send({ requestedFields: ['password'] });
|
|
230
230
|
|
|
231
231
|
expect(readExcludedFieldRes.status).toBe(403);
|
|
232
232
|
expect(readExcludedFieldRes.body.contractVersion).toBe(ESCALATION_CONTRACT_VERSION);
|
|
@@ -53,6 +53,12 @@ describe('agent profiles resolver', () => {
|
|
|
53
53
|
expect(policy.credentialAccess.maxReads).toBe(100);
|
|
54
54
|
expect(policy.overrideDelta).toEqual(['maxReads', 'ttlSeconds']);
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
it('scopes dev profile credential access to primary + legacy alias only', () => {
|
|
58
|
+
const policy = resolveProfileToEffectivePolicy({ profileId: 'dev' });
|
|
59
|
+
expect(policy.credentialAccess.read).toEqual(['agent:agent', 'agent:primary']);
|
|
60
|
+
expect(policy.credentialAccess.write).toEqual(['agent:agent', 'agent:primary']);
|
|
61
|
+
});
|
|
56
62
|
});
|
|
57
63
|
|
|
58
64
|
describe('describeProfile', () => {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildNpxLatestCommand,
|
|
4
|
+
buildUpdateCommand,
|
|
5
|
+
buildUpdateFallbackCommand,
|
|
6
|
+
buildUpdateForceCommand,
|
|
7
|
+
buildVersionInfo,
|
|
8
|
+
isNewerVersion,
|
|
9
|
+
} from '../../lib/update-check';
|
|
3
10
|
|
|
4
11
|
describe('update-check helpers', () => {
|
|
5
12
|
it('detects newer versions', () => {
|
|
@@ -12,6 +19,7 @@ describe('update-check helpers', () => {
|
|
|
12
19
|
const info = buildVersionInfo('1.0.0', '1.1.0');
|
|
13
20
|
expect(info.updateAvailable).toBe(true);
|
|
14
21
|
expect(buildUpdateCommand()).toBe('npm install -g auramaxx --foreground-scripts');
|
|
22
|
+
expect(buildUpdateForceCommand()).toBe('npm install -g auramaxx --foreground-scripts --force');
|
|
15
23
|
expect(buildNpxLatestCommand()).toBe('npx --yes auramaxx@latest');
|
|
16
24
|
expect(buildNpxLatestCommand('auramaxx', ['restart'])).toBe('npx --yes auramaxx@latest restart');
|
|
17
25
|
expect(buildUpdateFallbackCommand()).toBe('npx --yes auramaxx@latest start');
|
|
@@ -2844,6 +2844,148 @@ describe('MCP Server — Tool Registration', () => {
|
|
|
2844
2844
|
vi.doUnmock('@modelcontextprotocol/sdk/server/stdio.js');
|
|
2845
2845
|
});
|
|
2846
2846
|
|
|
2847
|
+
it('get_secret and inject_secret should enforce explicit field reads without primary fallback', async () => {
|
|
2848
|
+
const capturedHandlers: Array<{
|
|
2849
|
+
name: string;
|
|
2850
|
+
handler: (input: unknown) => Promise<unknown>;
|
|
2851
|
+
}> = [];
|
|
2852
|
+
|
|
2853
|
+
vi.doMock('@modelcontextprotocol/sdk/server/mcp.js', () => ({
|
|
2854
|
+
McpServer: class {
|
|
2855
|
+
tool(name: string, _desc: string, _shape: unknown, handler: (input: unknown) => Promise<unknown>) {
|
|
2856
|
+
capturedHandlers.push({ name, handler });
|
|
2857
|
+
}
|
|
2858
|
+
resource() {}
|
|
2859
|
+
async connect() {}
|
|
2860
|
+
},
|
|
2861
|
+
}));
|
|
2862
|
+
vi.doMock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
|
|
2863
|
+
StdioServerTransport: class {},
|
|
2864
|
+
}));
|
|
2865
|
+
vi.doMock('crypto', async () => {
|
|
2866
|
+
const actual = await vi.importActual<typeof import('crypto')>('crypto');
|
|
2867
|
+
return {
|
|
2868
|
+
...actual,
|
|
2869
|
+
privateDecrypt: vi.fn((_opts: unknown, ciphertext: Buffer) => {
|
|
2870
|
+
const marker = ciphertext.toString('utf8');
|
|
2871
|
+
if (marker === 'cred-cipher') {
|
|
2872
|
+
return Buffer.from(JSON.stringify({
|
|
2873
|
+
id: 'cred-1',
|
|
2874
|
+
agentId: 'primary',
|
|
2875
|
+
type: 'card',
|
|
2876
|
+
fields: [
|
|
2877
|
+
{ key: 'number', value: '1234567890', sensitive: true },
|
|
2878
|
+
{ key: 'cvv', value: '321', sensitive: true },
|
|
2879
|
+
],
|
|
2880
|
+
}), 'utf8');
|
|
2881
|
+
}
|
|
2882
|
+
throw new Error(`Unexpected privateDecrypt input: ${marker}`);
|
|
2883
|
+
}),
|
|
2884
|
+
};
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
vi.resetModules();
|
|
2888
|
+
const originalFetch = globalThis.fetch;
|
|
2889
|
+
const originalToken = process.env.AURA_TOKEN;
|
|
2890
|
+
const originalGetEnv = process.env.AURA_VISA_7890;
|
|
2891
|
+
const originalInjectEnv = process.env.MY_SECRET;
|
|
2892
|
+
process.env.AURA_TOKEN = 'base-token';
|
|
2893
|
+
|
|
2894
|
+
const readRequestBodies: Array<{ requestedFields?: string[] }> = [];
|
|
2895
|
+
const credentialCipher = Buffer.from('cred-cipher', 'utf8').toString('base64');
|
|
2896
|
+
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
2897
|
+
const url = String(input);
|
|
2898
|
+
const method = init?.method || 'GET';
|
|
2899
|
+
|
|
2900
|
+
if (url.endsWith('/setup') && method === 'GET') {
|
|
2901
|
+
return new Response(JSON.stringify({ projectScopeMode: 'auto' }), { status: 200 });
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
if (url.endsWith('/setup/agents') && method === 'GET') {
|
|
2905
|
+
return new Response(JSON.stringify({ agents: [{ id: 'primary', name: 'primary' }] }), { status: 200 });
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
if (url.includes('/credentials?q=visa_7890') && method === 'GET') {
|
|
2909
|
+
return new Response(JSON.stringify({
|
|
2910
|
+
credentials: [{ id: 'cred-1', name: 'visa_7890', type: 'card', agentId: 'primary' }],
|
|
2911
|
+
}), { status: 200 });
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
if (url.endsWith('/credentials/cred-1/read') && method === 'POST') {
|
|
2915
|
+
if (init?.body) {
|
|
2916
|
+
readRequestBodies.push(JSON.parse(String(init.body)) as { requestedFields?: string[] });
|
|
2917
|
+
} else {
|
|
2918
|
+
readRequestBodies.push({});
|
|
2919
|
+
}
|
|
2920
|
+
return new Response(JSON.stringify({ encrypted: credentialCipher }), { status: 200 });
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
return new Response('not mocked', { status: 500 });
|
|
2924
|
+
});
|
|
2925
|
+
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
2926
|
+
|
|
2927
|
+
await import('../../mcp/server.js');
|
|
2928
|
+
const getSecret = capturedHandlers.find((tool) => tool.name === 'get_secret');
|
|
2929
|
+
const injectSecret = capturedHandlers.find((tool) => tool.name === 'inject_secret');
|
|
2930
|
+
expect(getSecret).toBeDefined();
|
|
2931
|
+
expect(injectSecret).toBeDefined();
|
|
2932
|
+
|
|
2933
|
+
const getCvv = await getSecret!.handler({
|
|
2934
|
+
name: 'visa_7890',
|
|
2935
|
+
field: 'cvv',
|
|
2936
|
+
dangerPlaintext: true,
|
|
2937
|
+
}) as { content: Array<{ text: string }> };
|
|
2938
|
+
const getCvvPayload = JSON.parse(getCvv.content[0].text) as { secret?: string };
|
|
2939
|
+
expect(getCvvPayload.secret).toBe('321');
|
|
2940
|
+
expect(process.env.AURA_VISA_7890).toBe('321');
|
|
2941
|
+
|
|
2942
|
+
const getMissing = await getSecret!.handler({
|
|
2943
|
+
name: 'visa_7890',
|
|
2944
|
+
field: 'security_code',
|
|
2945
|
+
}) as { content: Array<{ text: string }> };
|
|
2946
|
+
const getMissingPayload = JSON.parse(getMissing.content[0].text) as { error?: string; availableFields?: string };
|
|
2947
|
+
expect(getMissingPayload.error).toContain('Field "security_code" not found on credential "visa_7890"');
|
|
2948
|
+
expect(getMissingPayload.availableFields).toContain('number');
|
|
2949
|
+
expect(getMissingPayload.availableFields).toContain('cvv');
|
|
2950
|
+
|
|
2951
|
+
const injectCvv = await injectSecret!.handler({
|
|
2952
|
+
name: 'visa_7890',
|
|
2953
|
+
field: 'cvv',
|
|
2954
|
+
envVar: 'MY_SECRET',
|
|
2955
|
+
dangerPlaintext: true,
|
|
2956
|
+
}) as { content: Array<{ text: string }> };
|
|
2957
|
+
const injectCvvPayload = JSON.parse(injectCvv.content[0].text) as { secret?: string };
|
|
2958
|
+
expect(injectCvvPayload.secret).toBe('321');
|
|
2959
|
+
expect(process.env.MY_SECRET).toBe('321');
|
|
2960
|
+
|
|
2961
|
+
const injectMissing = await injectSecret!.handler({
|
|
2962
|
+
name: 'visa_7890',
|
|
2963
|
+
field: 'security_code',
|
|
2964
|
+
envVar: 'MY_SECRET',
|
|
2965
|
+
}) as { content: Array<{ text: string }> };
|
|
2966
|
+
const injectMissingPayload = JSON.parse(injectMissing.content[0].text) as { error?: string; availableFields?: string };
|
|
2967
|
+
expect(injectMissingPayload.error).toContain('Field "security_code" not found on credential "visa_7890"');
|
|
2968
|
+
expect(injectMissingPayload.availableFields).toContain('number');
|
|
2969
|
+
expect(injectMissingPayload.availableFields).toContain('cvv');
|
|
2970
|
+
|
|
2971
|
+
expect(readRequestBodies).toEqual([
|
|
2972
|
+
{ requestedFields: ['cvv'] },
|
|
2973
|
+
{ requestedFields: ['security_code'] },
|
|
2974
|
+
{ requestedFields: ['cvv'] },
|
|
2975
|
+
{ requestedFields: ['security_code'] },
|
|
2976
|
+
]);
|
|
2977
|
+
|
|
2978
|
+
if (originalGetEnv === undefined) delete process.env.AURA_VISA_7890;
|
|
2979
|
+
else process.env.AURA_VISA_7890 = originalGetEnv;
|
|
2980
|
+
if (originalInjectEnv === undefined) delete process.env.MY_SECRET;
|
|
2981
|
+
else process.env.MY_SECRET = originalInjectEnv;
|
|
2982
|
+
globalThis.fetch = originalFetch;
|
|
2983
|
+
process.env.AURA_TOKEN = originalToken;
|
|
2984
|
+
vi.doUnmock('crypto');
|
|
2985
|
+
vi.doUnmock('@modelcontextprotocol/sdk/server/mcp.js');
|
|
2986
|
+
vi.doUnmock('@modelcontextprotocol/sdk/server/stdio.js');
|
|
2987
|
+
});
|
|
2988
|
+
|
|
2847
2989
|
it('write_diary should create a new diary note and fall back to primary when daily-logs is absent', async () => {
|
|
2848
2990
|
const capturedHandlers: Array<{
|
|
2849
2991
|
name: string;
|