@kya-os/mcp-i-core 1.3.10-canary.clientinfo.20251126124133 → 1.3.11
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/.claude/settings.local.json +9 -0
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test$colon$coverage.log +3419 -3072
- package/.turbo/turbo-test.log +1805 -1680
- package/coverage/coverage-final.json +59 -56
- package/dist/config/remote-config.d.ts +51 -0
- package/dist/config/remote-config.d.ts.map +1 -1
- package/dist/config/remote-config.js +74 -0
- package/dist/config/remote-config.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/delegation/did-key-resolver.d.ts +64 -0
- package/dist/delegation/did-key-resolver.d.ts.map +1 -0
- package/dist/delegation/did-key-resolver.js +159 -0
- package/dist/delegation/did-key-resolver.js.map +1 -0
- package/dist/delegation/utils.d.ts +76 -0
- package/dist/delegation/utils.d.ts.map +1 -1
- package/dist/delegation/utils.js +117 -0
- package/dist/delegation/utils.js.map +1 -1
- package/dist/identity/idp-token-resolver.d.ts +17 -1
- package/dist/identity/idp-token-resolver.d.ts.map +1 -1
- package/dist/identity/idp-token-resolver.js +34 -6
- package/dist/identity/idp-token-resolver.js.map +1 -1
- package/dist/identity/idp-token-storage.interface.d.ts +38 -7
- package/dist/identity/idp-token-storage.interface.d.ts.map +1 -1
- package/dist/identity/idp-token-storage.interface.js +2 -0
- package/dist/identity/idp-token-storage.interface.js.map +1 -1
- package/dist/identity/user-did-manager.d.ts +95 -12
- package/dist/identity/user-did-manager.d.ts.map +1 -1
- package/dist/identity/user-did-manager.js +107 -25
- package/dist/identity/user-did-manager.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +25 -8
- package/dist/runtime/base.d.ts.map +1 -1
- package/dist/runtime/base.js +74 -21
- package/dist/runtime/base.js.map +1 -1
- package/dist/services/session-registration.service.d.ts.map +1 -1
- package/dist/services/session-registration.service.js +10 -90
- package/dist/services/session-registration.service.js.map +1 -1
- package/dist/services/tool-context-builder.d.ts +18 -1
- package/dist/services/tool-context-builder.d.ts.map +1 -1
- package/dist/services/tool-context-builder.js +63 -10
- package/dist/services/tool-context-builder.js.map +1 -1
- package/dist/services/tool-protection.service.d.ts +6 -3
- package/dist/services/tool-protection.service.d.ts.map +1 -1
- package/dist/services/tool-protection.service.js +89 -34
- package/dist/services/tool-protection.service.js.map +1 -1
- package/dist/utils/base58.d.ts +31 -0
- package/dist/utils/base58.d.ts.map +1 -0
- package/dist/utils/base58.js +103 -0
- package/dist/utils/base58.js.map +1 -0
- package/dist/utils/did-helpers.d.ts +33 -0
- package/dist/utils/did-helpers.d.ts.map +1 -1
- package/dist/utils/did-helpers.js +53 -0
- package/dist/utils/did-helpers.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/identity/user-did-manager.test.ts +64 -45
- package/src/__tests__/integration/full-flow.test.ts +23 -10
- package/src/__tests__/runtime/base-extensions.test.ts +23 -21
- package/src/__tests__/runtime/proof-client-did.test.ts +19 -18
- package/src/__tests__/services/agentshield-integration.test.ts +10 -3
- package/src/__tests__/services/tool-protection-merged-config.test.ts +485 -0
- package/src/__tests__/services/tool-protection.service.test.ts +18 -11
- package/src/config/__tests__/merged-config.spec.ts +445 -0
- package/src/config/remote-config.ts +90 -0
- package/src/config.ts +3 -0
- package/src/delegation/__tests__/did-key-resolver.test.ts +265 -0
- package/src/delegation/__tests__/vc-issuer.test.ts +1 -1
- package/src/delegation/did-key-resolver.ts +179 -0
- package/src/delegation/utils.ts +179 -0
- package/src/identity/idp-token-resolver.ts +41 -7
- package/src/identity/idp-token-storage.interface.ts +42 -7
- package/src/identity/user-did-manager.ts +185 -29
- package/src/index.ts +42 -3
- package/src/runtime/base.ts +84 -21
- package/src/services/session-registration.service.ts +26 -121
- package/src/services/tool-context-builder.ts +75 -10
- package/src/services/tool-protection.service.ts +176 -88
- package/src/utils/__tests__/did-helpers.test.ts +55 -0
- package/src/utils/base58.ts +109 -0
- package/src/utils/did-helpers.ts +60 -0
- package/dist/__tests__/utils/mock-providers.d.ts +0 -103
- package/dist/__tests__/utils/mock-providers.d.ts.map +0 -1
- package/dist/__tests__/utils/mock-providers.js +0 -293
- package/dist/__tests__/utils/mock-providers.js.map +0 -1
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* If tool not discovered:
|
|
47
47
|
* - Tool won't appear in dashboard
|
|
48
48
|
* - Protection settings can't be configured
|
|
49
|
-
* - GET /
|
|
49
|
+
* - GET /config returns empty toolProtection.tools object
|
|
50
50
|
*
|
|
51
51
|
* DEBUGGING:
|
|
52
52
|
* ----------
|
|
@@ -87,34 +87,56 @@ import type {
|
|
|
87
87
|
import type { ToolProtectionCache } from "../cache/tool-protection-cache.js";
|
|
88
88
|
import { InMemoryToolProtectionCache } from "../cache/tool-protection-cache.js";
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Tool protection data structure in API responses
|
|
92
|
+
*/
|
|
93
|
+
interface ToolProtectionData {
|
|
94
|
+
requiresDelegation?: boolean;
|
|
95
|
+
requires_delegation?: boolean;
|
|
96
|
+
requiredScopes?: string[];
|
|
97
|
+
required_scopes?: string[];
|
|
98
|
+
scopes?: string[];
|
|
99
|
+
riskLevel?: string;
|
|
100
|
+
risk_level?: string;
|
|
101
|
+
oauthProvider?: string;
|
|
102
|
+
oauth_provider?: string;
|
|
103
|
+
authorization?: {
|
|
104
|
+
type: string;
|
|
105
|
+
provider?: string;
|
|
106
|
+
issuer?: string;
|
|
107
|
+
credentialType?: string;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
90
111
|
/**
|
|
91
112
|
* Response from AgentShield API bouncer endpoints
|
|
92
113
|
*
|
|
93
114
|
* Supports multiple endpoint formats:
|
|
94
|
-
* 1.
|
|
95
|
-
* 2.
|
|
96
|
-
* 3.
|
|
115
|
+
* 1. Merged config (/projects/{projectId}/config): { data: { config: { toolProtection: { tools: {...} } } } }
|
|
116
|
+
* 2. Legacy tool-protections endpoint: { data: { toolProtections: { [toolName]: {...} } } }
|
|
117
|
+
* 3. Old config endpoint (/config?agent_did=...): { data: { tools: [{ name: string, ... }] } }
|
|
118
|
+
* 4. Legacy format: { data: { tools: { [toolName]: {...} } } }
|
|
97
119
|
*/
|
|
98
120
|
interface BouncerConfigApiResponse {
|
|
99
121
|
success: boolean;
|
|
100
122
|
data: {
|
|
101
123
|
agent_did?: string;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
124
|
+
|
|
125
|
+
// NEW: Merged config format (v1.6.0+) - preferred format
|
|
126
|
+
// The entire config is returned with tools embedded at config.toolProtection.tools
|
|
127
|
+
config?: {
|
|
128
|
+
toolProtection?: {
|
|
129
|
+
source?: string;
|
|
130
|
+
tools?: Record<string, ToolProtectionData>;
|
|
131
|
+
};
|
|
132
|
+
// Other config fields we don't need to parse
|
|
133
|
+
[key: string]: unknown;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// DEPRECATED: Top-level toolProtections (backward compatibility during transition)
|
|
137
|
+
toolProtections?: Record<string, ToolProtectionData>;
|
|
138
|
+
|
|
139
|
+
// Legacy endpoint formats
|
|
118
140
|
tools?:
|
|
119
141
|
| Array<{
|
|
120
142
|
name: string;
|
|
@@ -122,20 +144,10 @@ interface BouncerConfigApiResponse {
|
|
|
122
144
|
requires_delegation?: boolean;
|
|
123
145
|
scopes?: string[];
|
|
124
146
|
required_scopes?: string[];
|
|
125
|
-
oauthProvider?: string;
|
|
126
|
-
oauth_provider?: string;
|
|
147
|
+
oauthProvider?: string;
|
|
148
|
+
oauth_provider?: string;
|
|
127
149
|
}>
|
|
128
|
-
| Record<
|
|
129
|
-
string,
|
|
130
|
-
{
|
|
131
|
-
requiresDelegation?: boolean;
|
|
132
|
-
requires_delegation?: boolean;
|
|
133
|
-
scopes?: string[];
|
|
134
|
-
required_scopes?: string[];
|
|
135
|
-
oauthProvider?: string; // Phase 2: Tool-specific OAuth provider
|
|
136
|
-
oauth_provider?: string; // Phase 2: snake_case variant
|
|
137
|
-
}
|
|
138
|
-
>;
|
|
150
|
+
| Record<string, ToolProtectionData>;
|
|
139
151
|
reputation_threshold?: number;
|
|
140
152
|
denied_agents?: string[];
|
|
141
153
|
};
|
|
@@ -254,7 +266,10 @@ export class ToolProtectionService {
|
|
|
254
266
|
projectId: this.config.projectId || "none",
|
|
255
267
|
toolCount: Object.keys(cached.toolProtections).length,
|
|
256
268
|
protectedTools: Object.entries(cached.toolProtections)
|
|
257
|
-
.filter(
|
|
269
|
+
.filter(
|
|
270
|
+
([_, config]: [string, ToolProtection]) =>
|
|
271
|
+
config.requiresDelegation
|
|
272
|
+
)
|
|
258
273
|
.map(([name]) => name),
|
|
259
274
|
cacheTtlMs: ttl,
|
|
260
275
|
cachedUntil,
|
|
@@ -271,7 +286,7 @@ export class ToolProtectionService {
|
|
|
271
286
|
projectId: this.config.projectId || "none",
|
|
272
287
|
apiUrl: this.config.apiUrl,
|
|
273
288
|
endpoint: this.config.projectId
|
|
274
|
-
? `/api/v1/bouncer/projects/${this.config.projectId}/
|
|
289
|
+
? `/api/v1/bouncer/projects/${this.config.projectId}/config`
|
|
275
290
|
: `/api/v1/bouncer/config?agent_did=${agentDid}`,
|
|
276
291
|
});
|
|
277
292
|
}
|
|
@@ -287,6 +302,8 @@ export class ToolProtectionService {
|
|
|
287
302
|
projectId: this.config.projectId || "none",
|
|
288
303
|
responseKeys: Object.keys(response),
|
|
289
304
|
dataKeys: response.data ? Object.keys(response.data) : [],
|
|
305
|
+
rawConfig: response.data?.config || null,
|
|
306
|
+
rawConfigToolProtection: response.data?.config?.toolProtection || null,
|
|
290
307
|
rawToolProtections: response.data?.toolProtections || null,
|
|
291
308
|
rawTools: response.data?.tools || null,
|
|
292
309
|
responseMetadata: response.metadata || null,
|
|
@@ -294,15 +311,54 @@ export class ToolProtectionService {
|
|
|
294
311
|
}
|
|
295
312
|
|
|
296
313
|
// Transform API response format to internal format
|
|
297
|
-
// Supports multiple response formats:
|
|
298
|
-
// 1.
|
|
299
|
-
// 2.
|
|
300
|
-
// 3. Old endpoint (
|
|
314
|
+
// Supports multiple response formats (in priority order):
|
|
315
|
+
// 1. Merged config endpoint: { data: { config: { toolProtection: { tools: {...} } } } }
|
|
316
|
+
// 2. Legacy toolProtections: { data: { toolProtections: { greet: { requiresDelegation: true, ... } } } }
|
|
317
|
+
// 3. Old endpoint (array): { data: { tools: [{ name: "greet", requiresDelegation: true, ... }] } }
|
|
318
|
+
// 4. Old endpoint (object): { data: { tools: { greet: { requiresDelegation: true, ... } } } }
|
|
301
319
|
const toolProtections: Record<string, ToolProtection> = {};
|
|
302
320
|
|
|
303
|
-
// Check for
|
|
304
|
-
if (response.data.
|
|
305
|
-
//
|
|
321
|
+
// Check for merged config format first (data.config.toolProtection.tools)
|
|
322
|
+
if (response.data.config?.toolProtection?.tools) {
|
|
323
|
+
// Merged config endpoint format: object with tool names as keys
|
|
324
|
+
if (this.config.debug) {
|
|
325
|
+
console.log("[ToolProtectionService] Using merged config format (data.config.toolProtection.tools)");
|
|
326
|
+
}
|
|
327
|
+
for (const [toolName, toolConfig] of Object.entries(
|
|
328
|
+
response.data.config.toolProtection.tools
|
|
329
|
+
)) {
|
|
330
|
+
const requiresDelegation =
|
|
331
|
+
(toolConfig as any).requiresDelegation ??
|
|
332
|
+
(toolConfig as any).requires_delegation ??
|
|
333
|
+
false;
|
|
334
|
+
const requiredScopes =
|
|
335
|
+
(toolConfig as any).requiredScopes ??
|
|
336
|
+
(toolConfig as any).required_scopes ??
|
|
337
|
+
(toolConfig as any).scopes ??
|
|
338
|
+
[];
|
|
339
|
+
|
|
340
|
+
const oauthProvider =
|
|
341
|
+
(toolConfig as any).oauthProvider ??
|
|
342
|
+
(toolConfig as any).oauth_provider ??
|
|
343
|
+
undefined;
|
|
344
|
+
|
|
345
|
+
const riskLevel =
|
|
346
|
+
(toolConfig as any).riskLevel ??
|
|
347
|
+
(toolConfig as any).risk_level ??
|
|
348
|
+
undefined;
|
|
349
|
+
|
|
350
|
+
toolProtections[toolName] = {
|
|
351
|
+
requiresDelegation,
|
|
352
|
+
requiredScopes,
|
|
353
|
+
...(oauthProvider && { oauthProvider }),
|
|
354
|
+
...(riskLevel && { riskLevel }),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
} else if (response.data.toolProtections) {
|
|
358
|
+
// Legacy toolProtections format: object with tool names as keys
|
|
359
|
+
if (this.config.debug) {
|
|
360
|
+
console.log("[ToolProtectionService] Using legacy toolProtections format (data.toolProtections)");
|
|
361
|
+
}
|
|
306
362
|
// Prefer camelCase over snake_case when both present
|
|
307
363
|
for (const [toolName, toolConfig] of Object.entries(
|
|
308
364
|
response.data.toolProtections
|
|
@@ -316,13 +372,13 @@ export class ToolProtectionService {
|
|
|
316
372
|
(toolConfig as any).required_scopes ??
|
|
317
373
|
(toolConfig as any).scopes ??
|
|
318
374
|
[];
|
|
319
|
-
|
|
375
|
+
|
|
320
376
|
// NEW: Parse oauthProvider (camelCase and snake_case support)
|
|
321
377
|
const oauthProvider =
|
|
322
378
|
(toolConfig as any).oauthProvider ??
|
|
323
379
|
(toolConfig as any).oauth_provider ??
|
|
324
380
|
undefined;
|
|
325
|
-
|
|
381
|
+
|
|
326
382
|
const riskLevel =
|
|
327
383
|
(toolConfig as any).riskLevel ??
|
|
328
384
|
(toolConfig as any).risk_level ??
|
|
@@ -361,17 +417,15 @@ export class ToolProtectionService {
|
|
|
361
417
|
(tool as any).required_scopes ??
|
|
362
418
|
(tool as any).scopes ??
|
|
363
419
|
[];
|
|
364
|
-
|
|
420
|
+
|
|
365
421
|
// NEW: Parse oauthProvider
|
|
366
422
|
const oauthProvider =
|
|
367
423
|
(tool as any).oauthProvider ??
|
|
368
424
|
(tool as any).oauth_provider ??
|
|
369
425
|
undefined;
|
|
370
|
-
|
|
426
|
+
|
|
371
427
|
const riskLevel =
|
|
372
|
-
(tool as any).riskLevel ??
|
|
373
|
-
(tool as any).risk_level ??
|
|
374
|
-
undefined;
|
|
428
|
+
(tool as any).riskLevel ?? (tool as any).risk_level ?? undefined;
|
|
375
429
|
|
|
376
430
|
toolProtections[toolName] = {
|
|
377
431
|
requiresDelegation,
|
|
@@ -395,13 +449,13 @@ export class ToolProtectionService {
|
|
|
395
449
|
(toolConfig as any).required_scopes ??
|
|
396
450
|
(toolConfig as any).scopes ??
|
|
397
451
|
[];
|
|
398
|
-
|
|
452
|
+
|
|
399
453
|
// NEW: Parse oauthProvider
|
|
400
454
|
const oauthProvider =
|
|
401
455
|
(toolConfig as any).oauthProvider ??
|
|
402
456
|
(toolConfig as any).oauth_provider ??
|
|
403
457
|
undefined;
|
|
404
|
-
|
|
458
|
+
|
|
405
459
|
const riskLevel =
|
|
406
460
|
(toolConfig as any).riskLevel ??
|
|
407
461
|
(toolConfig as any).risk_level ??
|
|
@@ -427,7 +481,11 @@ export class ToolProtectionService {
|
|
|
427
481
|
)) {
|
|
428
482
|
// Skip if localConfig is empty or not a valid ToolProtection object
|
|
429
483
|
// This prevents empty objects from corrupting the merged config
|
|
430
|
-
if (
|
|
484
|
+
if (
|
|
485
|
+
!localConfig ||
|
|
486
|
+
typeof localConfig !== "object" ||
|
|
487
|
+
Object.keys(localConfig).length === 0
|
|
488
|
+
) {
|
|
431
489
|
if (this.config.debug) {
|
|
432
490
|
console.log(
|
|
433
491
|
"[ToolProtectionService] Skipping empty/invalid fallback config entry",
|
|
@@ -436,13 +494,14 @@ export class ToolProtectionService {
|
|
|
436
494
|
}
|
|
437
495
|
continue;
|
|
438
496
|
}
|
|
439
|
-
|
|
497
|
+
|
|
440
498
|
// Ensure requiredScopes exists (default to empty array if missing)
|
|
441
499
|
const validConfig: ToolProtection = {
|
|
442
|
-
requiresDelegation:
|
|
500
|
+
requiresDelegation:
|
|
501
|
+
(localConfig as any).requiresDelegation ?? false,
|
|
443
502
|
requiredScopes: (localConfig as any).requiredScopes ?? [],
|
|
444
503
|
};
|
|
445
|
-
|
|
504
|
+
|
|
446
505
|
// Local config overrides API config for this tool
|
|
447
506
|
mergedToolProtections[toolName] = validConfig;
|
|
448
507
|
if (this.config.debug) {
|
|
@@ -471,8 +530,10 @@ export class ToolProtectionService {
|
|
|
471
530
|
console.log("[ToolProtectionService] Config loaded from API", {
|
|
472
531
|
source: "api",
|
|
473
532
|
toolCount: Object.keys(mergedToolProtections).length,
|
|
474
|
-
|
|
475
|
-
.filter(
|
|
533
|
+
protectedTools: Object.entries(mergedToolProtections)
|
|
534
|
+
.filter(
|
|
535
|
+
([_, config]: [string, ToolProtection]) => config.requiresDelegation
|
|
536
|
+
)
|
|
476
537
|
.map(([name]) => name),
|
|
477
538
|
agentDid: agentDid.slice(0, 20) + "...",
|
|
478
539
|
projectId: this.config.projectId || "none",
|
|
@@ -665,7 +726,10 @@ export class ToolProtectionService {
|
|
|
665
726
|
|
|
666
727
|
/**
|
|
667
728
|
* Fetch tool protection config from AgentShield API
|
|
668
|
-
*
|
|
729
|
+
*
|
|
730
|
+
* Uses the merged /config endpoint which returns tool protections embedded
|
|
731
|
+
* at config.toolProtection.tools. Falls back to legacy formats for backward
|
|
732
|
+
* compatibility.
|
|
669
733
|
*
|
|
670
734
|
* @param agentDid DID of the agent to fetch config for
|
|
671
735
|
* @param options Optional fetch options
|
|
@@ -675,17 +739,17 @@ export class ToolProtectionService {
|
|
|
675
739
|
agentDid: string,
|
|
676
740
|
options?: { bypassCDNCache?: boolean }
|
|
677
741
|
): Promise<BouncerConfigApiResponse> {
|
|
678
|
-
//
|
|
679
|
-
//
|
|
742
|
+
// Use the merged /config endpoint which includes embedded tool protections
|
|
743
|
+
// This endpoint returns config.toolProtection.tools with all tool rules
|
|
680
744
|
let url: string;
|
|
681
|
-
let
|
|
745
|
+
let useMergedEndpoint = false;
|
|
682
746
|
|
|
683
747
|
if (this.config.projectId) {
|
|
684
|
-
// ✅
|
|
685
|
-
url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/
|
|
686
|
-
|
|
748
|
+
// ✅ MERGED CONFIG ENDPOINT: Returns config with embedded toolProtection.tools
|
|
749
|
+
url = `${this.config.apiUrl}/api/v1/bouncer/projects/${encodeURIComponent(this.config.projectId)}/config`;
|
|
750
|
+
useMergedEndpoint = true;
|
|
687
751
|
} else {
|
|
688
|
-
// ⚠️
|
|
752
|
+
// ⚠️ LEGACY ENDPOINT: Agent-scoped, returns tools array (backward compatibility)
|
|
689
753
|
url = `${this.config.apiUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
|
|
690
754
|
}
|
|
691
755
|
|
|
@@ -712,9 +776,9 @@ export class ToolProtectionService {
|
|
|
712
776
|
|
|
713
777
|
if (this.config.debug) {
|
|
714
778
|
console.log("[ToolProtectionService] Fetching from API:", url, {
|
|
715
|
-
method:
|
|
716
|
-
? "projects/{projectId}/
|
|
717
|
-
: "config?agent_did (
|
|
779
|
+
method: useMergedEndpoint
|
|
780
|
+
? "projects/{projectId}/config (merged)"
|
|
781
|
+
: "config?agent_did (legacy)",
|
|
718
782
|
projectId: this.config.projectId || "none",
|
|
719
783
|
apiKeyPresent: !!this.config.apiKey,
|
|
720
784
|
apiKeyLength,
|
|
@@ -736,19 +800,19 @@ export class ToolProtectionService {
|
|
|
736
800
|
);
|
|
737
801
|
}
|
|
738
802
|
|
|
739
|
-
// Build headers -
|
|
803
|
+
// Build headers - merged endpoint uses X-API-Key, legacy uses Authorization Bearer
|
|
740
804
|
const headers: Record<string, string> = {
|
|
741
805
|
"Content-Type": "application/json",
|
|
742
806
|
};
|
|
743
807
|
|
|
744
|
-
if (
|
|
745
|
-
// ✅
|
|
808
|
+
if (useMergedEndpoint) {
|
|
809
|
+
// ✅ Merged config endpoint headers
|
|
746
810
|
headers["X-API-Key"] = this.config.apiKey;
|
|
747
811
|
if (this.config.projectId) {
|
|
748
812
|
headers["X-Project-Id"] = this.config.projectId;
|
|
749
813
|
}
|
|
750
814
|
} else {
|
|
751
|
-
// ⚠️
|
|
815
|
+
// ⚠️ Legacy endpoint headers (backward compatibility)
|
|
752
816
|
headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
753
817
|
}
|
|
754
818
|
|
|
@@ -786,6 +850,19 @@ export class ToolProtectionService {
|
|
|
786
850
|
throw new Error("API returned success: false");
|
|
787
851
|
}
|
|
788
852
|
|
|
853
|
+
// Transform merged config format to normalized format
|
|
854
|
+
// If response contains config.toolProtection.tools, extract them to data.toolProtections
|
|
855
|
+
if (useMergedEndpoint && data.data.config?.toolProtection?.tools) {
|
|
856
|
+
// Extract embedded tools to the standard toolProtections field
|
|
857
|
+
data.data.toolProtections = data.data.config.toolProtection.tools;
|
|
858
|
+
if (this.config.debug) {
|
|
859
|
+
console.log("[ToolProtectionService] Extracted tools from merged config", {
|
|
860
|
+
toolCount: Object.keys(data.data.toolProtections).length,
|
|
861
|
+
tools: Object.keys(data.data.toolProtections),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
789
866
|
return data;
|
|
790
867
|
}
|
|
791
868
|
|
|
@@ -816,28 +893,28 @@ export class ToolProtectionService {
|
|
|
816
893
|
|
|
817
894
|
/**
|
|
818
895
|
* Clear cache and immediately fetch fresh config from API
|
|
819
|
-
*
|
|
896
|
+
*
|
|
820
897
|
* This method is designed for Cloudflare Workers where KV has edge caching.
|
|
821
898
|
* After clearing the KV entry, it fetches fresh data from the API and writes
|
|
822
899
|
* it back to KV. This ensures:
|
|
823
900
|
* 1. The global KV entry is deleted
|
|
824
901
|
* 2. Fresh data is fetched from API (with CDN cache bypass!)
|
|
825
902
|
* 3. New data is written to KV (updating edge cache)
|
|
826
|
-
*
|
|
903
|
+
*
|
|
827
904
|
* The next request from the same edge location will get the fresh data.
|
|
828
|
-
*
|
|
905
|
+
*
|
|
829
906
|
* IMPORTANT: This method uses bypassCDNCache to ensure we get fresh data
|
|
830
907
|
* from AgentShield's origin server, not stale CDN-cached data. This is
|
|
831
908
|
* critical for instant cache invalidation when tool protection settings
|
|
832
909
|
* are changed in the AgentShield dashboard.
|
|
833
|
-
*
|
|
910
|
+
*
|
|
834
911
|
* @param agentDid DID of the agent (used for cache key)
|
|
835
912
|
* @returns The fresh tool protection config from API
|
|
836
913
|
*/
|
|
837
914
|
async clearAndRefresh(agentDid: string): Promise<{
|
|
838
915
|
config: ToolProtectionConfig;
|
|
839
916
|
cacheKey: string;
|
|
840
|
-
source:
|
|
917
|
+
source: "api" | "fallback";
|
|
841
918
|
}> {
|
|
842
919
|
const cacheKey = this.config.projectId
|
|
843
920
|
? `config:tool-protections:${this.config.projectId}`
|
|
@@ -857,7 +934,9 @@ export class ToolProtectionService {
|
|
|
857
934
|
// 2. Fetch fresh config from API with CDN cache bypass
|
|
858
935
|
// This ensures we get fresh data from origin, not stale CDN data
|
|
859
936
|
try {
|
|
860
|
-
const response = await this.fetchFromApi(agentDid, {
|
|
937
|
+
const response = await this.fetchFromApi(agentDid, {
|
|
938
|
+
bypassCDNCache: true,
|
|
939
|
+
});
|
|
861
940
|
|
|
862
941
|
// Transform API response to internal format (same logic as getToolProtectionConfig)
|
|
863
942
|
const toolProtections: Record<string, ToolProtection> = {};
|
|
@@ -897,15 +976,20 @@ export class ToolProtectionService {
|
|
|
897
976
|
const toolName = (tool as any).name;
|
|
898
977
|
if (!toolName) continue;
|
|
899
978
|
const requiresDelegation =
|
|
900
|
-
(tool as any).requiresDelegation ??
|
|
979
|
+
(tool as any).requiresDelegation ??
|
|
980
|
+
(tool as any).requires_delegation ??
|
|
981
|
+
false;
|
|
901
982
|
const requiredScopes =
|
|
902
983
|
(tool as any).requiredScopes ??
|
|
903
984
|
(tool as any).required_scopes ??
|
|
904
985
|
(tool as any).scopes ??
|
|
905
986
|
[];
|
|
906
987
|
const oauthProvider =
|
|
907
|
-
(tool as any).oauthProvider ??
|
|
908
|
-
|
|
988
|
+
(tool as any).oauthProvider ??
|
|
989
|
+
(tool as any).oauth_provider ??
|
|
990
|
+
undefined;
|
|
991
|
+
const riskLevel =
|
|
992
|
+
(tool as any).riskLevel ?? (tool as any).risk_level ?? undefined;
|
|
909
993
|
|
|
910
994
|
toolProtections[toolName] = {
|
|
911
995
|
requiresDelegation,
|
|
@@ -964,19 +1048,23 @@ export class ToolProtectionService {
|
|
|
964
1048
|
source: "api",
|
|
965
1049
|
});
|
|
966
1050
|
|
|
967
|
-
return { config: freshConfig, cacheKey, source:
|
|
1051
|
+
return { config: freshConfig, cacheKey, source: "api" };
|
|
968
1052
|
} catch (error) {
|
|
969
|
-
console.warn(
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1053
|
+
console.warn(
|
|
1054
|
+
"[ToolProtectionService] API fetch failed during refresh, using fallback",
|
|
1055
|
+
{
|
|
1056
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1057
|
+
cacheKey,
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
973
1060
|
|
|
974
1061
|
// Use fallback config if API fails
|
|
975
|
-
const fallbackConfig: ToolProtectionConfig = this.config
|
|
1062
|
+
const fallbackConfig: ToolProtectionConfig = this.config
|
|
1063
|
+
.fallbackConfig || {
|
|
976
1064
|
toolProtections: {},
|
|
977
1065
|
};
|
|
978
1066
|
|
|
979
|
-
return { config: fallbackConfig, cacheKey, source:
|
|
1067
|
+
return { config: fallbackConfig, cacheKey, source: "fallback" };
|
|
980
1068
|
}
|
|
981
1069
|
}
|
|
982
1070
|
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
normalizeDid,
|
|
12
12
|
compareDids,
|
|
13
13
|
getServerDid,
|
|
14
|
+
generateDidKeyFromBytes,
|
|
15
|
+
generateDidKeyFromBase64,
|
|
14
16
|
} from "../did-helpers";
|
|
15
17
|
|
|
16
18
|
describe("DID Helpers", () => {
|
|
@@ -97,5 +99,58 @@ describe("DID Helpers", () => {
|
|
|
97
99
|
expect(() => getServerDid(config)).toThrow("Server DID not configured");
|
|
98
100
|
});
|
|
99
101
|
});
|
|
102
|
+
|
|
103
|
+
describe("generateDidKeyFromBytes", () => {
|
|
104
|
+
it("should generate valid did:key from 32-byte Ed25519 public key", () => {
|
|
105
|
+
// Use a known test key (32 bytes)
|
|
106
|
+
const publicKeyBytes = new Uint8Array(32).fill(0xab);
|
|
107
|
+
const did = generateDidKeyFromBytes(publicKeyBytes);
|
|
108
|
+
|
|
109
|
+
expect(did).toMatch(/^did:key:z6Mk/);
|
|
110
|
+
expect(isValidDid(did)).toBe(true);
|
|
111
|
+
expect(getDidMethod(did)).toBe("key");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should generate consistent did:key for same input", () => {
|
|
115
|
+
const publicKeyBytes = new Uint8Array(32).fill(0x42);
|
|
116
|
+
const did1 = generateDidKeyFromBytes(publicKeyBytes);
|
|
117
|
+
const did2 = generateDidKeyFromBytes(publicKeyBytes);
|
|
118
|
+
|
|
119
|
+
expect(did1).toBe(did2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should generate different did:key for different inputs", () => {
|
|
123
|
+
const key1 = new Uint8Array(32).fill(0x11);
|
|
124
|
+
const key2 = new Uint8Array(32).fill(0x22);
|
|
125
|
+
|
|
126
|
+
const did1 = generateDidKeyFromBytes(key1);
|
|
127
|
+
const did2 = generateDidKeyFromBytes(key2);
|
|
128
|
+
|
|
129
|
+
expect(did1).not.toBe(did2);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("generateDidKeyFromBase64", () => {
|
|
134
|
+
it("should generate valid did:key from base64-encoded public key", () => {
|
|
135
|
+
// Base64 encode a 32-byte key
|
|
136
|
+
const publicKeyBytes = new Uint8Array(32).fill(0xcd);
|
|
137
|
+
const publicKeyBase64 = btoa(String.fromCharCode(...publicKeyBytes));
|
|
138
|
+
|
|
139
|
+
const did = generateDidKeyFromBase64(publicKeyBase64);
|
|
140
|
+
|
|
141
|
+
expect(did).toMatch(/^did:key:z6Mk/);
|
|
142
|
+
expect(isValidDid(did)).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should produce same result as generateDidKeyFromBytes", () => {
|
|
146
|
+
const publicKeyBytes = new Uint8Array(32).fill(0xef);
|
|
147
|
+
const publicKeyBase64 = btoa(String.fromCharCode(...publicKeyBytes));
|
|
148
|
+
|
|
149
|
+
const didFromBytes = generateDidKeyFromBytes(publicKeyBytes);
|
|
150
|
+
const didFromBase64 = generateDidKeyFromBase64(publicKeyBase64);
|
|
151
|
+
|
|
152
|
+
expect(didFromBytes).toBe(didFromBase64);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
100
155
|
});
|
|
101
156
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base58 Utilities (Bitcoin alphabet)
|
|
3
|
+
*
|
|
4
|
+
* Encoding and decoding utilities for Base58 (Bitcoin alphabet).
|
|
5
|
+
* Used for did:key multibase encoding (with 'z' prefix for base58btc).
|
|
6
|
+
*
|
|
7
|
+
* The Bitcoin alphabet excludes ambiguous characters (0, O, I, l).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
11
|
+
const ALPHABET_MAP = new Map<string, number>();
|
|
12
|
+
|
|
13
|
+
// Build reverse lookup map
|
|
14
|
+
for (let i = 0; i < ALPHABET.length; i++) {
|
|
15
|
+
ALPHABET_MAP.set(ALPHABET[i], i);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Encode bytes to Base58 (Bitcoin alphabet)
|
|
20
|
+
*
|
|
21
|
+
* @param bytes - Bytes to encode
|
|
22
|
+
* @returns Base58-encoded string
|
|
23
|
+
*/
|
|
24
|
+
export function base58Encode(bytes: Uint8Array): string {
|
|
25
|
+
if (bytes.length === 0) return '';
|
|
26
|
+
|
|
27
|
+
// Convert bytes to big integer
|
|
28
|
+
let num = BigInt(0);
|
|
29
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
30
|
+
num = num * BigInt(256) + BigInt(bytes[i]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Convert to base58
|
|
34
|
+
let result = '';
|
|
35
|
+
while (num > 0) {
|
|
36
|
+
result = ALPHABET[Number(num % BigInt(58))] + result;
|
|
37
|
+
num = num / BigInt(58);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add leading zeros (encoded as '1' in base58)
|
|
41
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
|
|
42
|
+
result = '1' + result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Decode Base58 (Bitcoin alphabet) to bytes
|
|
50
|
+
*
|
|
51
|
+
* @param encoded - Base58-encoded string
|
|
52
|
+
* @returns Decoded bytes
|
|
53
|
+
* @throws Error if input contains invalid characters
|
|
54
|
+
*/
|
|
55
|
+
export function base58Decode(encoded: string): Uint8Array {
|
|
56
|
+
if (encoded.length === 0) return new Uint8Array(0);
|
|
57
|
+
|
|
58
|
+
// Convert base58 to big integer
|
|
59
|
+
let num = BigInt(0);
|
|
60
|
+
for (const char of encoded) {
|
|
61
|
+
const value = ALPHABET_MAP.get(char);
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
throw new Error(`Invalid base58 character: ${char}`);
|
|
64
|
+
}
|
|
65
|
+
num = num * BigInt(58) + BigInt(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Convert big integer to bytes
|
|
69
|
+
const bytes: number[] = [];
|
|
70
|
+
while (num > 0) {
|
|
71
|
+
bytes.unshift(Number(num % BigInt(256)));
|
|
72
|
+
num = num / BigInt(256);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Count leading zeros in input (encoded as '1')
|
|
76
|
+
let leadingZeros = 0;
|
|
77
|
+
for (const char of encoded) {
|
|
78
|
+
if (char === '1') {
|
|
79
|
+
leadingZeros++;
|
|
80
|
+
} else {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prepend leading zero bytes
|
|
86
|
+
const result = new Uint8Array(leadingZeros + bytes.length);
|
|
87
|
+
// Leading zeros are already 0 in Uint8Array
|
|
88
|
+
result.set(bytes, leadingZeros);
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validate a Base58 string
|
|
95
|
+
*
|
|
96
|
+
* @param encoded - String to validate
|
|
97
|
+
* @returns true if valid Base58, false otherwise
|
|
98
|
+
*/
|
|
99
|
+
export function isValidBase58(encoded: string): boolean {
|
|
100
|
+
if (encoded.length === 0) return true;
|
|
101
|
+
|
|
102
|
+
for (const char of encoded) {
|
|
103
|
+
if (!ALPHABET_MAP.has(char)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return true;
|
|
109
|
+
}
|