@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.js
CHANGED
|
@@ -51,7 +51,66 @@ function hasMinimumAccess(actual, required) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// src/version.ts
|
|
54
|
-
var SDK_VERSION = "2.4.
|
|
54
|
+
var SDK_VERSION = "2.4.13";
|
|
55
|
+
|
|
56
|
+
// src/well-known.ts
|
|
57
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
58
|
+
var cache = /* @__PURE__ */ new Map();
|
|
59
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
60
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
61
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
62
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
63
|
+
}
|
|
64
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
65
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
method: "GET",
|
|
68
|
+
headers: { Accept: "application/json" },
|
|
69
|
+
signal: AbortSignal.timeout(5e3)
|
|
70
|
+
});
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
85
|
+
const existing = inflight.get(apiBaseUrl);
|
|
86
|
+
if (existing) return existing;
|
|
87
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
88
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
89
|
+
inflight.delete(apiBaseUrl);
|
|
90
|
+
return data;
|
|
91
|
+
}).catch((err) => {
|
|
92
|
+
inflight.delete(apiBaseUrl);
|
|
93
|
+
throw err;
|
|
94
|
+
});
|
|
95
|
+
inflight.set(apiBaseUrl, promise);
|
|
96
|
+
return promise;
|
|
97
|
+
}
|
|
98
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
99
|
+
const entry = cache.get(apiBaseUrl);
|
|
100
|
+
if (entry) {
|
|
101
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
102
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return entry.data;
|
|
106
|
+
}
|
|
107
|
+
const pending = inflight.get(apiBaseUrl);
|
|
108
|
+
if (pending) return pending;
|
|
109
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
110
|
+
}
|
|
111
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
112
|
+
return cache.get(apiBaseUrl)?.data;
|
|
113
|
+
}
|
|
55
114
|
|
|
56
115
|
// src/verify.ts
|
|
57
116
|
var DEFAULT_CONFIG = {
|
|
@@ -70,22 +129,27 @@ var DEFAULT_CONFIG = {
|
|
|
70
129
|
};
|
|
71
130
|
var initCheckPerformed = false;
|
|
72
131
|
var deprecationWarningShown = false;
|
|
73
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
132
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
74
133
|
initCheckPerformed = true;
|
|
75
134
|
try {
|
|
76
135
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
77
136
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
78
137
|
const contentType = response.headers.get("content-type") ?? "";
|
|
79
138
|
if (contentType.startsWith("text/html")) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
139
|
+
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).`;
|
|
140
|
+
if (strictInit) {
|
|
141
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
142
|
+
}
|
|
143
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
83
144
|
} else if (debug) {
|
|
84
145
|
console.log(
|
|
85
146
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
86
147
|
);
|
|
87
148
|
}
|
|
88
149
|
} catch (err) {
|
|
150
|
+
if (strictInit) {
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
89
153
|
if (debug) {
|
|
90
154
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
91
155
|
}
|
|
@@ -109,7 +173,23 @@ function getCacheKey(request) {
|
|
|
109
173
|
request.counterpartyType || "",
|
|
110
174
|
request.isSubAgentRequest ? "1" : "0",
|
|
111
175
|
request.parentAgentId || "",
|
|
112
|
-
request.subAgentDepth ?? ""
|
|
176
|
+
request.subAgentDepth ?? "",
|
|
177
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
178
|
+
// backend verdict. Without these, two requests with different
|
|
179
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
180
|
+
// the shorter-duration allow served the longer-duration request.
|
|
181
|
+
request.durationRequired ?? "",
|
|
182
|
+
request.invocationProtocol || "",
|
|
183
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
184
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
185
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
186
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
187
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
188
|
+
// be cached across IPs).
|
|
189
|
+
request.callerMetadata?.sourceIp || "",
|
|
190
|
+
request.callerMetadata?.userAgent || "",
|
|
191
|
+
request.callerMetadata?.forwardedFor || "",
|
|
192
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
113
193
|
].join("|");
|
|
114
194
|
}
|
|
115
195
|
function getCachedResult(request) {
|
|
@@ -135,9 +215,13 @@ function cacheResult(request, result, configuredTtl) {
|
|
|
135
215
|
}
|
|
136
216
|
function extractCredentials(headers, query) {
|
|
137
217
|
const credentials = {};
|
|
218
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
138
219
|
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"];
|
|
139
220
|
if (astraIdHeader) {
|
|
140
|
-
|
|
221
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
222
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
223
|
+
credentials.astraId = raw;
|
|
224
|
+
}
|
|
141
225
|
}
|
|
142
226
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
143
227
|
if (apiKeyHeader) {
|
|
@@ -146,9 +230,11 @@ function extractCredentials(headers, query) {
|
|
|
146
230
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
147
231
|
if (authHeader) {
|
|
148
232
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
233
|
+
if (typeof authValue === "string") {
|
|
234
|
+
credentials.authorizationHeader = authValue;
|
|
235
|
+
if (authValue.startsWith("Bearer ")) {
|
|
236
|
+
credentials.jwt = authValue.slice(7);
|
|
237
|
+
}
|
|
152
238
|
}
|
|
153
239
|
}
|
|
154
240
|
if (query) {
|
|
@@ -161,21 +247,22 @@ function extractCredentials(headers, query) {
|
|
|
161
247
|
}
|
|
162
248
|
return credentials;
|
|
163
249
|
}
|
|
164
|
-
function createGuidanceResponse(
|
|
250
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
165
251
|
const source = options.source ?? "no_credentials";
|
|
166
252
|
const isApiError = source === "api_error";
|
|
253
|
+
const urls = options.urls;
|
|
167
254
|
const guidance = isApiError ? {
|
|
168
255
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
169
|
-
registrationUrl:
|
|
170
|
-
documentationUrl:
|
|
256
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
257
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
171
258
|
steps: [
|
|
172
259
|
"Retry the request with exponential backoff",
|
|
173
260
|
"If failures persist, share the correlationId with support"
|
|
174
261
|
]
|
|
175
262
|
} : {
|
|
176
263
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
177
|
-
registrationUrl:
|
|
178
|
-
documentationUrl:
|
|
264
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
265
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
179
266
|
steps: [
|
|
180
267
|
"Register for an AstraSync account",
|
|
181
268
|
"Create and register your agent",
|
|
@@ -217,7 +304,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
217
304
|
const { credentials, ...requestData } = request;
|
|
218
305
|
const body = {
|
|
219
306
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
220
|
-
purpose: requestData.purpose
|
|
307
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
221
308
|
};
|
|
222
309
|
if (requestData.action) body.action = requestData.action;
|
|
223
310
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -251,12 +338,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
251
338
|
"Content-Type": "application/json",
|
|
252
339
|
...config.customHeaders
|
|
253
340
|
};
|
|
254
|
-
if (credentials.authorizationHeader) {
|
|
255
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
256
|
-
} else if (config.apiKey) {
|
|
257
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
258
|
-
}
|
|
259
341
|
if (config.apiKey) {
|
|
342
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
260
343
|
headers["X-API-Key"] = config.apiKey;
|
|
261
344
|
}
|
|
262
345
|
try {
|
|
@@ -301,8 +384,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
301
384
|
}
|
|
302
385
|
async function verify(config, request) {
|
|
303
386
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
387
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
304
388
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
305
|
-
|
|
389
|
+
if (mergedConfig.strictInit) {
|
|
390
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
391
|
+
} else {
|
|
392
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
393
|
+
}
|
|
306
394
|
}
|
|
307
395
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
308
396
|
deprecationWarningShown = true;
|
|
@@ -333,7 +421,8 @@ async function verify(config, request) {
|
|
|
333
421
|
if (!apiResponse.success) {
|
|
334
422
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
335
423
|
source: "api_error",
|
|
336
|
-
correlationId: apiResponse.correlationId
|
|
424
|
+
correlationId: apiResponse.correlationId,
|
|
425
|
+
urls
|
|
337
426
|
});
|
|
338
427
|
}
|
|
339
428
|
if (!apiResponse.access?.allowed) {
|
|
@@ -356,8 +445,8 @@ async function verify(config, request) {
|
|
|
356
445
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
357
446
|
guidance: {
|
|
358
447
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
359
|
-
registrationUrl:
|
|
360
|
-
documentationUrl:
|
|
448
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
449
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
361
450
|
},
|
|
362
451
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
363
452
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -426,13 +515,15 @@ async function verify(config, request) {
|
|
|
426
515
|
result.denialReasons = result.recommendationReasons || [
|
|
427
516
|
"Access denied by AstraSync recommendation"
|
|
428
517
|
];
|
|
429
|
-
|
|
430
|
-
result.
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
518
|
+
result.guidance = result.runtimeChallenge ? {
|
|
519
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
520
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
521
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
522
|
+
} : {
|
|
523
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
524
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
525
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
526
|
+
};
|
|
436
527
|
} else if (result.recommendation === "step_up_required") {
|
|
437
528
|
result.requiresStepUp = true;
|
|
438
529
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -567,19 +658,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
|
|
|
567
658
|
}
|
|
568
659
|
return { value: void 0, source: void 0 };
|
|
569
660
|
}
|
|
570
|
-
function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
571
|
-
const resource =
|
|
661
|
+
function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
|
|
662
|
+
const resource = toolGate?.resource ?? requestPath;
|
|
572
663
|
let purpose;
|
|
573
664
|
let purposeSource;
|
|
574
|
-
if (
|
|
665
|
+
if (toolGate?.purpose !== void 0) {
|
|
666
|
+
purpose = toolGate.purpose;
|
|
667
|
+
purposeSource = "tool_gate";
|
|
668
|
+
} else if (headerPurpose) {
|
|
575
669
|
purpose = headerPurpose;
|
|
576
670
|
purposeSource = "header";
|
|
577
671
|
} else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
|
|
578
672
|
purpose = parsed.purposeFromBody;
|
|
579
673
|
purposeSource = parsed.purposeSourceFromBody;
|
|
580
674
|
} else {
|
|
581
|
-
purpose =
|
|
582
|
-
purposeSource =
|
|
675
|
+
purpose = void 0;
|
|
676
|
+
purposeSource = void 0;
|
|
583
677
|
}
|
|
584
678
|
let action;
|
|
585
679
|
let actionSource;
|
|
@@ -590,7 +684,7 @@ function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
|
590
684
|
action = parsed.actionFromBody;
|
|
591
685
|
actionSource = parsed.actionSourceFromBody;
|
|
592
686
|
} else {
|
|
593
|
-
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
687
|
+
action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
594
688
|
actionSource = "transport_layer";
|
|
595
689
|
}
|
|
596
690
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -604,11 +698,28 @@ function mcpRiskTier(parsed) {
|
|
|
604
698
|
}
|
|
605
699
|
|
|
606
700
|
// src/adapters/mcp.ts
|
|
701
|
+
function normalizeToolGate(gate) {
|
|
702
|
+
return typeof gate === "string" ? { minAccessLevel: gate } : gate;
|
|
703
|
+
}
|
|
607
704
|
function readSingleHeader(value) {
|
|
608
705
|
if (typeof value === "string") return value;
|
|
609
706
|
if (Array.isArray(value)) return value[0];
|
|
610
707
|
return void 0;
|
|
611
708
|
}
|
|
709
|
+
function dedupeFailures(result) {
|
|
710
|
+
if (result.failures && result.failures.length > 1) {
|
|
711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
712
|
+
result.failures = result.failures.filter((f) => {
|
|
713
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
714
|
+
if (seen.has(key)) return false;
|
|
715
|
+
seen.add(key);
|
|
716
|
+
return true;
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
720
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
612
723
|
function defaultMcpDenied(result, req, res) {
|
|
613
724
|
const id = req.body?.id ?? null;
|
|
614
725
|
const status = !result.identityVerified ? 401 : 403;
|
|
@@ -633,11 +744,17 @@ function defaultMcpDenied(result, req, res) {
|
|
|
633
744
|
});
|
|
634
745
|
}
|
|
635
746
|
function resolveMinAccessLevel(parsed, opts) {
|
|
636
|
-
if (parsed.toolName
|
|
637
|
-
|
|
747
|
+
if (!parsed.toolName) {
|
|
748
|
+
if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
|
|
749
|
+
return { level: opts.methodGates[parsed.method], source: "methodGate" };
|
|
750
|
+
}
|
|
751
|
+
return { level: "none", source: "discovery_default" };
|
|
638
752
|
}
|
|
639
|
-
if (opts.
|
|
640
|
-
return {
|
|
753
|
+
if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
|
|
754
|
+
return {
|
|
755
|
+
level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
|
|
756
|
+
source: "toolGate"
|
|
757
|
+
};
|
|
641
758
|
}
|
|
642
759
|
return { level: mcpRiskTier(parsed), source: "tier" };
|
|
643
760
|
}
|
|
@@ -648,12 +765,17 @@ function createMcpMiddleware(options) {
|
|
|
648
765
|
onAgentIdMismatch = "reject",
|
|
649
766
|
skip = false,
|
|
650
767
|
onDenied = defaultMcpDenied,
|
|
651
|
-
trustVerifiedHop =
|
|
768
|
+
trustVerifiedHop = false,
|
|
652
769
|
verifiedHopMaxAgeMs,
|
|
653
770
|
recordDecisions,
|
|
654
771
|
enableRuntimeChallenge = true,
|
|
772
|
+
failOnError = "open",
|
|
655
773
|
...config
|
|
656
774
|
} = options;
|
|
775
|
+
if (config.apiBaseUrl) {
|
|
776
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
777
|
+
});
|
|
778
|
+
}
|
|
657
779
|
return async (req, res, next) => {
|
|
658
780
|
try {
|
|
659
781
|
if (skip) return next();
|
|
@@ -666,6 +788,7 @@ function createMcpMiddleware(options) {
|
|
|
666
788
|
return next();
|
|
667
789
|
}
|
|
668
790
|
req.mcpRequest = parsed;
|
|
791
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
669
792
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
670
793
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
671
794
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -719,9 +842,17 @@ function createMcpMiddleware(options) {
|
|
|
719
842
|
}
|
|
720
843
|
return next();
|
|
721
844
|
}
|
|
845
|
+
const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
|
|
846
|
+
const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
|
|
722
847
|
const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
|
|
723
848
|
const headerAction = readSingleHeader(req.headers["x-astra-action"]);
|
|
724
|
-
const pdlss = mcpToPdlss(
|
|
849
|
+
const pdlss = mcpToPdlss(
|
|
850
|
+
parsed,
|
|
851
|
+
req.path,
|
|
852
|
+
headerPurpose,
|
|
853
|
+
headerAction,
|
|
854
|
+
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
855
|
+
);
|
|
725
856
|
if (config.debug) {
|
|
726
857
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
727
858
|
purpose_source: pdlss.purposeSource,
|
|
@@ -759,6 +890,7 @@ function createMcpMiddleware(options) {
|
|
|
759
890
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
760
891
|
});
|
|
761
892
|
}
|
|
893
|
+
dedupeFailures(result);
|
|
762
894
|
onDenied(result, req, res);
|
|
763
895
|
return;
|
|
764
896
|
}
|
|
@@ -781,6 +913,13 @@ function createMcpMiddleware(options) {
|
|
|
781
913
|
};
|
|
782
914
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
783
915
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
916
|
+
if (!result.guidance && wellKnownUrls) {
|
|
917
|
+
result.guidance = {
|
|
918
|
+
message: insufficientFailure.message,
|
|
919
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
920
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
921
|
+
};
|
|
922
|
+
}
|
|
784
923
|
if (shouldRecordDecisions) {
|
|
785
924
|
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
786
925
|
const override = {
|
|
@@ -804,6 +943,7 @@ function createMcpMiddleware(options) {
|
|
|
804
943
|
});
|
|
805
944
|
}
|
|
806
945
|
}
|
|
946
|
+
dedupeFailures(result);
|
|
807
947
|
onDenied(result, req, res);
|
|
808
948
|
return;
|
|
809
949
|
}
|
|
@@ -827,7 +967,38 @@ function createMcpMiddleware(options) {
|
|
|
827
967
|
}
|
|
828
968
|
next();
|
|
829
969
|
} catch (error) {
|
|
970
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
971
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
830
972
|
console.error("[VerificationGateway/MCP] Middleware error:", error);
|
|
973
|
+
console.warn(
|
|
974
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
975
|
+
);
|
|
976
|
+
if (failOnError === "closed") {
|
|
977
|
+
const result = {
|
|
978
|
+
identityVerified: false,
|
|
979
|
+
policyAllowed: false,
|
|
980
|
+
accessLevel: "none",
|
|
981
|
+
denialReasons: [`MCP middleware internal error: ${errorClass}`],
|
|
982
|
+
failures: [
|
|
983
|
+
{
|
|
984
|
+
dimension: "middleware.internal_error",
|
|
985
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
986
|
+
}
|
|
987
|
+
],
|
|
988
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
989
|
+
correlationId
|
|
990
|
+
};
|
|
991
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
992
|
+
if (catchUrls) {
|
|
993
|
+
result.guidance = {
|
|
994
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
995
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
996
|
+
documentationUrl: catchUrls.documentationUrl
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
dedupeFailures(result);
|
|
1000
|
+
return onDenied(result, req, res);
|
|
1001
|
+
}
|
|
831
1002
|
next();
|
|
832
1003
|
}
|
|
833
1004
|
};
|