@astrasyncai/verification-gateway 2.4.12 → 2.5.0
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/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +224 -42
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +224 -42
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +101 -57
- package/dist/adapters/mcp.d.ts +101 -57
- package/dist/adapters/mcp.js +215 -44
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +215 -44
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +87 -34
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +87 -34
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +61 -28
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +61 -28
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +29 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +29 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.js +102 -30
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +102 -30
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +102 -30
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +102 -30
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-C1ePFB7n.d.ts → express-CrfwoNAR.d.ts} +1 -1
- package/dist/{express-4WStX3PV.d.mts → express-ienhAXps.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +102 -30
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +102 -30
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-ChPX4WHl.d.mts → index-B5e2IDWU.d.mts} +1 -1
- package/dist/{index-CzJMCgEy.d.ts → index-CCdZxvAr.d.ts} +71 -6
- package/dist/{index-D8IEntil.d.mts → index-CEg_WG6y.d.mts} +71 -6
- package/dist/{index-Cjm-zBeZ.d.ts → index-DC5f8eoQ.d.ts} +1 -1
- package/dist/index.d.mts +39 -9
- package/dist/index.d.ts +39 -9
- package/dist/index.js +500 -94
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +497 -94
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/local-evaluator/evaluator.js +12 -2
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +12 -2
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{nextjs-BIORS__0.d.ts → nextjs-66R1KW8e.d.ts} +1 -1
- package/dist/{nextjs-CjzHdaXA.d.mts → nextjs-DSpisQst.d.mts} +1 -1
- package/dist/{sdk-Chhz-FcT.d.mts → sdk-5U_CBRpr.d.mts} +1 -1
- package/dist/{sdk-CqTEQAc6.d.ts → sdk-Bm8np66n.d.ts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +146 -28
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +146 -28
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-L15pYd2c.d.mts → types-B3USs-Kx.d.mts} +42 -1
- package/dist/{types-L15pYd2c.d.ts → types-B3USs-Kx.d.ts} +42 -1
- package/dist/{types-DNK2BgIf.d.mts → types-CgDCUfo8.d.mts} +1 -1
- package/dist/{types-DoWIuzfj.d.ts → types-R5N4ET6x.d.ts} +1 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -18,7 +18,66 @@ function hasMinimumAccess(actual, required) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// src/version.ts
|
|
21
|
-
var SDK_VERSION = "2.4.
|
|
21
|
+
var SDK_VERSION = "2.4.13";
|
|
22
|
+
|
|
23
|
+
// src/well-known.ts
|
|
24
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
25
|
+
var cache = /* @__PURE__ */ new Map();
|
|
26
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
27
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
28
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
29
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
30
|
+
}
|
|
31
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
32
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: { Accept: "application/json" },
|
|
36
|
+
signal: AbortSignal.timeout(5e3)
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
51
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
52
|
+
const existing = inflight.get(apiBaseUrl);
|
|
53
|
+
if (existing) return existing;
|
|
54
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
55
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
56
|
+
inflight.delete(apiBaseUrl);
|
|
57
|
+
return data;
|
|
58
|
+
}).catch((err) => {
|
|
59
|
+
inflight.delete(apiBaseUrl);
|
|
60
|
+
throw err;
|
|
61
|
+
});
|
|
62
|
+
inflight.set(apiBaseUrl, promise);
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
66
|
+
const entry = cache.get(apiBaseUrl);
|
|
67
|
+
if (entry) {
|
|
68
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
69
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return entry.data;
|
|
73
|
+
}
|
|
74
|
+
const pending = inflight.get(apiBaseUrl);
|
|
75
|
+
if (pending) return pending;
|
|
76
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
77
|
+
}
|
|
78
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
79
|
+
return cache.get(apiBaseUrl)?.data;
|
|
80
|
+
}
|
|
22
81
|
|
|
23
82
|
// src/verify.ts
|
|
24
83
|
var DEFAULT_CONFIG = {
|
|
@@ -37,22 +96,27 @@ var DEFAULT_CONFIG = {
|
|
|
37
96
|
};
|
|
38
97
|
var initCheckPerformed = false;
|
|
39
98
|
var deprecationWarningShown = false;
|
|
40
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
99
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
41
100
|
initCheckPerformed = true;
|
|
42
101
|
try {
|
|
43
102
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
44
103
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
45
104
|
const contentType = response.headers.get("content-type") ?? "";
|
|
46
105
|
if (contentType.startsWith("text/html")) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
106
|
+
const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
|
|
107
|
+
if (strictInit) {
|
|
108
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
109
|
+
}
|
|
110
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
50
111
|
} else if (debug) {
|
|
51
112
|
console.log(
|
|
52
113
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
53
114
|
);
|
|
54
115
|
}
|
|
55
116
|
} catch (err) {
|
|
117
|
+
if (strictInit) {
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
56
120
|
if (debug) {
|
|
57
121
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
58
122
|
}
|
|
@@ -76,7 +140,23 @@ function getCacheKey(request) {
|
|
|
76
140
|
request.counterpartyType || "",
|
|
77
141
|
request.isSubAgentRequest ? "1" : "0",
|
|
78
142
|
request.parentAgentId || "",
|
|
79
|
-
request.subAgentDepth ?? ""
|
|
143
|
+
request.subAgentDepth ?? "",
|
|
144
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
145
|
+
// backend verdict. Without these, two requests with different
|
|
146
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
147
|
+
// the shorter-duration allow served the longer-duration request.
|
|
148
|
+
request.durationRequired ?? "",
|
|
149
|
+
request.invocationProtocol || "",
|
|
150
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
151
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
152
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
153
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
154
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
155
|
+
// be cached across IPs).
|
|
156
|
+
request.callerMetadata?.sourceIp || "",
|
|
157
|
+
request.callerMetadata?.userAgent || "",
|
|
158
|
+
request.callerMetadata?.forwardedFor || "",
|
|
159
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
80
160
|
].join("|");
|
|
81
161
|
}
|
|
82
162
|
function getCachedResult(request) {
|
|
@@ -102,9 +182,13 @@ function cacheResult(request, result, configuredTtl) {
|
|
|
102
182
|
}
|
|
103
183
|
function extractCredentials(headers, query) {
|
|
104
184
|
const credentials = {};
|
|
185
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
105
186
|
const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
|
|
106
187
|
if (astraIdHeader) {
|
|
107
|
-
|
|
188
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
189
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
190
|
+
credentials.astraId = raw;
|
|
191
|
+
}
|
|
108
192
|
}
|
|
109
193
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
110
194
|
if (apiKeyHeader) {
|
|
@@ -113,9 +197,11 @@ function extractCredentials(headers, query) {
|
|
|
113
197
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
114
198
|
if (authHeader) {
|
|
115
199
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
if (typeof authValue === "string") {
|
|
201
|
+
credentials.authorizationHeader = authValue;
|
|
202
|
+
if (authValue.startsWith("Bearer ")) {
|
|
203
|
+
credentials.jwt = authValue.slice(7);
|
|
204
|
+
}
|
|
119
205
|
}
|
|
120
206
|
}
|
|
121
207
|
if (query) {
|
|
@@ -128,21 +214,22 @@ function extractCredentials(headers, query) {
|
|
|
128
214
|
}
|
|
129
215
|
return credentials;
|
|
130
216
|
}
|
|
131
|
-
function createGuidanceResponse(
|
|
217
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
132
218
|
const source = options.source ?? "no_credentials";
|
|
133
219
|
const isApiError = source === "api_error";
|
|
220
|
+
const urls = options.urls;
|
|
134
221
|
const guidance = isApiError ? {
|
|
135
222
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
136
|
-
registrationUrl:
|
|
137
|
-
documentationUrl:
|
|
223
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
224
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
138
225
|
steps: [
|
|
139
226
|
"Retry the request with exponential backoff",
|
|
140
227
|
"If failures persist, share the correlationId with support"
|
|
141
228
|
]
|
|
142
229
|
} : {
|
|
143
230
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
144
|
-
registrationUrl:
|
|
145
|
-
documentationUrl:
|
|
231
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
232
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
146
233
|
steps: [
|
|
147
234
|
"Register for an AstraSync account",
|
|
148
235
|
"Create and register your agent",
|
|
@@ -184,7 +271,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
184
271
|
const { credentials, ...requestData } = request;
|
|
185
272
|
const body = {
|
|
186
273
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
187
|
-
purpose: requestData.purpose
|
|
274
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
188
275
|
};
|
|
189
276
|
if (requestData.action) body.action = requestData.action;
|
|
190
277
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -218,12 +305,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
218
305
|
"Content-Type": "application/json",
|
|
219
306
|
...config.customHeaders
|
|
220
307
|
};
|
|
221
|
-
if (credentials.authorizationHeader) {
|
|
222
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
223
|
-
} else if (config.apiKey) {
|
|
224
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
225
|
-
}
|
|
226
308
|
if (config.apiKey) {
|
|
309
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
227
310
|
headers["X-API-Key"] = config.apiKey;
|
|
228
311
|
}
|
|
229
312
|
try {
|
|
@@ -268,8 +351,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
268
351
|
}
|
|
269
352
|
async function verify(config, request) {
|
|
270
353
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
354
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
271
355
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
272
|
-
|
|
356
|
+
if (mergedConfig.strictInit) {
|
|
357
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
358
|
+
} else {
|
|
359
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
360
|
+
}
|
|
273
361
|
}
|
|
274
362
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
275
363
|
deprecationWarningShown = true;
|
|
@@ -300,7 +388,8 @@ async function verify(config, request) {
|
|
|
300
388
|
if (!apiResponse.success) {
|
|
301
389
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
302
390
|
source: "api_error",
|
|
303
|
-
correlationId: apiResponse.correlationId
|
|
391
|
+
correlationId: apiResponse.correlationId,
|
|
392
|
+
urls
|
|
304
393
|
});
|
|
305
394
|
}
|
|
306
395
|
if (!apiResponse.access?.allowed) {
|
|
@@ -323,8 +412,8 @@ async function verify(config, request) {
|
|
|
323
412
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
324
413
|
guidance: {
|
|
325
414
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
326
|
-
registrationUrl:
|
|
327
|
-
documentationUrl:
|
|
415
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
416
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
328
417
|
},
|
|
329
418
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
330
419
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -393,13 +482,15 @@ async function verify(config, request) {
|
|
|
393
482
|
result.denialReasons = result.recommendationReasons || [
|
|
394
483
|
"Access denied by AstraSync recommendation"
|
|
395
484
|
];
|
|
396
|
-
|
|
397
|
-
result.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
485
|
+
result.guidance = result.runtimeChallenge ? {
|
|
486
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
487
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
488
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
489
|
+
} : {
|
|
490
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
491
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
492
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
493
|
+
};
|
|
403
494
|
} else if (result.recommendation === "step_up_required") {
|
|
404
495
|
result.requiresStepUp = true;
|
|
405
496
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -534,19 +625,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
|
|
|
534
625
|
}
|
|
535
626
|
return { value: void 0, source: void 0 };
|
|
536
627
|
}
|
|
537
|
-
function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
538
|
-
const resource =
|
|
628
|
+
function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
|
|
629
|
+
const resource = toolGate?.resource ?? requestPath;
|
|
539
630
|
let purpose;
|
|
540
631
|
let purposeSource;
|
|
541
|
-
if (
|
|
632
|
+
if (toolGate?.purpose !== void 0) {
|
|
633
|
+
purpose = toolGate.purpose;
|
|
634
|
+
purposeSource = "tool_gate";
|
|
635
|
+
} else if (headerPurpose) {
|
|
542
636
|
purpose = headerPurpose;
|
|
543
637
|
purposeSource = "header";
|
|
544
638
|
} else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
|
|
545
639
|
purpose = parsed.purposeFromBody;
|
|
546
640
|
purposeSource = parsed.purposeSourceFromBody;
|
|
547
641
|
} else {
|
|
548
|
-
purpose =
|
|
549
|
-
purposeSource =
|
|
642
|
+
purpose = void 0;
|
|
643
|
+
purposeSource = void 0;
|
|
550
644
|
}
|
|
551
645
|
let action;
|
|
552
646
|
let actionSource;
|
|
@@ -557,7 +651,7 @@ function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
|
557
651
|
action = parsed.actionFromBody;
|
|
558
652
|
actionSource = parsed.actionSourceFromBody;
|
|
559
653
|
} else {
|
|
560
|
-
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
654
|
+
action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
561
655
|
actionSource = "transport_layer";
|
|
562
656
|
}
|
|
563
657
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -571,11 +665,28 @@ function mcpRiskTier(parsed) {
|
|
|
571
665
|
}
|
|
572
666
|
|
|
573
667
|
// src/adapters/mcp.ts
|
|
668
|
+
function normalizeToolGate(gate) {
|
|
669
|
+
return typeof gate === "string" ? { minAccessLevel: gate } : gate;
|
|
670
|
+
}
|
|
574
671
|
function readSingleHeader(value) {
|
|
575
672
|
if (typeof value === "string") return value;
|
|
576
673
|
if (Array.isArray(value)) return value[0];
|
|
577
674
|
return void 0;
|
|
578
675
|
}
|
|
676
|
+
function dedupeFailures(result) {
|
|
677
|
+
if (result.failures && result.failures.length > 1) {
|
|
678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
679
|
+
result.failures = result.failures.filter((f) => {
|
|
680
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
681
|
+
if (seen.has(key)) return false;
|
|
682
|
+
seen.add(key);
|
|
683
|
+
return true;
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
687
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
688
|
+
}
|
|
689
|
+
}
|
|
579
690
|
function defaultMcpDenied(result, req, res) {
|
|
580
691
|
const id = req.body?.id ?? null;
|
|
581
692
|
const status = !result.identityVerified ? 401 : 403;
|
|
@@ -600,11 +711,17 @@ function defaultMcpDenied(result, req, res) {
|
|
|
600
711
|
});
|
|
601
712
|
}
|
|
602
713
|
function resolveMinAccessLevel(parsed, opts) {
|
|
603
|
-
if (parsed.toolName
|
|
604
|
-
|
|
714
|
+
if (!parsed.toolName) {
|
|
715
|
+
if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
|
|
716
|
+
return { level: opts.methodGates[parsed.method], source: "methodGate" };
|
|
717
|
+
}
|
|
718
|
+
return { level: "none", source: "discovery_default" };
|
|
605
719
|
}
|
|
606
|
-
if (opts.
|
|
607
|
-
return {
|
|
720
|
+
if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
|
|
721
|
+
return {
|
|
722
|
+
level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
|
|
723
|
+
source: "toolGate"
|
|
724
|
+
};
|
|
608
725
|
}
|
|
609
726
|
return { level: mcpRiskTier(parsed), source: "tier" };
|
|
610
727
|
}
|
|
@@ -615,12 +732,17 @@ function createMcpMiddleware(options) {
|
|
|
615
732
|
onAgentIdMismatch = "reject",
|
|
616
733
|
skip = false,
|
|
617
734
|
onDenied = defaultMcpDenied,
|
|
618
|
-
trustVerifiedHop =
|
|
735
|
+
trustVerifiedHop = false,
|
|
619
736
|
verifiedHopMaxAgeMs,
|
|
620
737
|
recordDecisions,
|
|
621
738
|
enableRuntimeChallenge = true,
|
|
739
|
+
failOnError = "open",
|
|
622
740
|
...config
|
|
623
741
|
} = options;
|
|
742
|
+
if (config.apiBaseUrl) {
|
|
743
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
744
|
+
});
|
|
745
|
+
}
|
|
624
746
|
return async (req, res, next) => {
|
|
625
747
|
try {
|
|
626
748
|
if (skip) return next();
|
|
@@ -633,6 +755,7 @@ function createMcpMiddleware(options) {
|
|
|
633
755
|
return next();
|
|
634
756
|
}
|
|
635
757
|
req.mcpRequest = parsed;
|
|
758
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
636
759
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
637
760
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
638
761
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -686,9 +809,17 @@ function createMcpMiddleware(options) {
|
|
|
686
809
|
}
|
|
687
810
|
return next();
|
|
688
811
|
}
|
|
812
|
+
const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
|
|
813
|
+
const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
|
|
689
814
|
const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
|
|
690
815
|
const headerAction = readSingleHeader(req.headers["x-astra-action"]);
|
|
691
|
-
const pdlss = mcpToPdlss(
|
|
816
|
+
const pdlss = mcpToPdlss(
|
|
817
|
+
parsed,
|
|
818
|
+
req.path,
|
|
819
|
+
headerPurpose,
|
|
820
|
+
headerAction,
|
|
821
|
+
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
822
|
+
);
|
|
692
823
|
if (config.debug) {
|
|
693
824
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
694
825
|
purpose_source: pdlss.purposeSource,
|
|
@@ -726,6 +857,7 @@ function createMcpMiddleware(options) {
|
|
|
726
857
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
727
858
|
});
|
|
728
859
|
}
|
|
860
|
+
dedupeFailures(result);
|
|
729
861
|
onDenied(result, req, res);
|
|
730
862
|
return;
|
|
731
863
|
}
|
|
@@ -748,6 +880,13 @@ function createMcpMiddleware(options) {
|
|
|
748
880
|
};
|
|
749
881
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
750
882
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
883
|
+
if (!result.guidance && wellKnownUrls) {
|
|
884
|
+
result.guidance = {
|
|
885
|
+
message: insufficientFailure.message,
|
|
886
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
887
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
888
|
+
};
|
|
889
|
+
}
|
|
751
890
|
if (shouldRecordDecisions) {
|
|
752
891
|
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
753
892
|
const override = {
|
|
@@ -771,6 +910,7 @@ function createMcpMiddleware(options) {
|
|
|
771
910
|
});
|
|
772
911
|
}
|
|
773
912
|
}
|
|
913
|
+
dedupeFailures(result);
|
|
774
914
|
onDenied(result, req, res);
|
|
775
915
|
return;
|
|
776
916
|
}
|
|
@@ -794,7 +934,38 @@ function createMcpMiddleware(options) {
|
|
|
794
934
|
}
|
|
795
935
|
next();
|
|
796
936
|
} catch (error) {
|
|
937
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
938
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
797
939
|
console.error("[VerificationGateway/MCP] Middleware error:", error);
|
|
940
|
+
console.warn(
|
|
941
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
942
|
+
);
|
|
943
|
+
if (failOnError === "closed") {
|
|
944
|
+
const result = {
|
|
945
|
+
identityVerified: false,
|
|
946
|
+
policyAllowed: false,
|
|
947
|
+
accessLevel: "none",
|
|
948
|
+
denialReasons: [`MCP middleware internal error: ${errorClass}`],
|
|
949
|
+
failures: [
|
|
950
|
+
{
|
|
951
|
+
dimension: "middleware.internal_error",
|
|
952
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
953
|
+
}
|
|
954
|
+
],
|
|
955
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
956
|
+
correlationId
|
|
957
|
+
};
|
|
958
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
959
|
+
if (catchUrls) {
|
|
960
|
+
result.guidance = {
|
|
961
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
962
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
963
|
+
documentationUrl: catchUrls.documentationUrl
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
dedupeFailures(result);
|
|
967
|
+
return onDenied(result, req, res);
|
|
968
|
+
}
|
|
798
969
|
next();
|
|
799
970
|
}
|
|
800
971
|
};
|