@kya-os/mcp-i-core 1.3.12 → 1.3.14
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/dist/config/remote-config.js +9 -12
- package/dist/runtime/base.js +11 -0
- package/dist/services/access-control.service.js +5 -0
- package/dist/services/tool-protection.service.js +17 -8
- package/package.json +2 -2
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test$colon$coverage.log +0 -4586
- package/.turbo/turbo-test.log +0 -3169
- package/COMPLIANCE_IMPROVEMENT_REPORT.md +0 -483
- package/Composer 3.md +0 -615
- package/GPT-5.md +0 -1169
- package/OPUS-plan.md +0 -352
- package/PHASE_3_AND_4.1_SUMMARY.md +0 -585
- package/PHASE_3_SUMMARY.md +0 -317
- package/PHASE_4.1.3_SUMMARY.md +0 -428
- package/PHASE_4.1_COMPLETE.md +0 -525
- package/PHASE_4_USER_DID_IDENTITY_LINKING_PLAN.md +0 -1240
- package/SCHEMA_COMPLIANCE_REPORT.md +0 -275
- package/TEST_PLAN.md +0 -571
- package/coverage/coverage-final.json +0 -60
- package/dist/cache/oauth-config-cache.d.ts.map +0 -1
- package/dist/cache/oauth-config-cache.js.map +0 -1
- package/dist/cache/tool-protection-cache.d.ts.map +0 -1
- package/dist/cache/tool-protection-cache.js.map +0 -1
- package/dist/compliance/index.d.ts.map +0 -1
- package/dist/compliance/index.js.map +0 -1
- package/dist/compliance/schema-registry.d.ts.map +0 -1
- package/dist/compliance/schema-registry.js.map +0 -1
- package/dist/compliance/schema-verifier.d.ts.map +0 -1
- package/dist/compliance/schema-verifier.js.map +0 -1
- package/dist/config/remote-config.d.ts.map +0 -1
- package/dist/config/remote-config.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/delegation/audience-validator.d.ts.map +0 -1
- package/dist/delegation/audience-validator.js.map +0 -1
- package/dist/delegation/bitstring.d.ts.map +0 -1
- package/dist/delegation/bitstring.js.map +0 -1
- package/dist/delegation/cascading-revocation.d.ts.map +0 -1
- package/dist/delegation/cascading-revocation.js.map +0 -1
- package/dist/delegation/delegation-graph.d.ts.map +0 -1
- package/dist/delegation/delegation-graph.js.map +0 -1
- package/dist/delegation/did-key-resolver.d.ts.map +0 -1
- package/dist/delegation/did-key-resolver.js.map +0 -1
- package/dist/delegation/index.d.ts.map +0 -1
- package/dist/delegation/index.js.map +0 -1
- package/dist/delegation/statuslist-manager.d.ts.map +0 -1
- package/dist/delegation/statuslist-manager.js.map +0 -1
- package/dist/delegation/storage/index.d.ts.map +0 -1
- package/dist/delegation/storage/index.js.map +0 -1
- package/dist/delegation/storage/memory-graph-storage.d.ts.map +0 -1
- package/dist/delegation/storage/memory-graph-storage.js.map +0 -1
- package/dist/delegation/storage/memory-statuslist-storage.d.ts.map +0 -1
- package/dist/delegation/storage/memory-statuslist-storage.js.map +0 -1
- package/dist/delegation/utils.d.ts.map +0 -1
- package/dist/delegation/utils.js.map +0 -1
- package/dist/delegation/vc-issuer.d.ts.map +0 -1
- package/dist/delegation/vc-issuer.js.map +0 -1
- package/dist/delegation/vc-verifier.d.ts.map +0 -1
- package/dist/delegation/vc-verifier.js.map +0 -1
- package/dist/identity/idp-token-resolver.d.ts.map +0 -1
- package/dist/identity/idp-token-resolver.js.map +0 -1
- package/dist/identity/idp-token-storage.interface.d.ts.map +0 -1
- package/dist/identity/idp-token-storage.interface.js.map +0 -1
- package/dist/identity/user-did-manager.d.ts.map +0 -1
- package/dist/identity/user-did-manager.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers/base.d.ts.map +0 -1
- package/dist/providers/base.js.map +0 -1
- package/dist/providers/memory.d.ts.map +0 -1
- package/dist/providers/memory.js.map +0 -1
- package/dist/runtime/audit-logger.d.ts.map +0 -1
- package/dist/runtime/audit-logger.js.map +0 -1
- package/dist/runtime/base.d.ts.map +0 -1
- package/dist/runtime/base.js.map +0 -1
- package/dist/services/access-control.service.d.ts.map +0 -1
- package/dist/services/access-control.service.js.map +0 -1
- package/dist/services/authorization/authorization-registry.d.ts.map +0 -1
- package/dist/services/authorization/authorization-registry.js.map +0 -1
- package/dist/services/authorization/types.d.ts.map +0 -1
- package/dist/services/authorization/types.js.map +0 -1
- package/dist/services/batch-delegation.service.d.ts.map +0 -1
- package/dist/services/batch-delegation.service.js.map +0 -1
- package/dist/services/crypto.service.d.ts.map +0 -1
- package/dist/services/crypto.service.js.map +0 -1
- package/dist/services/errors.d.ts.map +0 -1
- package/dist/services/errors.js.map +0 -1
- package/dist/services/index.d.ts.map +0 -1
- package/dist/services/index.js.map +0 -1
- package/dist/services/oauth-config.service.d.ts.map +0 -1
- package/dist/services/oauth-config.service.js.map +0 -1
- package/dist/services/oauth-provider-registry.d.ts.map +0 -1
- package/dist/services/oauth-provider-registry.js.map +0 -1
- package/dist/services/oauth-service.d.ts.map +0 -1
- package/dist/services/oauth-service.js.map +0 -1
- package/dist/services/oauth-token-retrieval.service.d.ts.map +0 -1
- package/dist/services/oauth-token-retrieval.service.js.map +0 -1
- package/dist/services/proof-verifier.d.ts.map +0 -1
- package/dist/services/proof-verifier.js.map +0 -1
- package/dist/services/provider-resolver.d.ts.map +0 -1
- package/dist/services/provider-resolver.js.map +0 -1
- package/dist/services/provider-validator.d.ts.map +0 -1
- package/dist/services/provider-validator.js.map +0 -1
- package/dist/services/session-registration.service.d.ts.map +0 -1
- package/dist/services/session-registration.service.js.map +0 -1
- package/dist/services/storage.service.d.ts.map +0 -1
- package/dist/services/storage.service.js.map +0 -1
- package/dist/services/tool-context-builder.d.ts.map +0 -1
- package/dist/services/tool-context-builder.js.map +0 -1
- package/dist/services/tool-protection.service.d.ts.map +0 -1
- package/dist/services/tool-protection.service.js.map +0 -1
- package/dist/types/oauth-required-error.d.ts.map +0 -1
- package/dist/types/oauth-required-error.js.map +0 -1
- package/dist/types/tool-protection.d.ts.map +0 -1
- package/dist/types/tool-protection.js.map +0 -1
- package/dist/utils/base58.d.ts.map +0 -1
- package/dist/utils/base58.js.map +0 -1
- package/dist/utils/base64.d.ts.map +0 -1
- package/dist/utils/base64.js.map +0 -1
- package/dist/utils/cors.d.ts.map +0 -1
- package/dist/utils/cors.js.map +0 -1
- package/dist/utils/did-helpers.d.ts.map +0 -1
- package/dist/utils/did-helpers.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/storage-keys.d.ts.map +0 -1
- package/dist/utils/storage-keys.js.map +0 -1
- package/docs/API_REFERENCE.md +0 -1362
- package/docs/COMPLIANCE_MATRIX.md +0 -691
- package/docs/STATUSLIST2021_GUIDE.md +0 -696
- package/docs/W3C_VC_DELEGATION_GUIDE.md +0 -710
- package/src/__tests__/cache/tool-protection-cache.test.ts +0 -640
- package/src/__tests__/config/provider-runtime-config.test.ts +0 -309
- package/src/__tests__/delegation-e2e.test.ts +0 -690
- package/src/__tests__/identity/user-did-manager.test.ts +0 -232
- package/src/__tests__/index.test.ts +0 -56
- package/src/__tests__/integration/full-flow.test.ts +0 -789
- package/src/__tests__/integration.test.ts +0 -281
- package/src/__tests__/providers/base.test.ts +0 -173
- package/src/__tests__/providers/memory.test.ts +0 -319
- package/src/__tests__/regression/phase2-regression.test.ts +0 -429
- package/src/__tests__/runtime/audit-logger.test.ts +0 -154
- package/src/__tests__/runtime/base-extensions.test.ts +0 -595
- package/src/__tests__/runtime/base.test.ts +0 -869
- package/src/__tests__/runtime/delegation-flow.test.ts +0 -164
- package/src/__tests__/runtime/proof-client-did.test.ts +0 -376
- package/src/__tests__/runtime/route-interception.test.ts +0 -686
- package/src/__tests__/runtime/tool-protection-enforcement.test.ts +0 -908
- package/src/__tests__/services/agentshield-integration.test.ts +0 -791
- package/src/__tests__/services/cache-busting.test.ts +0 -125
- package/src/__tests__/services/oauth-service-pkce.test.ts +0 -556
- package/src/__tests__/services/provider-resolver-edge-cases.test.ts +0 -591
- package/src/__tests__/services/tool-protection-merged-config.test.ts +0 -485
- package/src/__tests__/services/tool-protection-oauth-provider.test.ts +0 -480
- package/src/__tests__/services/tool-protection.service.test.ts +0 -1373
- package/src/__tests__/utils/mock-providers.ts +0 -340
- package/src/cache/oauth-config-cache.d.ts +0 -69
- package/src/cache/oauth-config-cache.d.ts.map +0 -1
- package/src/cache/oauth-config-cache.js.map +0 -1
- package/src/cache/oauth-config-cache.ts +0 -123
- package/src/cache/tool-protection-cache.ts +0 -171
- package/src/compliance/EXAMPLE.md +0 -412
- package/src/compliance/__tests__/schema-verifier.test.ts +0 -797
- package/src/compliance/index.ts +0 -8
- package/src/compliance/schema-registry.ts +0 -460
- package/src/compliance/schema-verifier.ts +0 -708
- package/src/config/__tests__/merged-config.spec.ts +0 -445
- package/src/config/__tests__/remote-config.spec.ts +0 -268
- package/src/config/remote-config.ts +0 -264
- package/src/config.ts +0 -312
- package/src/delegation/__tests__/audience-validator.test.ts +0 -112
- package/src/delegation/__tests__/bitstring.test.ts +0 -346
- package/src/delegation/__tests__/cascading-revocation.test.ts +0 -628
- package/src/delegation/__tests__/delegation-graph.test.ts +0 -584
- package/src/delegation/__tests__/did-key-resolver.test.ts +0 -265
- package/src/delegation/__tests__/utils.test.ts +0 -152
- package/src/delegation/__tests__/vc-issuer.test.ts +0 -442
- package/src/delegation/__tests__/vc-verifier.test.ts +0 -922
- package/src/delegation/audience-validator.ts +0 -52
- package/src/delegation/bitstring.ts +0 -278
- package/src/delegation/cascading-revocation.ts +0 -370
- package/src/delegation/delegation-graph.ts +0 -299
- package/src/delegation/did-key-resolver.ts +0 -179
- package/src/delegation/index.ts +0 -14
- package/src/delegation/statuslist-manager.ts +0 -353
- package/src/delegation/storage/__tests__/memory-graph-storage.test.ts +0 -366
- package/src/delegation/storage/__tests__/memory-statuslist-storage.test.ts +0 -228
- package/src/delegation/storage/index.ts +0 -9
- package/src/delegation/storage/memory-graph-storage.ts +0 -178
- package/src/delegation/storage/memory-statuslist-storage.ts +0 -77
- package/src/delegation/utils.ts +0 -221
- package/src/delegation/vc-issuer.ts +0 -232
- package/src/delegation/vc-verifier.ts +0 -568
- package/src/identity/idp-token-resolver.ts +0 -181
- package/src/identity/idp-token-storage.interface.ts +0 -94
- package/src/identity/user-did-manager.ts +0 -526
- package/src/index.ts +0 -310
- package/src/providers/base.d.ts +0 -91
- package/src/providers/base.d.ts.map +0 -1
- package/src/providers/base.js.map +0 -1
- package/src/providers/base.ts +0 -96
- package/src/providers/memory.ts +0 -142
- package/src/runtime/audit-logger.ts +0 -39
- package/src/runtime/base.ts +0 -1392
- package/src/services/__tests__/access-control.integration.test.ts +0 -443
- package/src/services/__tests__/access-control.proof-response-validation.test.ts +0 -578
- package/src/services/__tests__/access-control.service.test.ts +0 -970
- package/src/services/__tests__/batch-delegation.service.test.ts +0 -351
- package/src/services/__tests__/crypto.service.test.ts +0 -531
- package/src/services/__tests__/oauth-provider-registry.test.ts +0 -142
- package/src/services/__tests__/proof-verifier.integration.test.ts +0 -485
- package/src/services/__tests__/proof-verifier.test.ts +0 -489
- package/src/services/__tests__/provider-resolution.integration.test.ts +0 -202
- package/src/services/__tests__/provider-resolver.test.ts +0 -213
- package/src/services/__tests__/storage.service.test.ts +0 -358
- package/src/services/access-control.service.ts +0 -990
- package/src/services/authorization/authorization-registry.ts +0 -66
- package/src/services/authorization/types.ts +0 -71
- package/src/services/batch-delegation.service.ts +0 -137
- package/src/services/crypto.service.ts +0 -302
- package/src/services/errors.ts +0 -76
- package/src/services/index.ts +0 -18
- package/src/services/oauth-config.service.d.ts +0 -53
- package/src/services/oauth-config.service.d.ts.map +0 -1
- package/src/services/oauth-config.service.js.map +0 -1
- package/src/services/oauth-config.service.ts +0 -192
- package/src/services/oauth-provider-registry.d.ts +0 -57
- package/src/services/oauth-provider-registry.d.ts.map +0 -1
- package/src/services/oauth-provider-registry.js.map +0 -1
- package/src/services/oauth-provider-registry.ts +0 -141
- package/src/services/oauth-service.ts +0 -544
- package/src/services/oauth-token-retrieval.service.ts +0 -245
- package/src/services/proof-verifier.ts +0 -478
- package/src/services/provider-resolver.d.ts +0 -48
- package/src/services/provider-resolver.d.ts.map +0 -1
- package/src/services/provider-resolver.js.map +0 -1
- package/src/services/provider-resolver.ts +0 -146
- package/src/services/provider-validator.ts +0 -170
- package/src/services/session-registration.service.ts +0 -251
- package/src/services/storage.service.ts +0 -566
- package/src/services/tool-context-builder.ts +0 -237
- package/src/services/tool-protection.service.ts +0 -1070
- package/src/types/oauth-required-error.ts +0 -63
- package/src/types/tool-protection.ts +0 -155
- package/src/utils/__tests__/did-helpers.test.ts +0 -156
- package/src/utils/base58.ts +0 -109
- package/src/utils/base64.ts +0 -148
- package/src/utils/cors.ts +0 -83
- package/src/utils/did-helpers.ts +0 -210
- package/src/utils/index.ts +0 -8
- package/src/utils/storage-keys.ts +0 -278
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -56
|
@@ -1,908 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool Protection Enforcement Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for tool protection enforcement in processToolCall.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
8
|
-
import { MCPIRuntimeBase } from "../../runtime/base";
|
|
9
|
-
import { ProviderRuntimeConfig } from "../../config";
|
|
10
|
-
import {
|
|
11
|
-
createMockProviders,
|
|
12
|
-
MockClockProvider,
|
|
13
|
-
} from "../utils/mock-providers";
|
|
14
|
-
import { ToolProtectionService } from "../../services/tool-protection.service";
|
|
15
|
-
import { DelegationRequiredError } from "../../types/tool-protection";
|
|
16
|
-
import { AccessControlApiService } from "../../services/access-control.service";
|
|
17
|
-
import type { VerifyDelegationAPIResponse } from "@kya-os/contracts/agentshield-api";
|
|
18
|
-
|
|
19
|
-
describe("MCPIRuntimeBase - Tool Protection Enforcement", () => {
|
|
20
|
-
let runtime: MCPIRuntimeBase;
|
|
21
|
-
let config: ProviderRuntimeConfig;
|
|
22
|
-
let mockProviders: ReturnType<typeof createMockProviders>;
|
|
23
|
-
let mockToolProtectionService: {
|
|
24
|
-
checkToolProtection: ReturnType<typeof vi.fn>;
|
|
25
|
-
};
|
|
26
|
-
let mockAccessControlService: {
|
|
27
|
-
verifyDelegation: ReturnType<typeof vi.fn>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
beforeEach(async () => {
|
|
31
|
-
vi.clearAllMocks();
|
|
32
|
-
mockProviders = createMockProviders();
|
|
33
|
-
|
|
34
|
-
// Create mock tool protection service
|
|
35
|
-
mockToolProtectionService = {
|
|
36
|
-
checkToolProtection: vi.fn(),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// Create mock access control service
|
|
40
|
-
mockAccessControlService = {
|
|
41
|
-
verifyDelegation: vi.fn(),
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
config = {
|
|
45
|
-
...mockProviders,
|
|
46
|
-
environment: "development",
|
|
47
|
-
session: {
|
|
48
|
-
timestampSkewSeconds: 120,
|
|
49
|
-
ttlMinutes: 30,
|
|
50
|
-
},
|
|
51
|
-
audit: {
|
|
52
|
-
enabled: true,
|
|
53
|
-
logFunction: vi.fn(),
|
|
54
|
-
includePayloads: true,
|
|
55
|
-
},
|
|
56
|
-
toolProtectionService: mockToolProtectionService as any,
|
|
57
|
-
};
|
|
58
|
-
runtime = new MCPIRuntimeBase(config);
|
|
59
|
-
// Inject mock access control service
|
|
60
|
-
(runtime as any).accessControlService = mockAccessControlService as any;
|
|
61
|
-
await runtime.initialize();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe("processToolCall with tool protection", () => {
|
|
65
|
-
const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
|
|
66
|
-
const session = {
|
|
67
|
-
id: "session123",
|
|
68
|
-
audience: "https://client.example.com",
|
|
69
|
-
nonce: "test-nonce",
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
it("should allow tool execution when no protection required", async () => {
|
|
73
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
74
|
-
|
|
75
|
-
const result = await runtime.processToolCall(
|
|
76
|
-
"unprotectedTool",
|
|
77
|
-
{ arg: "value" },
|
|
78
|
-
mockHandler,
|
|
79
|
-
session
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
83
|
-
expect(result).toEqual({ result: "success" });
|
|
84
|
-
expect(
|
|
85
|
-
mockToolProtectionService.checkToolProtection
|
|
86
|
-
).toHaveBeenCalledWith("unprotectedTool", "did:key:zmock123");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should block tool execution when protection required and no delegation", async () => {
|
|
90
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
91
|
-
requiresDelegation: true,
|
|
92
|
-
requiredScopes: ["files:write"],
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
await expect(
|
|
96
|
-
runtime.processToolCall(
|
|
97
|
-
"protectedTool",
|
|
98
|
-
{ arg: "value" },
|
|
99
|
-
mockHandler,
|
|
100
|
-
session
|
|
101
|
-
)
|
|
102
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
103
|
-
|
|
104
|
-
expect(mockHandler).not.toHaveBeenCalled();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("should allow tool execution when protection required and delegation provided", async () => {
|
|
108
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
109
|
-
requiresDelegation: true,
|
|
110
|
-
requiredScopes: ["files:write"],
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Mock successful delegation verification
|
|
114
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
115
|
-
success: true,
|
|
116
|
-
data: {
|
|
117
|
-
valid: true,
|
|
118
|
-
delegation_id: "test-delegation-id",
|
|
119
|
-
credential: {
|
|
120
|
-
agent_did: "did:key:zmock123",
|
|
121
|
-
scopes: ["files:write"],
|
|
122
|
-
issued_at: Date.now(),
|
|
123
|
-
created_at: Date.now(),
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
} as VerifyDelegationAPIResponse);
|
|
127
|
-
|
|
128
|
-
const sessionWithDelegation = {
|
|
129
|
-
...session,
|
|
130
|
-
delegationToken: "valid-delegation-token",
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const result = await runtime.processToolCall(
|
|
134
|
-
"protectedTool",
|
|
135
|
-
{ arg: "value" },
|
|
136
|
-
mockHandler,
|
|
137
|
-
sessionWithDelegation
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
// Verify delegation was checked
|
|
141
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
|
|
142
|
-
expect.objectContaining({
|
|
143
|
-
agent_did: "did:key:zmock123",
|
|
144
|
-
delegation_token: "valid-delegation-token",
|
|
145
|
-
scopes: ["files:write"],
|
|
146
|
-
}),
|
|
147
|
-
expect.objectContaining({
|
|
148
|
-
delegationToken: "valid-delegation-token",
|
|
149
|
-
})
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
153
|
-
expect(result).toEqual({ result: "success" });
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("should allow tool execution when protection required and consentProof provided", async () => {
|
|
157
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
158
|
-
requiresDelegation: true,
|
|
159
|
-
requiredScopes: ["files:write"],
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Mock successful delegation verification using consent proof as credential_jwt
|
|
163
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
164
|
-
success: true,
|
|
165
|
-
data: {
|
|
166
|
-
valid: true,
|
|
167
|
-
delegation_id: "test-delegation-id",
|
|
168
|
-
credential: {
|
|
169
|
-
agent_did: "did:key:zmock123",
|
|
170
|
-
scopes: ["files:write"],
|
|
171
|
-
issued_at: Date.now(),
|
|
172
|
-
created_at: Date.now(),
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
} as VerifyDelegationAPIResponse);
|
|
176
|
-
|
|
177
|
-
const sessionWithConsent = {
|
|
178
|
-
...session,
|
|
179
|
-
consentProof: "valid-consent-proof",
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const result = await runtime.processToolCall(
|
|
183
|
-
"protectedTool",
|
|
184
|
-
{ arg: "value" },
|
|
185
|
-
mockHandler,
|
|
186
|
-
sessionWithConsent
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Verify delegation was checked using consent proof
|
|
190
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
|
|
191
|
-
expect.objectContaining({
|
|
192
|
-
agent_did: "did:key:zmock123",
|
|
193
|
-
credential_jwt: "valid-consent-proof",
|
|
194
|
-
scopes: ["files:write"],
|
|
195
|
-
}),
|
|
196
|
-
expect.objectContaining({
|
|
197
|
-
credentialJwt: "valid-consent-proof",
|
|
198
|
-
})
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
202
|
-
expect(result).toEqual({ result: "success" });
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("should block tool execution when delegation verification fails", async () => {
|
|
206
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
207
|
-
requiresDelegation: true,
|
|
208
|
-
requiredScopes: ["files:write"],
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// Mock failed delegation verification
|
|
212
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
213
|
-
success: true,
|
|
214
|
-
data: {
|
|
215
|
-
valid: false,
|
|
216
|
-
reason: "Delegation token expired",
|
|
217
|
-
},
|
|
218
|
-
} as VerifyDelegationAPIResponse);
|
|
219
|
-
|
|
220
|
-
const sessionWithDelegation = {
|
|
221
|
-
...session,
|
|
222
|
-
delegationToken: "expired-delegation-token",
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
await expect(
|
|
226
|
-
runtime.processToolCall(
|
|
227
|
-
"protectedTool",
|
|
228
|
-
{ arg: "value" },
|
|
229
|
-
mockHandler,
|
|
230
|
-
sessionWithDelegation
|
|
231
|
-
)
|
|
232
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
233
|
-
|
|
234
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
|
|
235
|
-
expect(mockHandler).not.toHaveBeenCalled();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("should block tool execution when delegation has wrong scopes", async () => {
|
|
239
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
240
|
-
requiresDelegation: true,
|
|
241
|
-
requiredScopes: ["files:write"],
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
// Mock delegation with insufficient scopes
|
|
245
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
246
|
-
success: true,
|
|
247
|
-
data: {
|
|
248
|
-
valid: false,
|
|
249
|
-
reason: "Insufficient scopes",
|
|
250
|
-
credential: {
|
|
251
|
-
agent_did: "did:key:zmock123",
|
|
252
|
-
scopes: ["files:read"], // Only read, not write
|
|
253
|
-
issued_at: Date.now(),
|
|
254
|
-
created_at: Date.now(),
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
} as VerifyDelegationAPIResponse);
|
|
258
|
-
|
|
259
|
-
const sessionWithDelegation = {
|
|
260
|
-
...session,
|
|
261
|
-
delegationToken: "insufficient-scope-token",
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
await expect(
|
|
265
|
-
runtime.processToolCall(
|
|
266
|
-
"protectedTool",
|
|
267
|
-
{ arg: "value" },
|
|
268
|
-
mockHandler,
|
|
269
|
-
sessionWithDelegation
|
|
270
|
-
)
|
|
271
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
272
|
-
|
|
273
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalledWith(
|
|
274
|
-
expect.objectContaining({
|
|
275
|
-
scopes: ["files:write"],
|
|
276
|
-
}),
|
|
277
|
-
expect.any(Object)
|
|
278
|
-
);
|
|
279
|
-
expect(mockHandler).not.toHaveBeenCalled();
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("should handle API errors during verification gracefully", async () => {
|
|
283
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
284
|
-
requiresDelegation: true,
|
|
285
|
-
requiredScopes: ["files:write"],
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Mock API error
|
|
289
|
-
const { AgentShieldAPIError } = await import(
|
|
290
|
-
"@kya-os/contracts/agentshield-api"
|
|
291
|
-
);
|
|
292
|
-
mockAccessControlService.verifyDelegation.mockRejectedValue(
|
|
293
|
-
new AgentShieldAPIError("network_error", "API unavailable", {})
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
const sessionWithDelegation = {
|
|
297
|
-
...session,
|
|
298
|
-
delegationToken: "valid-token",
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// Should fail securely by requiring delegation
|
|
302
|
-
await expect(
|
|
303
|
-
runtime.processToolCall(
|
|
304
|
-
"protectedTool",
|
|
305
|
-
{ arg: "value" },
|
|
306
|
-
mockHandler,
|
|
307
|
-
sessionWithDelegation
|
|
308
|
-
)
|
|
309
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
310
|
-
|
|
311
|
-
expect(mockHandler).not.toHaveBeenCalled();
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it("should allow tool execution when access control service not configured (graceful degradation)", async () => {
|
|
315
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
316
|
-
requiresDelegation: true,
|
|
317
|
-
requiredScopes: ["files:write"],
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// Remove access control service
|
|
321
|
-
(runtime as any).accessControlService = undefined;
|
|
322
|
-
|
|
323
|
-
const sessionWithDelegation = {
|
|
324
|
-
...session,
|
|
325
|
-
delegationToken: "valid-delegation-token",
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
// Should allow execution with warning (graceful degradation)
|
|
329
|
-
const result = await runtime.processToolCall(
|
|
330
|
-
"protectedTool",
|
|
331
|
-
{ arg: "value" },
|
|
332
|
-
mockHandler,
|
|
333
|
-
sessionWithDelegation
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
337
|
-
expect(result).toEqual({ result: "success" });
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
describe("user_identifier validation", () => {
|
|
341
|
-
const userDidA = "did:key:zUserA123456789";
|
|
342
|
-
const userDidB = "did:key:zUserB987654321";
|
|
343
|
-
|
|
344
|
-
it("should reject delegation when user_identifier does not match session userDid", async () => {
|
|
345
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
346
|
-
requiresDelegation: true,
|
|
347
|
-
requiredScopes: ["files:write"],
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
// Mock verification response with User B's user_identifier
|
|
351
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
352
|
-
success: true,
|
|
353
|
-
data: {
|
|
354
|
-
valid: true,
|
|
355
|
-
delegation_id: "test-delegation-id",
|
|
356
|
-
credential: {
|
|
357
|
-
agent_did: "did:key:zmock123",
|
|
358
|
-
user_identifier: userDidB, // ❌ User B's identifier, not User A's
|
|
359
|
-
scopes: ["files:write"],
|
|
360
|
-
issued_at: Date.now(),
|
|
361
|
-
created_at: Date.now(),
|
|
362
|
-
},
|
|
363
|
-
},
|
|
364
|
-
} as VerifyDelegationAPIResponse);
|
|
365
|
-
|
|
366
|
-
// Session with User A's DID
|
|
367
|
-
const sessionWithUserA = {
|
|
368
|
-
...session,
|
|
369
|
-
userDid: userDidA, // User A's DID
|
|
370
|
-
delegationToken: "delegation-token-for-user-b",
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
// Should reject delegation due to user_identifier mismatch
|
|
374
|
-
await expect(
|
|
375
|
-
runtime.processToolCall(
|
|
376
|
-
"protectedTool",
|
|
377
|
-
{ arg: "value" },
|
|
378
|
-
mockHandler,
|
|
379
|
-
sessionWithUserA
|
|
380
|
-
)
|
|
381
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
382
|
-
|
|
383
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
|
|
384
|
-
expect(mockHandler).not.toHaveBeenCalled();
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("should accept delegation when user_identifier matches session userDid", async () => {
|
|
388
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
389
|
-
requiresDelegation: true,
|
|
390
|
-
requiredScopes: ["files:write"],
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// Mock verification response with User A's user_identifier
|
|
394
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
395
|
-
success: true,
|
|
396
|
-
data: {
|
|
397
|
-
valid: true,
|
|
398
|
-
delegation_id: "test-delegation-id",
|
|
399
|
-
credential: {
|
|
400
|
-
agent_did: "did:key:zmock123",
|
|
401
|
-
user_identifier: userDidA, // ✅ User A's identifier matches session
|
|
402
|
-
scopes: ["files:write"],
|
|
403
|
-
issued_at: Date.now(),
|
|
404
|
-
created_at: Date.now(),
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
} as VerifyDelegationAPIResponse);
|
|
408
|
-
|
|
409
|
-
// Session with User A's DID
|
|
410
|
-
const sessionWithUserA = {
|
|
411
|
-
...session,
|
|
412
|
-
userDid: userDidA, // User A's DID
|
|
413
|
-
delegationToken: "delegation-token-for-user-a",
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
// Should accept delegation when user_identifier matches
|
|
417
|
-
const result = await runtime.processToolCall(
|
|
418
|
-
"protectedTool",
|
|
419
|
-
{ arg: "value" },
|
|
420
|
-
mockHandler,
|
|
421
|
-
sessionWithUserA
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
expect(mockAccessControlService.verifyDelegation).toHaveBeenCalled();
|
|
425
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
426
|
-
expect(result).toEqual({ result: "success" });
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
it("should handle missing user_identifier gracefully (backward compatibility)", async () => {
|
|
430
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
431
|
-
requiresDelegation: true,
|
|
432
|
-
requiredScopes: ["files:write"],
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// Mock verification response without user_identifier (legacy format)
|
|
436
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
437
|
-
success: true,
|
|
438
|
-
data: {
|
|
439
|
-
valid: true,
|
|
440
|
-
delegation_id: "test-delegation-id",
|
|
441
|
-
credential: {
|
|
442
|
-
agent_did: "did:key:zmock123",
|
|
443
|
-
// No user_identifier field
|
|
444
|
-
scopes: ["files:write"],
|
|
445
|
-
issued_at: Date.now(),
|
|
446
|
-
created_at: Date.now(),
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
} as VerifyDelegationAPIResponse);
|
|
450
|
-
|
|
451
|
-
const sessionWithUserA = {
|
|
452
|
-
...session,
|
|
453
|
-
userDid: userDidA,
|
|
454
|
-
delegationToken: "legacy-delegation-token",
|
|
455
|
-
};
|
|
456
|
-
|
|
457
|
-
// Should allow execution when user_identifier is missing (backward compatibility)
|
|
458
|
-
const result = await runtime.processToolCall(
|
|
459
|
-
"protectedTool",
|
|
460
|
-
{ arg: "value" },
|
|
461
|
-
mockHandler,
|
|
462
|
-
sessionWithUserA
|
|
463
|
-
);
|
|
464
|
-
|
|
465
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
466
|
-
expect(result).toEqual({ result: "success" });
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
it("should handle missing session userDid gracefully", async () => {
|
|
470
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
471
|
-
requiresDelegation: true,
|
|
472
|
-
requiredScopes: ["files:write"],
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// Mock verification response with user_identifier
|
|
476
|
-
mockAccessControlService.verifyDelegation.mockResolvedValue({
|
|
477
|
-
success: true,
|
|
478
|
-
data: {
|
|
479
|
-
valid: true,
|
|
480
|
-
delegation_id: "test-delegation-id",
|
|
481
|
-
credential: {
|
|
482
|
-
agent_did: "did:key:zmock123",
|
|
483
|
-
user_identifier: userDidA,
|
|
484
|
-
scopes: ["files:write"],
|
|
485
|
-
issued_at: Date.now(),
|
|
486
|
-
created_at: Date.now(),
|
|
487
|
-
},
|
|
488
|
-
},
|
|
489
|
-
} as VerifyDelegationAPIResponse);
|
|
490
|
-
|
|
491
|
-
// Session without userDid (legacy format)
|
|
492
|
-
const sessionWithoutUserDid = {
|
|
493
|
-
...session,
|
|
494
|
-
// No userDid field
|
|
495
|
-
delegationToken: "delegation-token",
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
// Should allow execution when session userDid is missing (backward compatibility)
|
|
499
|
-
const result = await runtime.processToolCall(
|
|
500
|
-
"protectedTool",
|
|
501
|
-
{ arg: "value" },
|
|
502
|
-
mockHandler,
|
|
503
|
-
sessionWithoutUserDid
|
|
504
|
-
);
|
|
505
|
-
|
|
506
|
-
expect(mockHandler).toHaveBeenCalledWith({ arg: "value" });
|
|
507
|
-
expect(result).toEqual({ result: "success" });
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it("should create proof after successful tool execution", async () => {
|
|
512
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
513
|
-
|
|
514
|
-
await runtime.processToolCall(
|
|
515
|
-
"unprotectedTool",
|
|
516
|
-
{ arg: "value" },
|
|
517
|
-
mockHandler,
|
|
518
|
-
session
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
const proof = runtime.getLastProof();
|
|
522
|
-
expect(proof).toBeDefined();
|
|
523
|
-
expect(proof.did).toBe("did:key:zmock123");
|
|
524
|
-
expect(proof.sessionId).toBe("session123");
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
it("should not create proof when tool execution is blocked", async () => {
|
|
528
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
529
|
-
requiresDelegation: true,
|
|
530
|
-
requiredScopes: ["files:write"],
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const initialProof = runtime.getLastProof();
|
|
534
|
-
|
|
535
|
-
await expect(
|
|
536
|
-
runtime.processToolCall(
|
|
537
|
-
"protectedTool",
|
|
538
|
-
{ arg: "value" },
|
|
539
|
-
mockHandler,
|
|
540
|
-
session
|
|
541
|
-
)
|
|
542
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
543
|
-
|
|
544
|
-
// Proof should not be created when tool is blocked
|
|
545
|
-
const proofAfterBlock = runtime.getLastProof();
|
|
546
|
-
expect(proofAfterBlock).toBe(initialProof);
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
describe("DelegationRequiredError details", () => {
|
|
551
|
-
const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
|
|
552
|
-
const session = {
|
|
553
|
-
id: "session123",
|
|
554
|
-
audience: "https://client.example.com",
|
|
555
|
-
nonce: "test-nonce",
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
it("should include tool name in error", async () => {
|
|
559
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
560
|
-
requiresDelegation: true,
|
|
561
|
-
requiredScopes: ["files:write"],
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
try {
|
|
565
|
-
await runtime.processToolCall(
|
|
566
|
-
"protectedTool",
|
|
567
|
-
{ arg: "value" },
|
|
568
|
-
mockHandler,
|
|
569
|
-
session
|
|
570
|
-
);
|
|
571
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
572
|
-
} catch (error: any) {
|
|
573
|
-
expect(error).toBeInstanceOf(DelegationRequiredError);
|
|
574
|
-
expect(error.toolName).toBe("protectedTool");
|
|
575
|
-
expect(error.message).toContain("protectedTool");
|
|
576
|
-
}
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
it("should include required scopes in error", async () => {
|
|
580
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
581
|
-
requiresDelegation: true,
|
|
582
|
-
requiredScopes: ["files:write", "files:read"],
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
try {
|
|
586
|
-
await runtime.processToolCall(
|
|
587
|
-
"protectedTool",
|
|
588
|
-
{ arg: "value" },
|
|
589
|
-
mockHandler,
|
|
590
|
-
session
|
|
591
|
-
);
|
|
592
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
593
|
-
} catch (error: any) {
|
|
594
|
-
expect(error).toBeInstanceOf(DelegationRequiredError);
|
|
595
|
-
expect(error.requiredScopes).toEqual(["files:write", "files:read"]);
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
it("should include consent URL in error", async () => {
|
|
600
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
601
|
-
requiresDelegation: true,
|
|
602
|
-
requiredScopes: ["files:write"],
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
await runtime.processToolCall(
|
|
607
|
-
"protectedTool",
|
|
608
|
-
{ arg: "value" },
|
|
609
|
-
mockHandler,
|
|
610
|
-
session
|
|
611
|
-
);
|
|
612
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
613
|
-
} catch (error: any) {
|
|
614
|
-
expect(error).toBeInstanceOf(DelegationRequiredError);
|
|
615
|
-
expect(error.consentUrl).toBeDefined();
|
|
616
|
-
expect(error.consentUrl).toContain("kya.vouched.id/bouncer/consent");
|
|
617
|
-
expect(error.consentUrl).toContain("tool=protectedTool");
|
|
618
|
-
expect(error.consentUrl).toContain("scopes=files%3Awrite");
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
it("should include resume token in error", async () => {
|
|
623
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
624
|
-
requiresDelegation: true,
|
|
625
|
-
requiredScopes: ["files:write"],
|
|
626
|
-
});
|
|
627
|
-
|
|
628
|
-
try {
|
|
629
|
-
await runtime.processToolCall(
|
|
630
|
-
"protectedTool",
|
|
631
|
-
{ arg: "value" },
|
|
632
|
-
mockHandler,
|
|
633
|
-
session
|
|
634
|
-
);
|
|
635
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
636
|
-
} catch (error: any) {
|
|
637
|
-
expect(error).toBeInstanceOf(DelegationRequiredError);
|
|
638
|
-
expect(error.resumeToken).toBeDefined();
|
|
639
|
-
expect(error.resumeToken).toMatch(/^resume_/);
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
|
|
643
|
-
it("should include intercepted call context in error", async () => {
|
|
644
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
645
|
-
requiresDelegation: true,
|
|
646
|
-
requiredScopes: ["files:write"],
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
try {
|
|
650
|
-
await runtime.processToolCall(
|
|
651
|
-
"protectedTool",
|
|
652
|
-
{ arg: "value", nested: { data: "test" } },
|
|
653
|
-
mockHandler,
|
|
654
|
-
session
|
|
655
|
-
);
|
|
656
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
657
|
-
} catch (error: any) {
|
|
658
|
-
expect(error).toBeInstanceOf(DelegationRequiredError);
|
|
659
|
-
expect(error.interceptedCall).toBeDefined();
|
|
660
|
-
expect(error.interceptedCall.toolName).toBe("protectedTool");
|
|
661
|
-
expect(error.interceptedCall.args).toEqual({
|
|
662
|
-
arg: "value",
|
|
663
|
-
nested: { data: "test" },
|
|
664
|
-
});
|
|
665
|
-
expect(error.interceptedCall.sessionId).toBe("session123");
|
|
666
|
-
}
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
describe("audit logging", () => {
|
|
671
|
-
const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
|
|
672
|
-
const session = {
|
|
673
|
-
id: "session123",
|
|
674
|
-
audience: "https://client.example.com",
|
|
675
|
-
nonce: "test-nonce",
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
it("should log tool protection check when audit enabled", async () => {
|
|
679
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
680
|
-
|
|
681
|
-
await runtime.processToolCall(
|
|
682
|
-
"testTool",
|
|
683
|
-
{ arg: "value" },
|
|
684
|
-
mockHandler,
|
|
685
|
-
session
|
|
686
|
-
);
|
|
687
|
-
|
|
688
|
-
expect(config.audit!.logFunction).toHaveBeenCalled();
|
|
689
|
-
const logCalls = (config.audit!.logFunction as any).mock.calls;
|
|
690
|
-
const protectionLogCall = logCalls.find((call: any[]) => {
|
|
691
|
-
try {
|
|
692
|
-
const log = JSON.parse(call[0]);
|
|
693
|
-
return log.event === "tool_executed";
|
|
694
|
-
} catch {
|
|
695
|
-
return false;
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
expect(protectionLogCall).toBeDefined();
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it("should log blocked tool call when audit enabled", async () => {
|
|
702
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
703
|
-
requiresDelegation: true,
|
|
704
|
-
requiredScopes: ["files:write"],
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
const consoleWarnSpy = vi
|
|
708
|
-
.spyOn(console, "warn")
|
|
709
|
-
.mockImplementation(() => {});
|
|
710
|
-
|
|
711
|
-
try {
|
|
712
|
-
await runtime.processToolCall(
|
|
713
|
-
"protectedTool",
|
|
714
|
-
{ arg: "value" },
|
|
715
|
-
mockHandler,
|
|
716
|
-
session
|
|
717
|
-
);
|
|
718
|
-
} catch (error) {
|
|
719
|
-
// Expected
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
723
|
-
expect.stringContaining("[MCP-I] BLOCKED"),
|
|
724
|
-
expect.any(Object)
|
|
725
|
-
);
|
|
726
|
-
|
|
727
|
-
consoleWarnSpy.mockRestore();
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
it("should not log when audit disabled", async () => {
|
|
731
|
-
const auditLogFn = vi.fn();
|
|
732
|
-
const configNoAudit = {
|
|
733
|
-
...config,
|
|
734
|
-
audit: {
|
|
735
|
-
enabled: false,
|
|
736
|
-
logFunction: auditLogFn,
|
|
737
|
-
},
|
|
738
|
-
};
|
|
739
|
-
const runtimeNoAudit = new MCPIRuntimeBase(configNoAudit);
|
|
740
|
-
await runtimeNoAudit.initialize();
|
|
741
|
-
|
|
742
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
743
|
-
|
|
744
|
-
await runtimeNoAudit.processToolCall(
|
|
745
|
-
"testTool",
|
|
746
|
-
{ arg: "value" },
|
|
747
|
-
mockHandler,
|
|
748
|
-
session
|
|
749
|
-
);
|
|
750
|
-
|
|
751
|
-
// Should not call log function for tool execution (initialization may still log)
|
|
752
|
-
const toolExecutionLogs = auditLogFn.mock.calls.filter((call: any[]) => {
|
|
753
|
-
try {
|
|
754
|
-
const log = JSON.parse(call[0]);
|
|
755
|
-
return log.event === "tool_executed";
|
|
756
|
-
} catch {
|
|
757
|
-
return false;
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
expect(toolExecutionLogs.length).toBe(0);
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
describe("tool protection service integration", () => {
|
|
765
|
-
const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
|
|
766
|
-
const session = {
|
|
767
|
-
id: "session123",
|
|
768
|
-
audience: "https://client.example.com",
|
|
769
|
-
nonce: "test-nonce",
|
|
770
|
-
};
|
|
771
|
-
|
|
772
|
-
it("should use agent DID from identity for protection check", async () => {
|
|
773
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
774
|
-
|
|
775
|
-
await runtime.processToolCall(
|
|
776
|
-
"testTool",
|
|
777
|
-
{ arg: "value" },
|
|
778
|
-
mockHandler,
|
|
779
|
-
session
|
|
780
|
-
);
|
|
781
|
-
|
|
782
|
-
expect(
|
|
783
|
-
mockToolProtectionService.checkToolProtection
|
|
784
|
-
).toHaveBeenCalledWith("testTool", "did:key:zmock123");
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it("should handle tool protection service errors gracefully", async () => {
|
|
788
|
-
mockToolProtectionService.checkToolProtection.mockRejectedValue(
|
|
789
|
-
new Error("Service unavailable")
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
// If the service throws, the error propagates and tool execution is blocked
|
|
793
|
-
// This is the current behavior - service errors prevent tool execution
|
|
794
|
-
await expect(
|
|
795
|
-
runtime.processToolCall(
|
|
796
|
-
"testTool",
|
|
797
|
-
{ arg: "value" },
|
|
798
|
-
mockHandler,
|
|
799
|
-
session
|
|
800
|
-
)
|
|
801
|
-
).rejects.toThrow("Service unavailable");
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
it("should work without tool protection service", async () => {
|
|
805
|
-
const configNoService = {
|
|
806
|
-
...config,
|
|
807
|
-
toolProtectionService: undefined,
|
|
808
|
-
};
|
|
809
|
-
const runtimeNoService = new MCPIRuntimeBase(configNoService);
|
|
810
|
-
await runtimeNoService.initialize();
|
|
811
|
-
|
|
812
|
-
const result = await runtimeNoService.processToolCall(
|
|
813
|
-
"testTool",
|
|
814
|
-
{ arg: "value" },
|
|
815
|
-
mockHandler,
|
|
816
|
-
session
|
|
817
|
-
);
|
|
818
|
-
|
|
819
|
-
expect(mockHandler).toHaveBeenCalled();
|
|
820
|
-
expect(result).toEqual({ result: "success" });
|
|
821
|
-
});
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
describe("edge cases", () => {
|
|
825
|
-
const mockHandler = vi.fn().mockResolvedValue({ result: "success" });
|
|
826
|
-
const session = {
|
|
827
|
-
id: "session123",
|
|
828
|
-
audience: "https://client.example.com",
|
|
829
|
-
nonce: "test-nonce",
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
it("should handle empty required scopes array", async () => {
|
|
833
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
834
|
-
requiresDelegation: true,
|
|
835
|
-
requiredScopes: [],
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
await expect(
|
|
839
|
-
runtime.processToolCall(
|
|
840
|
-
"protectedTool",
|
|
841
|
-
{ arg: "value" },
|
|
842
|
-
mockHandler,
|
|
843
|
-
session
|
|
844
|
-
)
|
|
845
|
-
).rejects.toThrow(DelegationRequiredError);
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
it("should handle multiple required scopes", async () => {
|
|
849
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
850
|
-
requiresDelegation: true,
|
|
851
|
-
requiredScopes: ["scope1", "scope2", "scope3"],
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
try {
|
|
855
|
-
await runtime.processToolCall(
|
|
856
|
-
"protectedTool",
|
|
857
|
-
{ arg: "value" },
|
|
858
|
-
mockHandler,
|
|
859
|
-
session
|
|
860
|
-
);
|
|
861
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
862
|
-
} catch (error: any) {
|
|
863
|
-
expect(error.requiredScopes).toEqual(["scope1", "scope2", "scope3"]);
|
|
864
|
-
expect(error.consentUrl).toContain("scopes=scope1%2Cscope2%2Cscope3");
|
|
865
|
-
}
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
it("should handle session without id", async () => {
|
|
869
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue({
|
|
870
|
-
requiresDelegation: true,
|
|
871
|
-
requiredScopes: ["files:write"],
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
const sessionWithoutId = {
|
|
875
|
-
audience: "https://client.example.com",
|
|
876
|
-
nonce: "test-nonce",
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
try {
|
|
880
|
-
await runtime.processToolCall(
|
|
881
|
-
"protectedTool",
|
|
882
|
-
{ arg: "value" },
|
|
883
|
-
mockHandler,
|
|
884
|
-
sessionWithoutId
|
|
885
|
-
);
|
|
886
|
-
expect.fail("Should have thrown DelegationRequiredError");
|
|
887
|
-
} catch (error: any) {
|
|
888
|
-
expect(error.interceptedCall.sessionId).toBe("unknown");
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
it("should handle handler errors independently of protection", async () => {
|
|
893
|
-
mockToolProtectionService.checkToolProtection.mockResolvedValue(null);
|
|
894
|
-
const errorHandler = vi
|
|
895
|
-
.fn()
|
|
896
|
-
.mockRejectedValue(new Error("Handler failed"));
|
|
897
|
-
|
|
898
|
-
await expect(
|
|
899
|
-
runtime.processToolCall(
|
|
900
|
-
"errorTool",
|
|
901
|
-
{ arg: "value" },
|
|
902
|
-
errorHandler,
|
|
903
|
-
session
|
|
904
|
-
)
|
|
905
|
-
).rejects.toThrow("Handler failed");
|
|
906
|
-
});
|
|
907
|
-
});
|
|
908
|
-
});
|