agent-delegation-security.test.mjs
123 lines 4.3 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Tier 7 — SECURITY: scope denial, no existence leak, injection inert, SD-5 separation.
3 */
4 import { describe, it, beforeEach, afterEach } from 'node:test';
5 import assert from 'node:assert/strict';
6 import fs from 'node:fs';
7 import path from 'node:path';
8 import { fileURLToPath } from 'node:url';
9
10 import {
11 handleDelegationGrantMintRequest,
12 handleDelegationGrantListRequest,
13 handleDelegationConsentRevokeRequest,
14 getDelegationPolicyForbidden,
15 seedDelegationFixtures,
16 } from '../lib/agent/delegation.mjs';
17 import { getFlowExternalAgentEnabled } from '../lib/flow/external-agent.mjs';
18 import {
19 writeDelegationPolicy,
20 makeAgentIdentity,
21 makeDelegationConsent,
22 TEST_USER_ID,
23 TEST_PRINCIPAL_REF,
24 } from './fixtures/agent/delegation-helpers.mjs';
25
26 const __dirname = path.dirname(fileURLToPath(import.meta.url));
27 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-agent-delegation-security');
28
29 describe('Agent delegation — security', () => {
30 const dataDir = path.join(tmpRoot, 'data');
31 const vaultId = 'default';
32
33 beforeEach(() => {
34 fs.rmSync(tmpRoot, { recursive: true, force: true });
35 fs.mkdirSync(dataDir, { recursive: true });
36 writeDelegationPolicy(dataDir);
37 process.env.DELEGATION_ENABLED = '1';
38 const identity = makeAgentIdentity();
39 identity.label = '<script>alert(1)</script> untrusted label';
40 const consent = makeDelegationConsent();
41 seedDelegationFixtures(dataDir, vaultId, identity, consent);
42 });
43
44 afterEach(() => {
45 fs.rmSync(tmpRoot, { recursive: true, force: true });
46 delete process.env.DELEGATION_ENABLED;
47 delete process.env.DELEGATION_POLICY_FORBIDDEN;
48 });
49
50 it('unknown_consent for missing consent — opaque code', () => {
51 const result = handleDelegationGrantMintRequest({
52 dataDir,
53 vaultId,
54 consentId: 'dcons_nonexistent_xyz',
55 actorAgentId: 'agent_tutor_test01',
56 });
57 assert.equal(result.ok, false);
58 assert.equal(result.code, 'unknown_consent');
59 assert.equal(JSON.stringify(result).includes(vaultId), false);
60 });
61
62 it('task not on allowlist ⇒ DELEGATION_TASK_DENIED', () => {
63 const consent = makeDelegationConsent();
64 const identity = makeAgentIdentity();
65 const result = handleDelegationGrantMintRequest({
66 dataDir,
67 vaultId,
68 consentId: consent.consent_id,
69 actorAgentId: identity.agent_id,
70 taskRef: 'task_not_allowed',
71 });
72 assert.equal(result.ok, false);
73 assert.equal(result.code, 'DELEGATION_TASK_DENIED');
74 });
75
76 it('list/audit responses contain no bearer or secrets', () => {
77 const consent = makeDelegationConsent({ taskIds: [], flowIds: [] });
78 const identity = makeAgentIdentity();
79 handleDelegationGrantMintRequest({
80 dataDir,
81 vaultId,
82 consentId: consent.consent_id,
83 actorAgentId: identity.agent_id,
84 });
85 const list = handleDelegationGrantListRequest({ dataDir, vaultId });
86 const serialized = JSON.stringify(list.payload);
87 assert.equal(serialized.includes('bearer'), false);
88 assert.equal(serialized.includes('grant_bearer_hash'), false);
89 });
90
91 it('principal mismatch on consent revoke', () => {
92 const consent = makeDelegationConsent();
93 const result = handleDelegationConsentRevokeRequest({
94 dataDir,
95 vaultId,
96 consentId: consent.consent_id,
97 userId: 'other-user-id',
98 });
99 assert.equal(result.ok, false);
100 assert.equal(result.code, 'DELEGATION_PRINCIPAL_MISMATCH');
101 });
102
103 it('SD-5 external-agent gate independent of delegation gate', () => {
104 assert.equal(getFlowExternalAgentEnabled(dataDir), false);
105 assert.equal(getDelegationPolicyForbidden(dataDir), false);
106 process.env.DELEGATION_POLICY_FORBIDDEN = '1';
107 const mint = handleDelegationGrantMintRequest({
108 dataDir,
109 vaultId,
110 consentId: makeDelegationConsent().consent_id,
111 actorAgentId: makeAgentIdentity().agent_id,
112 });
113 assert.equal(mint.ok, false);
114 assert.equal(mint.code, 'DELEGATION_POLICY_FORBIDDEN');
115 });
116
117 it('label injection stored as data only in identity list', async () => {
118 const { handleAgentIdentityListRequest } = await import('../lib/agent/delegation.mjs');
119 const list = handleAgentIdentityListRequest({ dataDir, vaultId });
120 assert.equal(list.ok, true);
121 assert.equal(list.payload.identities[0].label.includes('<script>'), true);
122 });
123 });
File History 2 commits
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago
sha256:d8c648b20a4d53b2673c5c082ee7edfa7b2fc9b11080832da1f38807b6bf940b fix(7C-L1b): route hosted delegation proposals through cani… Human minor 30 days ago