@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/index.js
CHANGED
|
@@ -53,11 +53,14 @@ __export(src_exports, {
|
|
|
53
53
|
extractCredentials: () => extractCredentials,
|
|
54
54
|
extractMcpCredentials: () => extractMcpCredentials,
|
|
55
55
|
getAccessLevelForScore: () => getAccessLevelForScore,
|
|
56
|
+
getCachedWellKnownUrls: () => getCachedWellKnownUrls,
|
|
56
57
|
getCapabilities: () => getCapabilities,
|
|
57
58
|
getTrustLevel: () => getTrustLevel,
|
|
59
|
+
getWellKnownUrls: () => getWellKnownUrls,
|
|
58
60
|
hasCredentials: () => hasCredentials,
|
|
59
61
|
hasMinimumAccess: () => hasMinimumAccess,
|
|
60
62
|
nextjs: () => nextjs_exports,
|
|
63
|
+
prefetchWellKnown: () => prefetchWellKnown,
|
|
61
64
|
quickVerify: () => quickVerify,
|
|
62
65
|
recordDecision: () => recordDecision2,
|
|
63
66
|
sdk: () => sdk_exports,
|
|
@@ -189,7 +192,66 @@ function getCapabilities(accessLevel) {
|
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
// src/version.ts
|
|
192
|
-
var SDK_VERSION = "2.4.
|
|
195
|
+
var SDK_VERSION = "2.4.13";
|
|
196
|
+
|
|
197
|
+
// src/well-known.ts
|
|
198
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
199
|
+
var cache = /* @__PURE__ */ new Map();
|
|
200
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
201
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
202
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
203
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
204
|
+
}
|
|
205
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
206
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
207
|
+
const response = await fetch(url, {
|
|
208
|
+
method: "GET",
|
|
209
|
+
headers: { Accept: "application/json" },
|
|
210
|
+
signal: AbortSignal.timeout(5e3)
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
const data = await response.json();
|
|
218
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return data;
|
|
224
|
+
}
|
|
225
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
226
|
+
const existing = inflight.get(apiBaseUrl);
|
|
227
|
+
if (existing) return existing;
|
|
228
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
229
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
230
|
+
inflight.delete(apiBaseUrl);
|
|
231
|
+
return data;
|
|
232
|
+
}).catch((err) => {
|
|
233
|
+
inflight.delete(apiBaseUrl);
|
|
234
|
+
throw err;
|
|
235
|
+
});
|
|
236
|
+
inflight.set(apiBaseUrl, promise);
|
|
237
|
+
return promise;
|
|
238
|
+
}
|
|
239
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
240
|
+
const entry = cache.get(apiBaseUrl);
|
|
241
|
+
if (entry) {
|
|
242
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
243
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return entry.data;
|
|
247
|
+
}
|
|
248
|
+
const pending = inflight.get(apiBaseUrl);
|
|
249
|
+
if (pending) return pending;
|
|
250
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
251
|
+
}
|
|
252
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
253
|
+
return cache.get(apiBaseUrl)?.data;
|
|
254
|
+
}
|
|
193
255
|
|
|
194
256
|
// src/verify.ts
|
|
195
257
|
var DEFAULT_CONFIG = {
|
|
@@ -208,22 +270,27 @@ var DEFAULT_CONFIG = {
|
|
|
208
270
|
};
|
|
209
271
|
var initCheckPerformed = false;
|
|
210
272
|
var deprecationWarningShown = false;
|
|
211
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
273
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
212
274
|
initCheckPerformed = true;
|
|
213
275
|
try {
|
|
214
276
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
215
277
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
216
278
|
const contentType = response.headers.get("content-type") ?? "";
|
|
217
279
|
if (contentType.startsWith("text/html")) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
280
|
+
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).`;
|
|
281
|
+
if (strictInit) {
|
|
282
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
283
|
+
}
|
|
284
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
221
285
|
} else if (debug) {
|
|
222
286
|
console.log(
|
|
223
287
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
224
288
|
);
|
|
225
289
|
}
|
|
226
290
|
} catch (err) {
|
|
291
|
+
if (strictInit) {
|
|
292
|
+
throw err;
|
|
293
|
+
}
|
|
227
294
|
if (debug) {
|
|
228
295
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
229
296
|
}
|
|
@@ -247,7 +314,23 @@ function getCacheKey(request) {
|
|
|
247
314
|
request.counterpartyType || "",
|
|
248
315
|
request.isSubAgentRequest ? "1" : "0",
|
|
249
316
|
request.parentAgentId || "",
|
|
250
|
-
request.subAgentDepth ?? ""
|
|
317
|
+
request.subAgentDepth ?? "",
|
|
318
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
319
|
+
// backend verdict. Without these, two requests with different
|
|
320
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
321
|
+
// the shorter-duration allow served the longer-duration request.
|
|
322
|
+
request.durationRequired ?? "",
|
|
323
|
+
request.invocationProtocol || "",
|
|
324
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
325
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
326
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
327
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
328
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
329
|
+
// be cached across IPs).
|
|
330
|
+
request.callerMetadata?.sourceIp || "",
|
|
331
|
+
request.callerMetadata?.userAgent || "",
|
|
332
|
+
request.callerMetadata?.forwardedFor || "",
|
|
333
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
251
334
|
].join("|");
|
|
252
335
|
}
|
|
253
336
|
function getCachedResult(request) {
|
|
@@ -276,9 +359,13 @@ function clearCache() {
|
|
|
276
359
|
}
|
|
277
360
|
function extractCredentials(headers, query) {
|
|
278
361
|
const credentials = {};
|
|
362
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
279
363
|
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"];
|
|
280
364
|
if (astraIdHeader) {
|
|
281
|
-
|
|
365
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
366
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
367
|
+
credentials.astraId = raw;
|
|
368
|
+
}
|
|
282
369
|
}
|
|
283
370
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
284
371
|
if (apiKeyHeader) {
|
|
@@ -287,9 +374,11 @@ function extractCredentials(headers, query) {
|
|
|
287
374
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
288
375
|
if (authHeader) {
|
|
289
376
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
377
|
+
if (typeof authValue === "string") {
|
|
378
|
+
credentials.authorizationHeader = authValue;
|
|
379
|
+
if (authValue.startsWith("Bearer ")) {
|
|
380
|
+
credentials.jwt = authValue.slice(7);
|
|
381
|
+
}
|
|
293
382
|
}
|
|
294
383
|
}
|
|
295
384
|
if (query) {
|
|
@@ -305,21 +394,22 @@ function extractCredentials(headers, query) {
|
|
|
305
394
|
function hasCredentials(credentials) {
|
|
306
395
|
return !!(credentials.astraId || credentials.apiKey || credentials.jwt);
|
|
307
396
|
}
|
|
308
|
-
function createGuidanceResponse(
|
|
397
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
309
398
|
const source = options.source ?? "no_credentials";
|
|
310
399
|
const isApiError = source === "api_error";
|
|
400
|
+
const urls = options.urls;
|
|
311
401
|
const guidance = isApiError ? {
|
|
312
402
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
313
|
-
registrationUrl:
|
|
314
|
-
documentationUrl:
|
|
403
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
404
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
315
405
|
steps: [
|
|
316
406
|
"Retry the request with exponential backoff",
|
|
317
407
|
"If failures persist, share the correlationId with support"
|
|
318
408
|
]
|
|
319
409
|
} : {
|
|
320
410
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
321
|
-
registrationUrl:
|
|
322
|
-
documentationUrl:
|
|
411
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
412
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
323
413
|
steps: [
|
|
324
414
|
"Register for an AstraSync account",
|
|
325
415
|
"Create and register your agent",
|
|
@@ -361,7 +451,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
361
451
|
const { credentials, ...requestData } = request;
|
|
362
452
|
const body = {
|
|
363
453
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
364
|
-
purpose: requestData.purpose
|
|
454
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
365
455
|
};
|
|
366
456
|
if (requestData.action) body.action = requestData.action;
|
|
367
457
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -395,12 +485,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
395
485
|
"Content-Type": "application/json",
|
|
396
486
|
...config.customHeaders
|
|
397
487
|
};
|
|
398
|
-
if (credentials.authorizationHeader) {
|
|
399
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
400
|
-
} else if (config.apiKey) {
|
|
401
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
402
|
-
}
|
|
403
488
|
if (config.apiKey) {
|
|
489
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
404
490
|
headers["X-API-Key"] = config.apiKey;
|
|
405
491
|
}
|
|
406
492
|
try {
|
|
@@ -445,8 +531,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
445
531
|
}
|
|
446
532
|
async function verify(config, request) {
|
|
447
533
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
534
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
448
535
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
449
|
-
|
|
536
|
+
if (mergedConfig.strictInit) {
|
|
537
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
538
|
+
} else {
|
|
539
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
540
|
+
}
|
|
450
541
|
}
|
|
451
542
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
452
543
|
deprecationWarningShown = true;
|
|
@@ -477,7 +568,8 @@ async function verify(config, request) {
|
|
|
477
568
|
if (!apiResponse.success) {
|
|
478
569
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
479
570
|
source: "api_error",
|
|
480
|
-
correlationId: apiResponse.correlationId
|
|
571
|
+
correlationId: apiResponse.correlationId,
|
|
572
|
+
urls
|
|
481
573
|
});
|
|
482
574
|
}
|
|
483
575
|
if (!apiResponse.access?.allowed) {
|
|
@@ -500,8 +592,8 @@ async function verify(config, request) {
|
|
|
500
592
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
501
593
|
guidance: {
|
|
502
594
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
503
|
-
registrationUrl:
|
|
504
|
-
documentationUrl:
|
|
595
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
596
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
505
597
|
},
|
|
506
598
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
507
599
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -570,13 +662,15 @@ async function verify(config, request) {
|
|
|
570
662
|
result.denialReasons = result.recommendationReasons || [
|
|
571
663
|
"Access denied by AstraSync recommendation"
|
|
572
664
|
];
|
|
573
|
-
|
|
574
|
-
result.
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
665
|
+
result.guidance = result.runtimeChallenge ? {
|
|
666
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
667
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
668
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
669
|
+
} : {
|
|
670
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
671
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
672
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
673
|
+
};
|
|
580
674
|
} else if (result.recommendation === "step_up_required") {
|
|
581
675
|
result.requiresStepUp = true;
|
|
582
676
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -825,18 +919,43 @@ function defaultExtractPurpose(req) {
|
|
|
825
919
|
return "general";
|
|
826
920
|
}
|
|
827
921
|
}
|
|
828
|
-
function matchRoute(pattern, path) {
|
|
922
|
+
function matchRoute(pattern, path, opts) {
|
|
829
923
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
830
|
-
const
|
|
831
|
-
|
|
924
|
+
const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
|
|
925
|
+
const caseSensitiveResult = caseSensitiveRegex.test(path);
|
|
926
|
+
if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
|
|
927
|
+
return caseSensitiveResult;
|
|
928
|
+
}
|
|
929
|
+
const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
930
|
+
const caseInsensitiveResult = caseInsensitiveRegex.test(path);
|
|
931
|
+
if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
|
|
932
|
+
console.warn(
|
|
933
|
+
`[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
|
|
832
937
|
}
|
|
833
|
-
function findRouteConfig(routes, path, method) {
|
|
938
|
+
function findRouteConfig(routes, path, method, opts) {
|
|
834
939
|
return routes.find((route) => {
|
|
835
940
|
const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
|
|
836
|
-
const pathMatches = matchRoute(route.pattern, path);
|
|
941
|
+
const pathMatches = matchRoute(route.pattern, path, opts);
|
|
837
942
|
return methodMatches && pathMatches;
|
|
838
943
|
});
|
|
839
944
|
}
|
|
945
|
+
function dedupeFailures(result) {
|
|
946
|
+
if (result.failures && result.failures.length > 1) {
|
|
947
|
+
const seen = /* @__PURE__ */ new Set();
|
|
948
|
+
result.failures = result.failures.filter((f) => {
|
|
949
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
950
|
+
if (seen.has(key)) return false;
|
|
951
|
+
seen.add(key);
|
|
952
|
+
return true;
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
956
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
840
959
|
function defaultOnDenied(result, _req, res) {
|
|
841
960
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
842
961
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
@@ -863,8 +982,14 @@ function createMiddleware(options) {
|
|
|
863
982
|
recordDecisions,
|
|
864
983
|
enableRuntimeChallenge = true,
|
|
865
984
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
985
|
+
failOnError = "open",
|
|
986
|
+
caseInsensitiveRouteMatch = false,
|
|
866
987
|
...config
|
|
867
988
|
} = options;
|
|
989
|
+
if (config.apiBaseUrl) {
|
|
990
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
991
|
+
});
|
|
992
|
+
}
|
|
868
993
|
let cachedRoutes = [];
|
|
869
994
|
let lastFetchAt = 0;
|
|
870
995
|
let refreshing = null;
|
|
@@ -885,7 +1010,7 @@ function createMiddleware(options) {
|
|
|
885
1010
|
cachedRoutes = fetched;
|
|
886
1011
|
lastFetchAt = Date.now();
|
|
887
1012
|
if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
|
|
888
|
-
const dashboard = config.dashboardUrl ?? "https://
|
|
1013
|
+
const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
|
|
889
1014
|
console.warn(
|
|
890
1015
|
`[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
|
|
891
1016
|
);
|
|
@@ -911,7 +1036,12 @@ function createMiddleware(options) {
|
|
|
911
1036
|
refreshing = null;
|
|
912
1037
|
});
|
|
913
1038
|
}
|
|
914
|
-
const
|
|
1039
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
1040
|
+
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
1041
|
+
caseInsensitive: caseInsensitiveRouteMatch,
|
|
1042
|
+
logShadowDivergence: true,
|
|
1043
|
+
correlationId
|
|
1044
|
+
});
|
|
915
1045
|
if (!routeConfig) {
|
|
916
1046
|
if (config.setPassThroughHeader) {
|
|
917
1047
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
@@ -922,6 +1052,7 @@ function createMiddleware(options) {
|
|
|
922
1052
|
}
|
|
923
1053
|
return next();
|
|
924
1054
|
}
|
|
1055
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
925
1056
|
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
926
1057
|
const shouldEnforce = routeConfig.minAccessLevel !== "none";
|
|
927
1058
|
if (routeConfig.minAccessLevel === "none" && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
@@ -943,8 +1074,8 @@ function createMiddleware(options) {
|
|
|
943
1074
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
944
1075
|
guidance: {
|
|
945
1076
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
946
|
-
registrationUrl:
|
|
947
|
-
documentationUrl:
|
|
1077
|
+
registrationUrl: wellKnownUrls?.registrationUrl ?? "",
|
|
1078
|
+
documentationUrl: wellKnownUrls?.documentationUrl ?? ""
|
|
948
1079
|
},
|
|
949
1080
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
950
1081
|
};
|
|
@@ -958,13 +1089,19 @@ function createMiddleware(options) {
|
|
|
958
1089
|
requestMethod: req.method
|
|
959
1090
|
}).catch(() => {
|
|
960
1091
|
});
|
|
1092
|
+
dedupeFailures(result2);
|
|
961
1093
|
onDenied(result2, req, res);
|
|
962
1094
|
return;
|
|
963
1095
|
}
|
|
964
1096
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
965
1097
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
966
1098
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
967
|
-
const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() :
|
|
1099
|
+
const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1100
|
+
if (!req.ip && forwardedForStr) {
|
|
1101
|
+
console.warn(
|
|
1102
|
+
"[VerificationGateway] req.ip unset \u2014 falling back to leftmost X-Forwarded-For. Configure Express trust proxy correctly to avoid spoofable client IPs in audit logs."
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
968
1105
|
const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
|
|
969
1106
|
const result = await verify(config, {
|
|
970
1107
|
credentials,
|
|
@@ -995,6 +1132,7 @@ function createMiddleware(options) {
|
|
|
995
1132
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
996
1133
|
});
|
|
997
1134
|
}
|
|
1135
|
+
dedupeFailures(result);
|
|
998
1136
|
onDenied(result, req, res);
|
|
999
1137
|
return;
|
|
1000
1138
|
}
|
|
@@ -1017,10 +1155,18 @@ function createMiddleware(options) {
|
|
|
1017
1155
|
};
|
|
1018
1156
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
1019
1157
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
1158
|
+
if (!result.guidance && wellKnownUrls) {
|
|
1159
|
+
result.guidance = {
|
|
1160
|
+
message: insufficientFailure.message,
|
|
1161
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
1162
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1020
1165
|
if (shouldRecordDecisions && sessionId) {
|
|
1021
1166
|
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
1022
1167
|
});
|
|
1023
1168
|
}
|
|
1169
|
+
dedupeFailures(result);
|
|
1024
1170
|
onDenied(result, req, res);
|
|
1025
1171
|
return;
|
|
1026
1172
|
}
|
|
@@ -1033,10 +1179,18 @@ function createMiddleware(options) {
|
|
|
1033
1179
|
};
|
|
1034
1180
|
result.failures = [...result.failures ?? [], trustFailure];
|
|
1035
1181
|
result.denialReasons = [trustFailure.message];
|
|
1182
|
+
if (!result.guidance && wellKnownUrls) {
|
|
1183
|
+
result.guidance = {
|
|
1184
|
+
message: trustFailure.message,
|
|
1185
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
1186
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1036
1189
|
if (shouldRecordDecisions && sessionId) {
|
|
1037
1190
|
recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
|
|
1038
1191
|
});
|
|
1039
1192
|
}
|
|
1193
|
+
dedupeFailures(result);
|
|
1040
1194
|
onDenied(result, req, res);
|
|
1041
1195
|
return;
|
|
1042
1196
|
}
|
|
@@ -1051,7 +1205,38 @@ function createMiddleware(options) {
|
|
|
1051
1205
|
}
|
|
1052
1206
|
next();
|
|
1053
1207
|
} catch (error) {
|
|
1208
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
1209
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1054
1210
|
console.error("[VerificationGateway] Middleware error:", error);
|
|
1211
|
+
console.warn(
|
|
1212
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
1213
|
+
);
|
|
1214
|
+
if (failOnError === "closed") {
|
|
1215
|
+
const result = {
|
|
1216
|
+
identityVerified: false,
|
|
1217
|
+
policyAllowed: false,
|
|
1218
|
+
accessLevel: "none",
|
|
1219
|
+
denialReasons: [`Verification middleware internal error: ${errorClass}`],
|
|
1220
|
+
failures: [
|
|
1221
|
+
{
|
|
1222
|
+
dimension: "middleware.internal_error",
|
|
1223
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
1224
|
+
}
|
|
1225
|
+
],
|
|
1226
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
1227
|
+
correlationId
|
|
1228
|
+
};
|
|
1229
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
1230
|
+
if (catchUrls) {
|
|
1231
|
+
result.guidance = {
|
|
1232
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
1233
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
1234
|
+
documentationUrl: catchUrls.documentationUrl
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
dedupeFailures(result);
|
|
1238
|
+
return onDenied(result, req, res);
|
|
1239
|
+
}
|
|
1055
1240
|
next();
|
|
1056
1241
|
}
|
|
1057
1242
|
};
|
|
@@ -1063,6 +1248,18 @@ __export(nextjs_exports, {
|
|
|
1063
1248
|
createMatcherConfig: () => createMatcherConfig,
|
|
1064
1249
|
createMiddleware: () => createMiddleware2
|
|
1065
1250
|
});
|
|
1251
|
+
function escapeHtml(value) {
|
|
1252
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1253
|
+
}
|
|
1254
|
+
function sanitizeUrl(value, fallback) {
|
|
1255
|
+
if (typeof value !== "string" || value.length === 0) return escapeHtml(fallback);
|
|
1256
|
+
const trimmed = value.trim();
|
|
1257
|
+
if (/^javascript:|^data:|^vbscript:/i.test(trimmed)) return escapeHtml(fallback);
|
|
1258
|
+
if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("/")) {
|
|
1259
|
+
return escapeHtml(trimmed);
|
|
1260
|
+
}
|
|
1261
|
+
return escapeHtml(fallback);
|
|
1262
|
+
}
|
|
1066
1263
|
function extractCredentialsFromNextRequest(request) {
|
|
1067
1264
|
const credentials = {};
|
|
1068
1265
|
const astraId = request.headers.get("x-astra-id") || request.headers.get("X-Astra-Id");
|
|
@@ -1134,10 +1331,18 @@ function extractPurpose(request) {
|
|
|
1134
1331
|
}
|
|
1135
1332
|
}
|
|
1136
1333
|
function generateCommerceShieldHtml(result, options) {
|
|
1137
|
-
const title = options.commerceShield?.title || "AstraSync Agent Verification";
|
|
1138
|
-
const message =
|
|
1139
|
-
|
|
1140
|
-
|
|
1334
|
+
const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
|
|
1335
|
+
const message = escapeHtml(
|
|
1336
|
+
options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
|
|
1337
|
+
);
|
|
1338
|
+
const registrationUrl = sanitizeUrl(
|
|
1339
|
+
result.guidance?.registrationUrl,
|
|
1340
|
+
"https://astrasync.ai/register"
|
|
1341
|
+
);
|
|
1342
|
+
const docsUrl = sanitizeUrl(
|
|
1343
|
+
result.guidance?.documentationUrl,
|
|
1344
|
+
"https://astrasync.ai/docs/agent-access"
|
|
1345
|
+
);
|
|
1141
1346
|
const allowGuest = options.commerceShield?.allowGuestAccess ?? true;
|
|
1142
1347
|
return `
|
|
1143
1348
|
<!DOCTYPE html>
|
|
@@ -1259,7 +1464,7 @@ function generateCommerceShieldHtml(result, options) {
|
|
|
1259
1464
|
<div class="shield-steps">
|
|
1260
1465
|
<h3>To get verified access:</h3>
|
|
1261
1466
|
<ol>
|
|
1262
|
-
<li>Register at <a href="${registrationUrl}">astrasync.ai/register</a></li>
|
|
1467
|
+
<li>Register at <a href="${registrationUrl}">astrasync.ai/agents/register</a></li>
|
|
1263
1468
|
<li>Create and register your agent</li>
|
|
1264
1469
|
<li>Add your ASTRA-ID to request headers</li>
|
|
1265
1470
|
<li>Refresh this page</li>
|
|
@@ -1347,7 +1552,7 @@ function createMiddleware2(options) {
|
|
|
1347
1552
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
1348
1553
|
guidance: {
|
|
1349
1554
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
1350
|
-
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
|
|
1555
|
+
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
1351
1556
|
documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
1352
1557
|
},
|
|
1353
1558
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -2008,12 +2213,45 @@ function bufferToBase64(bytes) {
|
|
|
2008
2213
|
|
|
2009
2214
|
// src/transport/rfc9421-verify.ts
|
|
2010
2215
|
var import_http_message_signatures = require("http-message-signatures");
|
|
2216
|
+
|
|
2217
|
+
// src/transport/nonce-store.ts
|
|
2218
|
+
var InMemoryNonceStore = class {
|
|
2219
|
+
constructor(capacity = 1e4) {
|
|
2220
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
2221
|
+
this.lastSweepMs = 0;
|
|
2222
|
+
this.capacity = capacity;
|
|
2223
|
+
}
|
|
2224
|
+
seen(key, expiresAtMs) {
|
|
2225
|
+
const nowMs = Date.now();
|
|
2226
|
+
if (nowMs - this.lastSweepMs > 1e3) {
|
|
2227
|
+
for (const [k, exp] of this.entries) {
|
|
2228
|
+
if (exp <= nowMs) this.entries.delete(k);
|
|
2229
|
+
}
|
|
2230
|
+
this.lastSweepMs = nowMs;
|
|
2231
|
+
}
|
|
2232
|
+
const existing = this.entries.get(key);
|
|
2233
|
+
if (existing !== void 0 && existing > nowMs) {
|
|
2234
|
+
return true;
|
|
2235
|
+
}
|
|
2236
|
+
if (this.entries.size >= this.capacity) {
|
|
2237
|
+
const oldest = this.entries.keys().next().value;
|
|
2238
|
+
if (oldest !== void 0) this.entries.delete(oldest);
|
|
2239
|
+
}
|
|
2240
|
+
this.entries.set(key, expiresAtMs);
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
var defaultNonceStore = new InMemoryNonceStore();
|
|
2245
|
+
|
|
2246
|
+
// src/transport/rfc9421-verify.ts
|
|
2011
2247
|
async function verifyRFC9421(request, options) {
|
|
2012
2248
|
const { resolver } = options;
|
|
2013
|
-
const tolerance = options.clockSkewSec ??
|
|
2249
|
+
const tolerance = options.clockSkewSec ?? 60;
|
|
2014
2250
|
const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1e3);
|
|
2251
|
+
const nonceStore = options.nonceStore ?? defaultNonceStore;
|
|
2015
2252
|
let resolvedKid;
|
|
2016
2253
|
let resolvedAlg;
|
|
2254
|
+
let replayDetected = false;
|
|
2017
2255
|
const keyLookup = async (parameters) => {
|
|
2018
2256
|
const kid = typeof parameters.keyid === "string" ? parameters.keyid : void 0;
|
|
2019
2257
|
if (!kid) return null;
|
|
@@ -2027,6 +2265,14 @@ async function verifyRFC9421(request, options) {
|
|
|
2027
2265
|
const expires = toUnixSeconds(parameters.expires);
|
|
2028
2266
|
if (created !== void 0 && Math.abs(nowSec - created) > tolerance) return null;
|
|
2029
2267
|
if (expires !== void 0 && nowSec > expires + tolerance) return null;
|
|
2268
|
+
const nonce = typeof parameters.nonce === "string" ? parameters.nonce : void 0;
|
|
2269
|
+
if (nonce) {
|
|
2270
|
+
const expiresAtMs = (expires !== void 0 ? expires + tolerance : nowSec + tolerance) * 1e3;
|
|
2271
|
+
if (nonceStore.seen(`rfc9421:${kid}:${nonce}`, expiresAtMs)) {
|
|
2272
|
+
replayDetected = true;
|
|
2273
|
+
return null;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2030
2276
|
return jwkToVerifyingKey(kid, jwk, alg);
|
|
2031
2277
|
};
|
|
2032
2278
|
try {
|
|
@@ -2049,7 +2295,7 @@ async function verifyRFC9421(request, options) {
|
|
|
2049
2295
|
kid: resolvedKid,
|
|
2050
2296
|
registry: resolver.name,
|
|
2051
2297
|
algorithm: resolvedAlg,
|
|
2052
|
-
error: result === false ? "signature invalid" : "no signature found"
|
|
2298
|
+
error: replayDetected ? "RFC9421 signature replay \u2014 already seen within tolerance window" : result === false ? "signature invalid" : "no signature found"
|
|
2053
2299
|
};
|
|
2054
2300
|
} catch (err) {
|
|
2055
2301
|
return {
|
|
@@ -2874,14 +3120,26 @@ function sha256Sync2(data) {
|
|
|
2874
3120
|
function verifyAP2Chain(input) {
|
|
2875
3121
|
const { triple } = input;
|
|
2876
3122
|
const errors = [];
|
|
3123
|
+
const toleranceSec = input.clockSkewSec ?? 60;
|
|
3124
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
2877
3125
|
const intentPresent = triple.intent !== void 0;
|
|
2878
3126
|
const cartRefOk = checkCartRef(triple, errors);
|
|
2879
3127
|
const paymentRefOk = checkPaymentRef(triple, errors);
|
|
2880
3128
|
const { ok: agentIdContinuity, agentId } = checkAgentContinuity(triple, errors);
|
|
2881
3129
|
const paymentMethodAllowed = checkPaymentMethod(triple, errors);
|
|
2882
3130
|
const totalsConsistent = checkTotals(triple, errors);
|
|
2883
|
-
const expiryOk = checkExpiries(triple,
|
|
2884
|
-
|
|
3131
|
+
const expiryOk = checkExpiries(triple, toleranceSec, input.now, errors);
|
|
3132
|
+
let replayOk = true;
|
|
3133
|
+
const replayId = triple.payment?.raw?.id ?? triple.cart?.raw?.id;
|
|
3134
|
+
if (typeof replayId === "string" && replayId.length > 0) {
|
|
3135
|
+
const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3136
|
+
const expiresAt = (now + toleranceSec) * 1e3;
|
|
3137
|
+
if (nonceStore.seen(`ap2:${replayId}`, expiresAt)) {
|
|
3138
|
+
errors.push(`AP2 chain replay \u2014 mandate ${replayId} already seen within tolerance window`);
|
|
3139
|
+
replayOk = false;
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
const ok = cartRefOk && paymentRefOk && agentIdContinuity && paymentMethodAllowed && totalsConsistent && expiryOk && replayOk;
|
|
2885
3143
|
return {
|
|
2886
3144
|
ok,
|
|
2887
3145
|
checks: {
|
|
@@ -2927,7 +3185,10 @@ function checkAgentContinuity(triple, errors) {
|
|
|
2927
3185
|
const ids = [triple.intent?.agent_id, triple.cart?.agent_id, triple.payment?.agent_id].filter(
|
|
2928
3186
|
(id) => typeof id === "string" && id.length > 0
|
|
2929
3187
|
);
|
|
2930
|
-
if (ids.length === 0)
|
|
3188
|
+
if (ids.length === 0) {
|
|
3189
|
+
errors.push("agent_id missing across all three mandates (intent/cart/payment)");
|
|
3190
|
+
return { ok: false };
|
|
3191
|
+
}
|
|
2931
3192
|
const unique = new Set(ids);
|
|
2932
3193
|
if (unique.size > 1) {
|
|
2933
3194
|
errors.push(`agent_id mismatch across mandates: ${Array.from(unique).join(", ")}`);
|
|
@@ -2936,9 +3197,16 @@ function checkAgentContinuity(triple, errors) {
|
|
|
2936
3197
|
return { ok: true, agentId: ids[0] };
|
|
2937
3198
|
}
|
|
2938
3199
|
function checkPaymentMethod(triple, errors) {
|
|
2939
|
-
const paymentMethod = triple.payment?.payment_method;
|
|
2940
3200
|
const allowed = triple.intent?.paymentMethods;
|
|
2941
|
-
if (!
|
|
3201
|
+
if (!allowed || allowed.length === 0) return true;
|
|
3202
|
+
if (!triple.payment) return true;
|
|
3203
|
+
const paymentMethod = triple.payment.payment_method;
|
|
3204
|
+
if (!paymentMethod) {
|
|
3205
|
+
errors.push(
|
|
3206
|
+
`payment.payment_method missing but intent declares allowlist [${allowed.join(", ")}]`
|
|
3207
|
+
);
|
|
3208
|
+
return false;
|
|
3209
|
+
}
|
|
2942
3210
|
if (!allowed.includes(paymentMethod)) {
|
|
2943
3211
|
errors.push(
|
|
2944
3212
|
`payment_method "${paymentMethod}" not in intent.paymentMethods [${allowed.join(", ")}]`
|
|
@@ -2972,19 +3240,24 @@ function checkTotals(triple, errors) {
|
|
|
2972
3240
|
function checkExpiries(triple, toleranceSec, nowFn, errors) {
|
|
2973
3241
|
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
2974
3242
|
let ok = true;
|
|
2975
|
-
|
|
2976
|
-
["intent", triple.intent],
|
|
2977
|
-
["cart", triple.cart]
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3243
|
+
const layers = [
|
|
3244
|
+
["intent", triple.intent?.expires],
|
|
3245
|
+
["cart", triple.cart?.expires],
|
|
3246
|
+
[
|
|
3247
|
+
"payment",
|
|
3248
|
+
typeof triple.payment?.raw?.expires === "string" ? triple.payment.raw.expires : typeof triple.payment?.raw?.exp === "string" ? triple.payment.raw.exp : void 0
|
|
3249
|
+
]
|
|
3250
|
+
];
|
|
3251
|
+
for (const [name, expires] of layers) {
|
|
3252
|
+
if (!expires) continue;
|
|
3253
|
+
const parsed = parseExpiry(expires);
|
|
2981
3254
|
if (parsed === null) {
|
|
2982
3255
|
errors.push(`${name}.expires unparseable`);
|
|
2983
3256
|
ok = false;
|
|
2984
3257
|
continue;
|
|
2985
3258
|
}
|
|
2986
3259
|
if (now > parsed + toleranceSec) {
|
|
2987
|
-
errors.push(`${name} mandate expired at ${
|
|
3260
|
+
errors.push(`${name} mandate expired at ${expires}`);
|
|
2988
3261
|
ok = false;
|
|
2989
3262
|
}
|
|
2990
3263
|
}
|
|
@@ -3011,10 +3284,21 @@ async function verifyACPSignature(input) {
|
|
|
3011
3284
|
if (!input.signatureHeader) {
|
|
3012
3285
|
return { ok: false, error: "missing Signature header" };
|
|
3013
3286
|
}
|
|
3014
|
-
const
|
|
3287
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3288
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3289
|
+
const freshness = checkTimestamp(input.timestampHeader, tolerance, input.now);
|
|
3015
3290
|
if (!freshness.ok) {
|
|
3016
3291
|
return { ok: false, error: freshness.error, timestampStale: true };
|
|
3017
3292
|
}
|
|
3293
|
+
const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3294
|
+
const expiresAtMs = (nowSec + tolerance) * 1e3;
|
|
3295
|
+
const replayKey = `acp:${input.signatureHeader}:${input.timestampHeader ?? ""}`;
|
|
3296
|
+
if (nonceStore.seen(replayKey, expiresAtMs)) {
|
|
3297
|
+
return {
|
|
3298
|
+
ok: false,
|
|
3299
|
+
error: "ACP signature replay \u2014 already seen within tolerance window"
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3018
3302
|
const signatureBytes = decodeBase64(input.signatureHeader);
|
|
3019
3303
|
if (!signatureBytes) {
|
|
3020
3304
|
return { ok: false, error: "signature header is not valid base64" };
|
|
@@ -3232,8 +3516,9 @@ function coerceString6(v) {
|
|
|
3232
3516
|
var import_mppx2 = require("mppx");
|
|
3233
3517
|
function verifyMPP(input) {
|
|
3234
3518
|
const { context } = input;
|
|
3235
|
-
const tolerance = input.clockSkewSec ??
|
|
3519
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3236
3520
|
const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3521
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3237
3522
|
const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);
|
|
3238
3523
|
const source = context.credential?.source;
|
|
3239
3524
|
const method = challenge?.method;
|
|
@@ -3256,21 +3541,38 @@ function verifyMPP(input) {
|
|
|
3256
3541
|
}
|
|
3257
3542
|
}
|
|
3258
3543
|
let bodyDigestOk = null;
|
|
3259
|
-
if (
|
|
3260
|
-
|
|
3261
|
-
|
|
3544
|
+
if (input.rawBody !== void 0) {
|
|
3545
|
+
if (!challenge?.digest) {
|
|
3546
|
+
bodyDigestOk = false;
|
|
3547
|
+
} else {
|
|
3548
|
+
try {
|
|
3549
|
+
if (!/^sha-256=/.test(challenge.digest)) {
|
|
3550
|
+
bodyDigestOk = false;
|
|
3551
|
+
} else {
|
|
3552
|
+
bodyDigestOk = import_mppx2.BodyDigest.verify(challenge.digest, input.rawBody);
|
|
3553
|
+
}
|
|
3554
|
+
} catch {
|
|
3262
3555
|
bodyDigestOk = false;
|
|
3263
|
-
} else {
|
|
3264
|
-
bodyDigestOk = import_mppx2.BodyDigest.verify(challenge.digest, input.rawBody);
|
|
3265
3556
|
}
|
|
3266
|
-
} catch {
|
|
3267
|
-
bodyDigestOk = false;
|
|
3268
3557
|
}
|
|
3269
3558
|
}
|
|
3270
|
-
|
|
3559
|
+
let replayOk = true;
|
|
3560
|
+
if (challenge?.digest && expiryOk) {
|
|
3561
|
+
const replayKey = `mpp:${challenge.digest}:${challenge.nonce ?? ""}`;
|
|
3562
|
+
const expiresAt = (nowSec + tolerance) * 1e3;
|
|
3563
|
+
if (nonceStore.seen(replayKey, expiresAt)) {
|
|
3564
|
+
replayOk = false;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true) && replayOk;
|
|
3271
3568
|
const errors = [];
|
|
3272
3569
|
if (!expiryOk) errors.push("challenge expired");
|
|
3273
|
-
if (bodyDigestOk === false)
|
|
3570
|
+
if (bodyDigestOk === false) {
|
|
3571
|
+
errors.push(
|
|
3572
|
+
input.rawBody !== void 0 && !challenge?.digest ? "body digest required when rawBody present" : "body digest mismatch"
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
if (!replayOk) errors.push("MPP challenge replay \u2014 already seen within tolerance window");
|
|
3274
3576
|
return {
|
|
3275
3577
|
ok,
|
|
3276
3578
|
expiryOk,
|
|
@@ -3431,14 +3733,32 @@ function readHeader4(headers, name) {
|
|
|
3431
3733
|
var import_node_crypto4 = require("crypto");
|
|
3432
3734
|
async function verifyVIChain(input) {
|
|
3433
3735
|
const errors = [];
|
|
3434
|
-
const tolerance = input.clockSkewSec ??
|
|
3736
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3435
3737
|
const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3436
3738
|
const { l1, l2, l3a, l3b } = input.layers;
|
|
3739
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3740
|
+
if (!l1) {
|
|
3741
|
+
if (!input.allowUnboundChain) {
|
|
3742
|
+
errors.push(
|
|
3743
|
+
"L1 missing \u2014 chain root unbound (set allowUnboundChain + expectedL2Key to override)"
|
|
3744
|
+
);
|
|
3745
|
+
} else if (!input.expectedL2Key) {
|
|
3746
|
+
errors.push("allowUnboundChain set but expectedL2Key missing");
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3437
3749
|
const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;
|
|
3438
3750
|
if (l1 && !l1SigOk) errors.push("L1 signature invalid");
|
|
3439
3751
|
const l1Cnf = extractCnfJwk(l1?.payload);
|
|
3440
|
-
const
|
|
3752
|
+
const l2ExpectedKey = l1Cnf ?? input.expectedL2Key ?? null;
|
|
3753
|
+
const l2SigOk = await input.verifySignature(l2, l2ExpectedKey);
|
|
3441
3754
|
if (!l2SigOk) errors.push("L2 signature invalid");
|
|
3755
|
+
if (l2SigOk) {
|
|
3756
|
+
const replayKey = `vi:l2:${l2.compact}`;
|
|
3757
|
+
const expiresAt = now * 1e3 + tolerance * 1e3;
|
|
3758
|
+
if (nonceStore.seen(replayKey, expiresAt)) {
|
|
3759
|
+
errors.push("L2 signature replay \u2014 already seen within tolerance window");
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3442
3762
|
const l2Cnf = extractCnfJwk(l2.payload);
|
|
3443
3763
|
const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;
|
|
3444
3764
|
if (l3a && !l3aSigOk) errors.push("L3a signature invalid");
|
|
@@ -3482,7 +3802,10 @@ async function verifyVIChain(input) {
|
|
|
3482
3802
|
}
|
|
3483
3803
|
}
|
|
3484
3804
|
const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);
|
|
3485
|
-
const
|
|
3805
|
+
const noUnboundChainOrReplayErrors = !errors.some(
|
|
3806
|
+
(e) => e.startsWith("L1 missing") || e.startsWith("allowUnboundChain set") || e.startsWith("L2 signature replay")
|
|
3807
|
+
);
|
|
3808
|
+
const ok = l1SigOk !== false && l2SigOk && l3aSigOk !== false && l3bSigOk !== false && l1BindsL2 && l2BindsL3 && l3aL3bTxnIdMatch !== false && checkoutHashOk !== false && expiryOk && noUnboundChainOrReplayErrors;
|
|
3486
3809
|
return {
|
|
3487
3810
|
ok,
|
|
3488
3811
|
checks: {
|
|
@@ -3915,7 +4238,7 @@ async function exportJwkFromKeyLike(keyLike) {
|
|
|
3915
4238
|
|
|
3916
4239
|
// src/transport/registry/mastercard.ts
|
|
3917
4240
|
function createMastercardRegistry(options = {}) {
|
|
3918
|
-
const
|
|
4241
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
3919
4242
|
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3920
4243
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3921
4244
|
let warned = false;
|
|
@@ -3932,7 +4255,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3932
4255
|
}
|
|
3933
4256
|
return null;
|
|
3934
4257
|
}
|
|
3935
|
-
const cached =
|
|
4258
|
+
const cached = cache2.get(kid);
|
|
3936
4259
|
if (cached && cached.expiresAt > Date.now()) return cached.jwk;
|
|
3937
4260
|
try {
|
|
3938
4261
|
const res = await fetchFn(options.registryUrl);
|
|
@@ -3941,7 +4264,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3941
4264
|
const keys = body.keys ?? [];
|
|
3942
4265
|
for (const k of keys) {
|
|
3943
4266
|
if (k.kid === kid) {
|
|
3944
|
-
|
|
4267
|
+
cache2.set(kid, { jwk: k, expiresAt: Date.now() + ttlSec * 1e3 });
|
|
3945
4268
|
return k;
|
|
3946
4269
|
}
|
|
3947
4270
|
}
|
|
@@ -3956,7 +4279,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3956
4279
|
// src/transport/registry/web-bot-auth.ts
|
|
3957
4280
|
var DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
3958
4281
|
function createWebBotAuthRegistry(options = {}) {
|
|
3959
|
-
const
|
|
4282
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
3960
4283
|
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3961
4284
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3962
4285
|
return {
|
|
@@ -3965,7 +4288,7 @@ function createWebBotAuthRegistry(options = {}) {
|
|
|
3965
4288
|
if (!kid) return null;
|
|
3966
4289
|
const directoryUrl = resolveDirectoryUrl(options.directoryUrl, context?.origin);
|
|
3967
4290
|
if (!directoryUrl) return null;
|
|
3968
|
-
const cached =
|
|
4291
|
+
const cached = cache2.get(directoryUrl);
|
|
3969
4292
|
const now = Date.now();
|
|
3970
4293
|
if (cached && cached.expiresAt > now) {
|
|
3971
4294
|
return findKeyByKid(cached.keys, kid);
|
|
@@ -3975,7 +4298,7 @@ function createWebBotAuthRegistry(options = {}) {
|
|
|
3975
4298
|
if (!res.ok) return null;
|
|
3976
4299
|
const body = await res.json();
|
|
3977
4300
|
const keys = body.keys ?? [];
|
|
3978
|
-
|
|
4301
|
+
cache2.set(directoryUrl, { keys, expiresAt: now + ttlSec * 1e3 });
|
|
3979
4302
|
return findKeyByKid(keys, kid);
|
|
3980
4303
|
} catch {
|
|
3981
4304
|
return null;
|
|
@@ -4114,19 +4437,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
|
|
|
4114
4437
|
}
|
|
4115
4438
|
return { value: void 0, source: void 0 };
|
|
4116
4439
|
}
|
|
4117
|
-
function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
4118
|
-
const resource =
|
|
4440
|
+
function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
|
|
4441
|
+
const resource = toolGate?.resource ?? requestPath;
|
|
4119
4442
|
let purpose;
|
|
4120
4443
|
let purposeSource;
|
|
4121
|
-
if (
|
|
4444
|
+
if (toolGate?.purpose !== void 0) {
|
|
4445
|
+
purpose = toolGate.purpose;
|
|
4446
|
+
purposeSource = "tool_gate";
|
|
4447
|
+
} else if (headerPurpose) {
|
|
4122
4448
|
purpose = headerPurpose;
|
|
4123
4449
|
purposeSource = "header";
|
|
4124
4450
|
} else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
|
|
4125
4451
|
purpose = parsed.purposeFromBody;
|
|
4126
4452
|
purposeSource = parsed.purposeSourceFromBody;
|
|
4127
4453
|
} else {
|
|
4128
|
-
purpose =
|
|
4129
|
-
purposeSource =
|
|
4454
|
+
purpose = void 0;
|
|
4455
|
+
purposeSource = void 0;
|
|
4130
4456
|
}
|
|
4131
4457
|
let action;
|
|
4132
4458
|
let actionSource;
|
|
@@ -4137,7 +4463,7 @@ function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
|
4137
4463
|
action = parsed.actionFromBody;
|
|
4138
4464
|
actionSource = parsed.actionSourceFromBody;
|
|
4139
4465
|
} else {
|
|
4140
|
-
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4466
|
+
action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4141
4467
|
actionSource = "transport_layer";
|
|
4142
4468
|
}
|
|
4143
4469
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -4151,11 +4477,28 @@ function mcpRiskTier(parsed) {
|
|
|
4151
4477
|
}
|
|
4152
4478
|
|
|
4153
4479
|
// src/adapters/mcp.ts
|
|
4480
|
+
function normalizeToolGate(gate) {
|
|
4481
|
+
return typeof gate === "string" ? { minAccessLevel: gate } : gate;
|
|
4482
|
+
}
|
|
4154
4483
|
function readSingleHeader(value) {
|
|
4155
4484
|
if (typeof value === "string") return value;
|
|
4156
4485
|
if (Array.isArray(value)) return value[0];
|
|
4157
4486
|
return void 0;
|
|
4158
4487
|
}
|
|
4488
|
+
function dedupeFailures2(result) {
|
|
4489
|
+
if (result.failures && result.failures.length > 1) {
|
|
4490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4491
|
+
result.failures = result.failures.filter((f) => {
|
|
4492
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
4493
|
+
if (seen.has(key)) return false;
|
|
4494
|
+
seen.add(key);
|
|
4495
|
+
return true;
|
|
4496
|
+
});
|
|
4497
|
+
}
|
|
4498
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
4499
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4159
4502
|
function defaultMcpDenied(result, req, res) {
|
|
4160
4503
|
const id = req.body?.id ?? null;
|
|
4161
4504
|
const status = !result.identityVerified ? 401 : 403;
|
|
@@ -4180,11 +4523,17 @@ function defaultMcpDenied(result, req, res) {
|
|
|
4180
4523
|
});
|
|
4181
4524
|
}
|
|
4182
4525
|
function resolveMinAccessLevel(parsed, opts) {
|
|
4183
|
-
if (parsed.toolName
|
|
4184
|
-
|
|
4526
|
+
if (!parsed.toolName) {
|
|
4527
|
+
if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
|
|
4528
|
+
return { level: opts.methodGates[parsed.method], source: "methodGate" };
|
|
4529
|
+
}
|
|
4530
|
+
return { level: "none", source: "discovery_default" };
|
|
4185
4531
|
}
|
|
4186
|
-
if (opts.
|
|
4187
|
-
return {
|
|
4532
|
+
if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
|
|
4533
|
+
return {
|
|
4534
|
+
level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
|
|
4535
|
+
source: "toolGate"
|
|
4536
|
+
};
|
|
4188
4537
|
}
|
|
4189
4538
|
return { level: mcpRiskTier(parsed), source: "tier" };
|
|
4190
4539
|
}
|
|
@@ -4195,12 +4544,17 @@ function createMcpMiddleware(options) {
|
|
|
4195
4544
|
onAgentIdMismatch = "reject",
|
|
4196
4545
|
skip = false,
|
|
4197
4546
|
onDenied = defaultMcpDenied,
|
|
4198
|
-
trustVerifiedHop =
|
|
4547
|
+
trustVerifiedHop = false,
|
|
4199
4548
|
verifiedHopMaxAgeMs,
|
|
4200
4549
|
recordDecisions,
|
|
4201
4550
|
enableRuntimeChallenge = true,
|
|
4551
|
+
failOnError = "open",
|
|
4202
4552
|
...config
|
|
4203
4553
|
} = options;
|
|
4554
|
+
if (config.apiBaseUrl) {
|
|
4555
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
4556
|
+
});
|
|
4557
|
+
}
|
|
4204
4558
|
return async (req, res, next) => {
|
|
4205
4559
|
try {
|
|
4206
4560
|
if (skip) return next();
|
|
@@ -4213,6 +4567,7 @@ function createMcpMiddleware(options) {
|
|
|
4213
4567
|
return next();
|
|
4214
4568
|
}
|
|
4215
4569
|
req.mcpRequest = parsed;
|
|
4570
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
4216
4571
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
4217
4572
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
4218
4573
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -4266,9 +4621,17 @@ function createMcpMiddleware(options) {
|
|
|
4266
4621
|
}
|
|
4267
4622
|
return next();
|
|
4268
4623
|
}
|
|
4624
|
+
const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
|
|
4625
|
+
const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
|
|
4269
4626
|
const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
|
|
4270
4627
|
const headerAction = readSingleHeader(req.headers["x-astra-action"]);
|
|
4271
|
-
const pdlss = mcpToPdlss(
|
|
4628
|
+
const pdlss = mcpToPdlss(
|
|
4629
|
+
parsed,
|
|
4630
|
+
req.path,
|
|
4631
|
+
headerPurpose,
|
|
4632
|
+
headerAction,
|
|
4633
|
+
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
4634
|
+
);
|
|
4272
4635
|
if (config.debug) {
|
|
4273
4636
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
4274
4637
|
purpose_source: pdlss.purposeSource,
|
|
@@ -4306,6 +4669,7 @@ function createMcpMiddleware(options) {
|
|
|
4306
4669
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
4307
4670
|
});
|
|
4308
4671
|
}
|
|
4672
|
+
dedupeFailures2(result);
|
|
4309
4673
|
onDenied(result, req, res);
|
|
4310
4674
|
return;
|
|
4311
4675
|
}
|
|
@@ -4328,6 +4692,13 @@ function createMcpMiddleware(options) {
|
|
|
4328
4692
|
};
|
|
4329
4693
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
4330
4694
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
4695
|
+
if (!result.guidance && wellKnownUrls) {
|
|
4696
|
+
result.guidance = {
|
|
4697
|
+
message: insufficientFailure.message,
|
|
4698
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
4699
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
4700
|
+
};
|
|
4701
|
+
}
|
|
4331
4702
|
if (shouldRecordDecisions) {
|
|
4332
4703
|
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
4333
4704
|
const override = {
|
|
@@ -4351,6 +4722,7 @@ function createMcpMiddleware(options) {
|
|
|
4351
4722
|
});
|
|
4352
4723
|
}
|
|
4353
4724
|
}
|
|
4725
|
+
dedupeFailures2(result);
|
|
4354
4726
|
onDenied(result, req, res);
|
|
4355
4727
|
return;
|
|
4356
4728
|
}
|
|
@@ -4374,7 +4746,38 @@ function createMcpMiddleware(options) {
|
|
|
4374
4746
|
}
|
|
4375
4747
|
next();
|
|
4376
4748
|
} catch (error) {
|
|
4749
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
4750
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4377
4751
|
console.error("[VerificationGateway/MCP] Middleware error:", error);
|
|
4752
|
+
console.warn(
|
|
4753
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
4754
|
+
);
|
|
4755
|
+
if (failOnError === "closed") {
|
|
4756
|
+
const result = {
|
|
4757
|
+
identityVerified: false,
|
|
4758
|
+
policyAllowed: false,
|
|
4759
|
+
accessLevel: "none",
|
|
4760
|
+
denialReasons: [`MCP middleware internal error: ${errorClass}`],
|
|
4761
|
+
failures: [
|
|
4762
|
+
{
|
|
4763
|
+
dimension: "middleware.internal_error",
|
|
4764
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
4765
|
+
}
|
|
4766
|
+
],
|
|
4767
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
4768
|
+
correlationId
|
|
4769
|
+
};
|
|
4770
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
4771
|
+
if (catchUrls) {
|
|
4772
|
+
result.guidance = {
|
|
4773
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
4774
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
4775
|
+
documentationUrl: catchUrls.documentationUrl
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
dedupeFailures2(result);
|
|
4779
|
+
return onDenied(result, req, res);
|
|
4780
|
+
}
|
|
4378
4781
|
next();
|
|
4379
4782
|
}
|
|
4380
4783
|
};
|
|
@@ -5082,11 +5485,14 @@ var VERSION = "2.0.0";
|
|
|
5082
5485
|
extractCredentials,
|
|
5083
5486
|
extractMcpCredentials,
|
|
5084
5487
|
getAccessLevelForScore,
|
|
5488
|
+
getCachedWellKnownUrls,
|
|
5085
5489
|
getCapabilities,
|
|
5086
5490
|
getTrustLevel,
|
|
5491
|
+
getWellKnownUrls,
|
|
5087
5492
|
hasCredentials,
|
|
5088
5493
|
hasMinimumAccess,
|
|
5089
5494
|
nextjs,
|
|
5495
|
+
prefetchWellKnown,
|
|
5090
5496
|
quickVerify,
|
|
5091
5497
|
recordDecision,
|
|
5092
5498
|
sdk,
|