@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.mjs
CHANGED
|
@@ -126,7 +126,66 @@ function getCapabilities(accessLevel) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// src/version.ts
|
|
129
|
-
var SDK_VERSION = "2.4.
|
|
129
|
+
var SDK_VERSION = "2.4.13";
|
|
130
|
+
|
|
131
|
+
// src/well-known.ts
|
|
132
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
133
|
+
var cache = /* @__PURE__ */ new Map();
|
|
134
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
135
|
+
function wellKnownUrl(apiBaseUrl) {
|
|
136
|
+
const base = apiBaseUrl.replace(/\/api\/?$/, "");
|
|
137
|
+
return `${base}/.well-known/agentic-commerce`;
|
|
138
|
+
}
|
|
139
|
+
async function fetchWellKnown(apiBaseUrl) {
|
|
140
|
+
const url = wellKnownUrl(apiBaseUrl);
|
|
141
|
+
const response = await fetch(url, {
|
|
142
|
+
method: "GET",
|
|
143
|
+
headers: { Accept: "application/json" },
|
|
144
|
+
signal: AbortSignal.timeout(5e3)
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const data = await response.json();
|
|
152
|
+
if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return data;
|
|
158
|
+
}
|
|
159
|
+
function prefetchWellKnown(apiBaseUrl) {
|
|
160
|
+
const existing = inflight.get(apiBaseUrl);
|
|
161
|
+
if (existing) return existing;
|
|
162
|
+
const promise = fetchWellKnown(apiBaseUrl).then((data) => {
|
|
163
|
+
cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
|
|
164
|
+
inflight.delete(apiBaseUrl);
|
|
165
|
+
return data;
|
|
166
|
+
}).catch((err) => {
|
|
167
|
+
inflight.delete(apiBaseUrl);
|
|
168
|
+
throw err;
|
|
169
|
+
});
|
|
170
|
+
inflight.set(apiBaseUrl, promise);
|
|
171
|
+
return promise;
|
|
172
|
+
}
|
|
173
|
+
async function getWellKnownUrls(apiBaseUrl) {
|
|
174
|
+
const entry = cache.get(apiBaseUrl);
|
|
175
|
+
if (entry) {
|
|
176
|
+
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
177
|
+
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return entry.data;
|
|
181
|
+
}
|
|
182
|
+
const pending = inflight.get(apiBaseUrl);
|
|
183
|
+
if (pending) return pending;
|
|
184
|
+
return prefetchWellKnown(apiBaseUrl);
|
|
185
|
+
}
|
|
186
|
+
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
187
|
+
return cache.get(apiBaseUrl)?.data;
|
|
188
|
+
}
|
|
130
189
|
|
|
131
190
|
// src/verify.ts
|
|
132
191
|
var DEFAULT_CONFIG = {
|
|
@@ -145,22 +204,27 @@ var DEFAULT_CONFIG = {
|
|
|
145
204
|
};
|
|
146
205
|
var initCheckPerformed = false;
|
|
147
206
|
var deprecationWarningShown = false;
|
|
148
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
207
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
149
208
|
initCheckPerformed = true;
|
|
150
209
|
try {
|
|
151
210
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
152
211
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
153
212
|
const contentType = response.headers.get("content-type") ?? "";
|
|
154
213
|
if (contentType.startsWith("text/html")) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
214
|
+
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).`;
|
|
215
|
+
if (strictInit) {
|
|
216
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
217
|
+
}
|
|
218
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
158
219
|
} else if (debug) {
|
|
159
220
|
console.log(
|
|
160
221
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
161
222
|
);
|
|
162
223
|
}
|
|
163
224
|
} catch (err) {
|
|
225
|
+
if (strictInit) {
|
|
226
|
+
throw err;
|
|
227
|
+
}
|
|
164
228
|
if (debug) {
|
|
165
229
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
166
230
|
}
|
|
@@ -184,7 +248,23 @@ function getCacheKey(request) {
|
|
|
184
248
|
request.counterpartyType || "",
|
|
185
249
|
request.isSubAgentRequest ? "1" : "0",
|
|
186
250
|
request.parentAgentId || "",
|
|
187
|
-
request.subAgentDepth ?? ""
|
|
251
|
+
request.subAgentDepth ?? "",
|
|
252
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
253
|
+
// backend verdict. Without these, two requests with different
|
|
254
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
255
|
+
// the shorter-duration allow served the longer-duration request.
|
|
256
|
+
request.durationRequired ?? "",
|
|
257
|
+
request.invocationProtocol || "",
|
|
258
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
259
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
260
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
261
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
262
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
263
|
+
// be cached across IPs).
|
|
264
|
+
request.callerMetadata?.sourceIp || "",
|
|
265
|
+
request.callerMetadata?.userAgent || "",
|
|
266
|
+
request.callerMetadata?.forwardedFor || "",
|
|
267
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
188
268
|
].join("|");
|
|
189
269
|
}
|
|
190
270
|
function getCachedResult(request) {
|
|
@@ -213,9 +293,13 @@ function clearCache() {
|
|
|
213
293
|
}
|
|
214
294
|
function extractCredentials(headers, query) {
|
|
215
295
|
const credentials = {};
|
|
296
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
216
297
|
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"];
|
|
217
298
|
if (astraIdHeader) {
|
|
218
|
-
|
|
299
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
300
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
301
|
+
credentials.astraId = raw;
|
|
302
|
+
}
|
|
219
303
|
}
|
|
220
304
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
221
305
|
if (apiKeyHeader) {
|
|
@@ -224,9 +308,11 @@ function extractCredentials(headers, query) {
|
|
|
224
308
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
225
309
|
if (authHeader) {
|
|
226
310
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
311
|
+
if (typeof authValue === "string") {
|
|
312
|
+
credentials.authorizationHeader = authValue;
|
|
313
|
+
if (authValue.startsWith("Bearer ")) {
|
|
314
|
+
credentials.jwt = authValue.slice(7);
|
|
315
|
+
}
|
|
230
316
|
}
|
|
231
317
|
}
|
|
232
318
|
if (query) {
|
|
@@ -242,21 +328,22 @@ function extractCredentials(headers, query) {
|
|
|
242
328
|
function hasCredentials(credentials) {
|
|
243
329
|
return !!(credentials.astraId || credentials.apiKey || credentials.jwt);
|
|
244
330
|
}
|
|
245
|
-
function createGuidanceResponse(
|
|
331
|
+
function createGuidanceResponse(_config, reason, options = {}) {
|
|
246
332
|
const source = options.source ?? "no_credentials";
|
|
247
333
|
const isApiError = source === "api_error";
|
|
334
|
+
const urls = options.urls;
|
|
248
335
|
const guidance = isApiError ? {
|
|
249
336
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
250
|
-
registrationUrl:
|
|
251
|
-
documentationUrl:
|
|
337
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
338
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
252
339
|
steps: [
|
|
253
340
|
"Retry the request with exponential backoff",
|
|
254
341
|
"If failures persist, share the correlationId with support"
|
|
255
342
|
]
|
|
256
343
|
} : {
|
|
257
344
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
258
|
-
registrationUrl:
|
|
259
|
-
documentationUrl:
|
|
345
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
346
|
+
documentationUrl: urls?.documentationUrl ?? "",
|
|
260
347
|
steps: [
|
|
261
348
|
"Register for an AstraSync account",
|
|
262
349
|
"Create and register your agent",
|
|
@@ -298,7 +385,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
298
385
|
const { credentials, ...requestData } = request;
|
|
299
386
|
const body = {
|
|
300
387
|
...credentials.astraId && { agentId: credentials.astraId },
|
|
301
|
-
purpose: requestData.purpose
|
|
388
|
+
...requestData.purpose && { purpose: requestData.purpose }
|
|
302
389
|
};
|
|
303
390
|
if (requestData.action) body.action = requestData.action;
|
|
304
391
|
if (requestData.resourceType) body.resourceType = requestData.resourceType;
|
|
@@ -332,12 +419,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
332
419
|
"Content-Type": "application/json",
|
|
333
420
|
...config.customHeaders
|
|
334
421
|
};
|
|
335
|
-
if (credentials.authorizationHeader) {
|
|
336
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
337
|
-
} else if (config.apiKey) {
|
|
338
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
339
|
-
}
|
|
340
422
|
if (config.apiKey) {
|
|
423
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
341
424
|
headers["X-API-Key"] = config.apiKey;
|
|
342
425
|
}
|
|
343
426
|
try {
|
|
@@ -382,8 +465,13 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
382
465
|
}
|
|
383
466
|
async function verify(config, request) {
|
|
384
467
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
468
|
+
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
385
469
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
386
|
-
|
|
470
|
+
if (mergedConfig.strictInit) {
|
|
471
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
472
|
+
} else {
|
|
473
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
474
|
+
}
|
|
387
475
|
}
|
|
388
476
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
389
477
|
deprecationWarningShown = true;
|
|
@@ -414,7 +502,8 @@ async function verify(config, request) {
|
|
|
414
502
|
if (!apiResponse.success) {
|
|
415
503
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
416
504
|
source: "api_error",
|
|
417
|
-
correlationId: apiResponse.correlationId
|
|
505
|
+
correlationId: apiResponse.correlationId,
|
|
506
|
+
urls
|
|
418
507
|
});
|
|
419
508
|
}
|
|
420
509
|
if (!apiResponse.access?.allowed) {
|
|
@@ -437,8 +526,8 @@ async function verify(config, request) {
|
|
|
437
526
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
438
527
|
guidance: {
|
|
439
528
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
440
|
-
registrationUrl:
|
|
441
|
-
documentationUrl:
|
|
529
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
530
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
442
531
|
},
|
|
443
532
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
444
533
|
// Extract sessionId so decisions can be recorded for denials too
|
|
@@ -507,13 +596,15 @@ async function verify(config, request) {
|
|
|
507
596
|
result.denialReasons = result.recommendationReasons || [
|
|
508
597
|
"Access denied by AstraSync recommendation"
|
|
509
598
|
];
|
|
510
|
-
|
|
511
|
-
result.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
599
|
+
result.guidance = result.runtimeChallenge ? {
|
|
600
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
601
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
602
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
603
|
+
} : {
|
|
604
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
605
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
606
|
+
documentationUrl: urls?.documentationUrl ?? ""
|
|
607
|
+
};
|
|
517
608
|
} else if (result.recommendation === "step_up_required") {
|
|
518
609
|
result.requiresStepUp = true;
|
|
519
610
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -762,18 +853,43 @@ function defaultExtractPurpose(req) {
|
|
|
762
853
|
return "general";
|
|
763
854
|
}
|
|
764
855
|
}
|
|
765
|
-
function matchRoute(pattern, path) {
|
|
856
|
+
function matchRoute(pattern, path, opts) {
|
|
766
857
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
767
|
-
const
|
|
768
|
-
|
|
858
|
+
const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
|
|
859
|
+
const caseSensitiveResult = caseSensitiveRegex.test(path);
|
|
860
|
+
if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
|
|
861
|
+
return caseSensitiveResult;
|
|
862
|
+
}
|
|
863
|
+
const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
864
|
+
const caseInsensitiveResult = caseInsensitiveRegex.test(path);
|
|
865
|
+
if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
|
|
866
|
+
console.warn(
|
|
867
|
+
`[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
|
|
769
871
|
}
|
|
770
|
-
function findRouteConfig(routes, path, method) {
|
|
872
|
+
function findRouteConfig(routes, path, method, opts) {
|
|
771
873
|
return routes.find((route) => {
|
|
772
874
|
const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
|
|
773
|
-
const pathMatches = matchRoute(route.pattern, path);
|
|
875
|
+
const pathMatches = matchRoute(route.pattern, path, opts);
|
|
774
876
|
return methodMatches && pathMatches;
|
|
775
877
|
});
|
|
776
878
|
}
|
|
879
|
+
function dedupeFailures(result) {
|
|
880
|
+
if (result.failures && result.failures.length > 1) {
|
|
881
|
+
const seen = /* @__PURE__ */ new Set();
|
|
882
|
+
result.failures = result.failures.filter((f) => {
|
|
883
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
884
|
+
if (seen.has(key)) return false;
|
|
885
|
+
seen.add(key);
|
|
886
|
+
return true;
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
890
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
891
|
+
}
|
|
892
|
+
}
|
|
777
893
|
function defaultOnDenied(result, _req, res) {
|
|
778
894
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
779
895
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
@@ -800,8 +916,14 @@ function createMiddleware(options) {
|
|
|
800
916
|
recordDecisions,
|
|
801
917
|
enableRuntimeChallenge = true,
|
|
802
918
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
919
|
+
failOnError = "open",
|
|
920
|
+
caseInsensitiveRouteMatch = false,
|
|
803
921
|
...config
|
|
804
922
|
} = options;
|
|
923
|
+
if (config.apiBaseUrl) {
|
|
924
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
925
|
+
});
|
|
926
|
+
}
|
|
805
927
|
let cachedRoutes = [];
|
|
806
928
|
let lastFetchAt = 0;
|
|
807
929
|
let refreshing = null;
|
|
@@ -822,7 +944,7 @@ function createMiddleware(options) {
|
|
|
822
944
|
cachedRoutes = fetched;
|
|
823
945
|
lastFetchAt = Date.now();
|
|
824
946
|
if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
|
|
825
|
-
const dashboard = config.dashboardUrl ?? "https://
|
|
947
|
+
const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
|
|
826
948
|
console.warn(
|
|
827
949
|
`[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`
|
|
828
950
|
);
|
|
@@ -848,7 +970,12 @@ function createMiddleware(options) {
|
|
|
848
970
|
refreshing = null;
|
|
849
971
|
});
|
|
850
972
|
}
|
|
851
|
-
const
|
|
973
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
974
|
+
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
975
|
+
caseInsensitive: caseInsensitiveRouteMatch,
|
|
976
|
+
logShadowDivergence: true,
|
|
977
|
+
correlationId
|
|
978
|
+
});
|
|
852
979
|
if (!routeConfig) {
|
|
853
980
|
if (config.setPassThroughHeader) {
|
|
854
981
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
@@ -859,6 +986,7 @@ function createMiddleware(options) {
|
|
|
859
986
|
}
|
|
860
987
|
return next();
|
|
861
988
|
}
|
|
989
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
862
990
|
const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
|
|
863
991
|
const shouldEnforce = routeConfig.minAccessLevel !== "none";
|
|
864
992
|
if (routeConfig.minAccessLevel === "none" && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
|
|
@@ -880,8 +1008,8 @@ function createMiddleware(options) {
|
|
|
880
1008
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
881
1009
|
guidance: {
|
|
882
1010
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
883
|
-
registrationUrl:
|
|
884
|
-
documentationUrl:
|
|
1011
|
+
registrationUrl: wellKnownUrls?.registrationUrl ?? "",
|
|
1012
|
+
documentationUrl: wellKnownUrls?.documentationUrl ?? ""
|
|
885
1013
|
},
|
|
886
1014
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
887
1015
|
};
|
|
@@ -895,13 +1023,19 @@ function createMiddleware(options) {
|
|
|
895
1023
|
requestMethod: req.method
|
|
896
1024
|
}).catch(() => {
|
|
897
1025
|
});
|
|
1026
|
+
dedupeFailures(result2);
|
|
898
1027
|
onDenied(result2, req, res);
|
|
899
1028
|
return;
|
|
900
1029
|
}
|
|
901
1030
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
902
1031
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
903
1032
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
904
|
-
const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() :
|
|
1033
|
+
const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1034
|
+
if (!req.ip && forwardedForStr) {
|
|
1035
|
+
console.warn(
|
|
1036
|
+
"[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."
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
905
1039
|
const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
|
|
906
1040
|
const result = await verify(config, {
|
|
907
1041
|
credentials,
|
|
@@ -932,6 +1066,7 @@ function createMiddleware(options) {
|
|
|
932
1066
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
933
1067
|
});
|
|
934
1068
|
}
|
|
1069
|
+
dedupeFailures(result);
|
|
935
1070
|
onDenied(result, req, res);
|
|
936
1071
|
return;
|
|
937
1072
|
}
|
|
@@ -954,10 +1089,18 @@ function createMiddleware(options) {
|
|
|
954
1089
|
};
|
|
955
1090
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
956
1091
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
1092
|
+
if (!result.guidance && wellKnownUrls) {
|
|
1093
|
+
result.guidance = {
|
|
1094
|
+
message: insufficientFailure.message,
|
|
1095
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
1096
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
957
1099
|
if (shouldRecordDecisions && sessionId) {
|
|
958
1100
|
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
959
1101
|
});
|
|
960
1102
|
}
|
|
1103
|
+
dedupeFailures(result);
|
|
961
1104
|
onDenied(result, req, res);
|
|
962
1105
|
return;
|
|
963
1106
|
}
|
|
@@ -970,10 +1113,18 @@ function createMiddleware(options) {
|
|
|
970
1113
|
};
|
|
971
1114
|
result.failures = [...result.failures ?? [], trustFailure];
|
|
972
1115
|
result.denialReasons = [trustFailure.message];
|
|
1116
|
+
if (!result.guidance && wellKnownUrls) {
|
|
1117
|
+
result.guidance = {
|
|
1118
|
+
message: trustFailure.message,
|
|
1119
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
1120
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
973
1123
|
if (shouldRecordDecisions && sessionId) {
|
|
974
1124
|
recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
|
|
975
1125
|
});
|
|
976
1126
|
}
|
|
1127
|
+
dedupeFailures(result);
|
|
977
1128
|
onDenied(result, req, res);
|
|
978
1129
|
return;
|
|
979
1130
|
}
|
|
@@ -988,7 +1139,38 @@ function createMiddleware(options) {
|
|
|
988
1139
|
}
|
|
989
1140
|
next();
|
|
990
1141
|
} catch (error) {
|
|
1142
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
1143
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
991
1144
|
console.error("[VerificationGateway] Middleware error:", error);
|
|
1145
|
+
console.warn(
|
|
1146
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
1147
|
+
);
|
|
1148
|
+
if (failOnError === "closed") {
|
|
1149
|
+
const result = {
|
|
1150
|
+
identityVerified: false,
|
|
1151
|
+
policyAllowed: false,
|
|
1152
|
+
accessLevel: "none",
|
|
1153
|
+
denialReasons: [`Verification middleware internal error: ${errorClass}`],
|
|
1154
|
+
failures: [
|
|
1155
|
+
{
|
|
1156
|
+
dimension: "middleware.internal_error",
|
|
1157
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
1158
|
+
}
|
|
1159
|
+
],
|
|
1160
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
1161
|
+
correlationId
|
|
1162
|
+
};
|
|
1163
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
1164
|
+
if (catchUrls) {
|
|
1165
|
+
result.guidance = {
|
|
1166
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
1167
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
1168
|
+
documentationUrl: catchUrls.documentationUrl
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
dedupeFailures(result);
|
|
1172
|
+
return onDenied(result, req, res);
|
|
1173
|
+
}
|
|
992
1174
|
next();
|
|
993
1175
|
}
|
|
994
1176
|
};
|
|
@@ -1000,6 +1182,18 @@ __export(nextjs_exports, {
|
|
|
1000
1182
|
createMatcherConfig: () => createMatcherConfig,
|
|
1001
1183
|
createMiddleware: () => createMiddleware2
|
|
1002
1184
|
});
|
|
1185
|
+
function escapeHtml(value) {
|
|
1186
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1187
|
+
}
|
|
1188
|
+
function sanitizeUrl(value, fallback) {
|
|
1189
|
+
if (typeof value !== "string" || value.length === 0) return escapeHtml(fallback);
|
|
1190
|
+
const trimmed = value.trim();
|
|
1191
|
+
if (/^javascript:|^data:|^vbscript:/i.test(trimmed)) return escapeHtml(fallback);
|
|
1192
|
+
if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("/")) {
|
|
1193
|
+
return escapeHtml(trimmed);
|
|
1194
|
+
}
|
|
1195
|
+
return escapeHtml(fallback);
|
|
1196
|
+
}
|
|
1003
1197
|
function extractCredentialsFromNextRequest(request) {
|
|
1004
1198
|
const credentials = {};
|
|
1005
1199
|
const astraId = request.headers.get("x-astra-id") || request.headers.get("X-Astra-Id");
|
|
@@ -1071,10 +1265,18 @@ function extractPurpose(request) {
|
|
|
1071
1265
|
}
|
|
1072
1266
|
}
|
|
1073
1267
|
function generateCommerceShieldHtml(result, options) {
|
|
1074
|
-
const title = options.commerceShield?.title || "AstraSync Agent Verification";
|
|
1075
|
-
const message =
|
|
1076
|
-
|
|
1077
|
-
|
|
1268
|
+
const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
|
|
1269
|
+
const message = escapeHtml(
|
|
1270
|
+
options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
|
|
1271
|
+
);
|
|
1272
|
+
const registrationUrl = sanitizeUrl(
|
|
1273
|
+
result.guidance?.registrationUrl,
|
|
1274
|
+
"https://astrasync.ai/register"
|
|
1275
|
+
);
|
|
1276
|
+
const docsUrl = sanitizeUrl(
|
|
1277
|
+
result.guidance?.documentationUrl,
|
|
1278
|
+
"https://astrasync.ai/docs/agent-access"
|
|
1279
|
+
);
|
|
1078
1280
|
const allowGuest = options.commerceShield?.allowGuestAccess ?? true;
|
|
1079
1281
|
return `
|
|
1080
1282
|
<!DOCTYPE html>
|
|
@@ -1196,7 +1398,7 @@ function generateCommerceShieldHtml(result, options) {
|
|
|
1196
1398
|
<div class="shield-steps">
|
|
1197
1399
|
<h3>To get verified access:</h3>
|
|
1198
1400
|
<ol>
|
|
1199
|
-
<li>Register at <a href="${registrationUrl}">astrasync.ai/register</a></li>
|
|
1401
|
+
<li>Register at <a href="${registrationUrl}">astrasync.ai/agents/register</a></li>
|
|
1200
1402
|
<li>Create and register your agent</li>
|
|
1201
1403
|
<li>Add your ASTRA-ID to request headers</li>
|
|
1202
1404
|
<li>Refresh this page</li>
|
|
@@ -1284,7 +1486,7 @@ function createMiddleware2(options) {
|
|
|
1284
1486
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
1285
1487
|
guidance: {
|
|
1286
1488
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
1287
|
-
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
|
|
1489
|
+
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
1288
1490
|
documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
1289
1491
|
},
|
|
1290
1492
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -1945,12 +2147,45 @@ function bufferToBase64(bytes) {
|
|
|
1945
2147
|
|
|
1946
2148
|
// src/transport/rfc9421-verify.ts
|
|
1947
2149
|
import { httpbis } from "http-message-signatures";
|
|
2150
|
+
|
|
2151
|
+
// src/transport/nonce-store.ts
|
|
2152
|
+
var InMemoryNonceStore = class {
|
|
2153
|
+
constructor(capacity = 1e4) {
|
|
2154
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
2155
|
+
this.lastSweepMs = 0;
|
|
2156
|
+
this.capacity = capacity;
|
|
2157
|
+
}
|
|
2158
|
+
seen(key, expiresAtMs) {
|
|
2159
|
+
const nowMs = Date.now();
|
|
2160
|
+
if (nowMs - this.lastSweepMs > 1e3) {
|
|
2161
|
+
for (const [k, exp] of this.entries) {
|
|
2162
|
+
if (exp <= nowMs) this.entries.delete(k);
|
|
2163
|
+
}
|
|
2164
|
+
this.lastSweepMs = nowMs;
|
|
2165
|
+
}
|
|
2166
|
+
const existing = this.entries.get(key);
|
|
2167
|
+
if (existing !== void 0 && existing > nowMs) {
|
|
2168
|
+
return true;
|
|
2169
|
+
}
|
|
2170
|
+
if (this.entries.size >= this.capacity) {
|
|
2171
|
+
const oldest = this.entries.keys().next().value;
|
|
2172
|
+
if (oldest !== void 0) this.entries.delete(oldest);
|
|
2173
|
+
}
|
|
2174
|
+
this.entries.set(key, expiresAtMs);
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
};
|
|
2178
|
+
var defaultNonceStore = new InMemoryNonceStore();
|
|
2179
|
+
|
|
2180
|
+
// src/transport/rfc9421-verify.ts
|
|
1948
2181
|
async function verifyRFC9421(request, options) {
|
|
1949
2182
|
const { resolver } = options;
|
|
1950
|
-
const tolerance = options.clockSkewSec ??
|
|
2183
|
+
const tolerance = options.clockSkewSec ?? 60;
|
|
1951
2184
|
const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1e3);
|
|
2185
|
+
const nonceStore = options.nonceStore ?? defaultNonceStore;
|
|
1952
2186
|
let resolvedKid;
|
|
1953
2187
|
let resolvedAlg;
|
|
2188
|
+
let replayDetected = false;
|
|
1954
2189
|
const keyLookup = async (parameters) => {
|
|
1955
2190
|
const kid = typeof parameters.keyid === "string" ? parameters.keyid : void 0;
|
|
1956
2191
|
if (!kid) return null;
|
|
@@ -1964,6 +2199,14 @@ async function verifyRFC9421(request, options) {
|
|
|
1964
2199
|
const expires = toUnixSeconds(parameters.expires);
|
|
1965
2200
|
if (created !== void 0 && Math.abs(nowSec - created) > tolerance) return null;
|
|
1966
2201
|
if (expires !== void 0 && nowSec > expires + tolerance) return null;
|
|
2202
|
+
const nonce = typeof parameters.nonce === "string" ? parameters.nonce : void 0;
|
|
2203
|
+
if (nonce) {
|
|
2204
|
+
const expiresAtMs = (expires !== void 0 ? expires + tolerance : nowSec + tolerance) * 1e3;
|
|
2205
|
+
if (nonceStore.seen(`rfc9421:${kid}:${nonce}`, expiresAtMs)) {
|
|
2206
|
+
replayDetected = true;
|
|
2207
|
+
return null;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
1967
2210
|
return jwkToVerifyingKey(kid, jwk, alg);
|
|
1968
2211
|
};
|
|
1969
2212
|
try {
|
|
@@ -1986,7 +2229,7 @@ async function verifyRFC9421(request, options) {
|
|
|
1986
2229
|
kid: resolvedKid,
|
|
1987
2230
|
registry: resolver.name,
|
|
1988
2231
|
algorithm: resolvedAlg,
|
|
1989
|
-
error: result === false ? "signature invalid" : "no signature found"
|
|
2232
|
+
error: replayDetected ? "RFC9421 signature replay \u2014 already seen within tolerance window" : result === false ? "signature invalid" : "no signature found"
|
|
1990
2233
|
};
|
|
1991
2234
|
} catch (err) {
|
|
1992
2235
|
return {
|
|
@@ -2811,14 +3054,26 @@ function sha256Sync2(data) {
|
|
|
2811
3054
|
function verifyAP2Chain(input) {
|
|
2812
3055
|
const { triple } = input;
|
|
2813
3056
|
const errors = [];
|
|
3057
|
+
const toleranceSec = input.clockSkewSec ?? 60;
|
|
3058
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
2814
3059
|
const intentPresent = triple.intent !== void 0;
|
|
2815
3060
|
const cartRefOk = checkCartRef(triple, errors);
|
|
2816
3061
|
const paymentRefOk = checkPaymentRef(triple, errors);
|
|
2817
3062
|
const { ok: agentIdContinuity, agentId } = checkAgentContinuity(triple, errors);
|
|
2818
3063
|
const paymentMethodAllowed = checkPaymentMethod(triple, errors);
|
|
2819
3064
|
const totalsConsistent = checkTotals(triple, errors);
|
|
2820
|
-
const expiryOk = checkExpiries(triple,
|
|
2821
|
-
|
|
3065
|
+
const expiryOk = checkExpiries(triple, toleranceSec, input.now, errors);
|
|
3066
|
+
let replayOk = true;
|
|
3067
|
+
const replayId = triple.payment?.raw?.id ?? triple.cart?.raw?.id;
|
|
3068
|
+
if (typeof replayId === "string" && replayId.length > 0) {
|
|
3069
|
+
const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3070
|
+
const expiresAt = (now + toleranceSec) * 1e3;
|
|
3071
|
+
if (nonceStore.seen(`ap2:${replayId}`, expiresAt)) {
|
|
3072
|
+
errors.push(`AP2 chain replay \u2014 mandate ${replayId} already seen within tolerance window`);
|
|
3073
|
+
replayOk = false;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
const ok = cartRefOk && paymentRefOk && agentIdContinuity && paymentMethodAllowed && totalsConsistent && expiryOk && replayOk;
|
|
2822
3077
|
return {
|
|
2823
3078
|
ok,
|
|
2824
3079
|
checks: {
|
|
@@ -2864,7 +3119,10 @@ function checkAgentContinuity(triple, errors) {
|
|
|
2864
3119
|
const ids = [triple.intent?.agent_id, triple.cart?.agent_id, triple.payment?.agent_id].filter(
|
|
2865
3120
|
(id) => typeof id === "string" && id.length > 0
|
|
2866
3121
|
);
|
|
2867
|
-
if (ids.length === 0)
|
|
3122
|
+
if (ids.length === 0) {
|
|
3123
|
+
errors.push("agent_id missing across all three mandates (intent/cart/payment)");
|
|
3124
|
+
return { ok: false };
|
|
3125
|
+
}
|
|
2868
3126
|
const unique = new Set(ids);
|
|
2869
3127
|
if (unique.size > 1) {
|
|
2870
3128
|
errors.push(`agent_id mismatch across mandates: ${Array.from(unique).join(", ")}`);
|
|
@@ -2873,9 +3131,16 @@ function checkAgentContinuity(triple, errors) {
|
|
|
2873
3131
|
return { ok: true, agentId: ids[0] };
|
|
2874
3132
|
}
|
|
2875
3133
|
function checkPaymentMethod(triple, errors) {
|
|
2876
|
-
const paymentMethod = triple.payment?.payment_method;
|
|
2877
3134
|
const allowed = triple.intent?.paymentMethods;
|
|
2878
|
-
if (!
|
|
3135
|
+
if (!allowed || allowed.length === 0) return true;
|
|
3136
|
+
if (!triple.payment) return true;
|
|
3137
|
+
const paymentMethod = triple.payment.payment_method;
|
|
3138
|
+
if (!paymentMethod) {
|
|
3139
|
+
errors.push(
|
|
3140
|
+
`payment.payment_method missing but intent declares allowlist [${allowed.join(", ")}]`
|
|
3141
|
+
);
|
|
3142
|
+
return false;
|
|
3143
|
+
}
|
|
2879
3144
|
if (!allowed.includes(paymentMethod)) {
|
|
2880
3145
|
errors.push(
|
|
2881
3146
|
`payment_method "${paymentMethod}" not in intent.paymentMethods [${allowed.join(", ")}]`
|
|
@@ -2909,19 +3174,24 @@ function checkTotals(triple, errors) {
|
|
|
2909
3174
|
function checkExpiries(triple, toleranceSec, nowFn, errors) {
|
|
2910
3175
|
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
2911
3176
|
let ok = true;
|
|
2912
|
-
|
|
2913
|
-
["intent", triple.intent],
|
|
2914
|
-
["cart", triple.cart]
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3177
|
+
const layers = [
|
|
3178
|
+
["intent", triple.intent?.expires],
|
|
3179
|
+
["cart", triple.cart?.expires],
|
|
3180
|
+
[
|
|
3181
|
+
"payment",
|
|
3182
|
+
typeof triple.payment?.raw?.expires === "string" ? triple.payment.raw.expires : typeof triple.payment?.raw?.exp === "string" ? triple.payment.raw.exp : void 0
|
|
3183
|
+
]
|
|
3184
|
+
];
|
|
3185
|
+
for (const [name, expires] of layers) {
|
|
3186
|
+
if (!expires) continue;
|
|
3187
|
+
const parsed = parseExpiry(expires);
|
|
2918
3188
|
if (parsed === null) {
|
|
2919
3189
|
errors.push(`${name}.expires unparseable`);
|
|
2920
3190
|
ok = false;
|
|
2921
3191
|
continue;
|
|
2922
3192
|
}
|
|
2923
3193
|
if (now > parsed + toleranceSec) {
|
|
2924
|
-
errors.push(`${name} mandate expired at ${
|
|
3194
|
+
errors.push(`${name} mandate expired at ${expires}`);
|
|
2925
3195
|
ok = false;
|
|
2926
3196
|
}
|
|
2927
3197
|
}
|
|
@@ -2948,10 +3218,21 @@ async function verifyACPSignature(input) {
|
|
|
2948
3218
|
if (!input.signatureHeader) {
|
|
2949
3219
|
return { ok: false, error: "missing Signature header" };
|
|
2950
3220
|
}
|
|
2951
|
-
const
|
|
3221
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3222
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3223
|
+
const freshness = checkTimestamp(input.timestampHeader, tolerance, input.now);
|
|
2952
3224
|
if (!freshness.ok) {
|
|
2953
3225
|
return { ok: false, error: freshness.error, timestampStale: true };
|
|
2954
3226
|
}
|
|
3227
|
+
const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3228
|
+
const expiresAtMs = (nowSec + tolerance) * 1e3;
|
|
3229
|
+
const replayKey = `acp:${input.signatureHeader}:${input.timestampHeader ?? ""}`;
|
|
3230
|
+
if (nonceStore.seen(replayKey, expiresAtMs)) {
|
|
3231
|
+
return {
|
|
3232
|
+
ok: false,
|
|
3233
|
+
error: "ACP signature replay \u2014 already seen within tolerance window"
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
2955
3236
|
const signatureBytes = decodeBase64(input.signatureHeader);
|
|
2956
3237
|
if (!signatureBytes) {
|
|
2957
3238
|
return { ok: false, error: "signature header is not valid base64" };
|
|
@@ -3169,8 +3450,9 @@ function coerceString6(v) {
|
|
|
3169
3450
|
import { BodyDigest } from "mppx";
|
|
3170
3451
|
function verifyMPP(input) {
|
|
3171
3452
|
const { context } = input;
|
|
3172
|
-
const tolerance = input.clockSkewSec ??
|
|
3453
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3173
3454
|
const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3455
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3174
3456
|
const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);
|
|
3175
3457
|
const source = context.credential?.source;
|
|
3176
3458
|
const method = challenge?.method;
|
|
@@ -3193,21 +3475,38 @@ function verifyMPP(input) {
|
|
|
3193
3475
|
}
|
|
3194
3476
|
}
|
|
3195
3477
|
let bodyDigestOk = null;
|
|
3196
|
-
if (
|
|
3197
|
-
|
|
3198
|
-
|
|
3478
|
+
if (input.rawBody !== void 0) {
|
|
3479
|
+
if (!challenge?.digest) {
|
|
3480
|
+
bodyDigestOk = false;
|
|
3481
|
+
} else {
|
|
3482
|
+
try {
|
|
3483
|
+
if (!/^sha-256=/.test(challenge.digest)) {
|
|
3484
|
+
bodyDigestOk = false;
|
|
3485
|
+
} else {
|
|
3486
|
+
bodyDigestOk = BodyDigest.verify(challenge.digest, input.rawBody);
|
|
3487
|
+
}
|
|
3488
|
+
} catch {
|
|
3199
3489
|
bodyDigestOk = false;
|
|
3200
|
-
} else {
|
|
3201
|
-
bodyDigestOk = BodyDigest.verify(challenge.digest, input.rawBody);
|
|
3202
3490
|
}
|
|
3203
|
-
} catch {
|
|
3204
|
-
bodyDigestOk = false;
|
|
3205
3491
|
}
|
|
3206
3492
|
}
|
|
3207
|
-
|
|
3493
|
+
let replayOk = true;
|
|
3494
|
+
if (challenge?.digest && expiryOk) {
|
|
3495
|
+
const replayKey = `mpp:${challenge.digest}:${challenge.nonce ?? ""}`;
|
|
3496
|
+
const expiresAt = (nowSec + tolerance) * 1e3;
|
|
3497
|
+
if (nonceStore.seen(replayKey, expiresAt)) {
|
|
3498
|
+
replayOk = false;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3501
|
+
const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true) && replayOk;
|
|
3208
3502
|
const errors = [];
|
|
3209
3503
|
if (!expiryOk) errors.push("challenge expired");
|
|
3210
|
-
if (bodyDigestOk === false)
|
|
3504
|
+
if (bodyDigestOk === false) {
|
|
3505
|
+
errors.push(
|
|
3506
|
+
input.rawBody !== void 0 && !challenge?.digest ? "body digest required when rawBody present" : "body digest mismatch"
|
|
3507
|
+
);
|
|
3508
|
+
}
|
|
3509
|
+
if (!replayOk) errors.push("MPP challenge replay \u2014 already seen within tolerance window");
|
|
3211
3510
|
return {
|
|
3212
3511
|
ok,
|
|
3213
3512
|
expiryOk,
|
|
@@ -3371,14 +3670,32 @@ function readHeader4(headers, name) {
|
|
|
3371
3670
|
import { createHash as createHash3, webcrypto } from "crypto";
|
|
3372
3671
|
async function verifyVIChain(input) {
|
|
3373
3672
|
const errors = [];
|
|
3374
|
-
const tolerance = input.clockSkewSec ??
|
|
3673
|
+
const tolerance = input.clockSkewSec ?? 60;
|
|
3375
3674
|
const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
|
|
3376
3675
|
const { l1, l2, l3a, l3b } = input.layers;
|
|
3676
|
+
const nonceStore = input.nonceStore ?? defaultNonceStore;
|
|
3677
|
+
if (!l1) {
|
|
3678
|
+
if (!input.allowUnboundChain) {
|
|
3679
|
+
errors.push(
|
|
3680
|
+
"L1 missing \u2014 chain root unbound (set allowUnboundChain + expectedL2Key to override)"
|
|
3681
|
+
);
|
|
3682
|
+
} else if (!input.expectedL2Key) {
|
|
3683
|
+
errors.push("allowUnboundChain set but expectedL2Key missing");
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3377
3686
|
const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;
|
|
3378
3687
|
if (l1 && !l1SigOk) errors.push("L1 signature invalid");
|
|
3379
3688
|
const l1Cnf = extractCnfJwk(l1?.payload);
|
|
3380
|
-
const
|
|
3689
|
+
const l2ExpectedKey = l1Cnf ?? input.expectedL2Key ?? null;
|
|
3690
|
+
const l2SigOk = await input.verifySignature(l2, l2ExpectedKey);
|
|
3381
3691
|
if (!l2SigOk) errors.push("L2 signature invalid");
|
|
3692
|
+
if (l2SigOk) {
|
|
3693
|
+
const replayKey = `vi:l2:${l2.compact}`;
|
|
3694
|
+
const expiresAt = now * 1e3 + tolerance * 1e3;
|
|
3695
|
+
if (nonceStore.seen(replayKey, expiresAt)) {
|
|
3696
|
+
errors.push("L2 signature replay \u2014 already seen within tolerance window");
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3382
3699
|
const l2Cnf = extractCnfJwk(l2.payload);
|
|
3383
3700
|
const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;
|
|
3384
3701
|
if (l3a && !l3aSigOk) errors.push("L3a signature invalid");
|
|
@@ -3422,7 +3739,10 @@ async function verifyVIChain(input) {
|
|
|
3422
3739
|
}
|
|
3423
3740
|
}
|
|
3424
3741
|
const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);
|
|
3425
|
-
const
|
|
3742
|
+
const noUnboundChainOrReplayErrors = !errors.some(
|
|
3743
|
+
(e) => e.startsWith("L1 missing") || e.startsWith("allowUnboundChain set") || e.startsWith("L2 signature replay")
|
|
3744
|
+
);
|
|
3745
|
+
const ok = l1SigOk !== false && l2SigOk && l3aSigOk !== false && l3bSigOk !== false && l1BindsL2 && l2BindsL3 && l3aL3bTxnIdMatch !== false && checkoutHashOk !== false && expiryOk && noUnboundChainOrReplayErrors;
|
|
3426
3746
|
return {
|
|
3427
3747
|
ok,
|
|
3428
3748
|
checks: {
|
|
@@ -3855,7 +4175,7 @@ async function exportJwkFromKeyLike(keyLike) {
|
|
|
3855
4175
|
|
|
3856
4176
|
// src/transport/registry/mastercard.ts
|
|
3857
4177
|
function createMastercardRegistry(options = {}) {
|
|
3858
|
-
const
|
|
4178
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
3859
4179
|
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3860
4180
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3861
4181
|
let warned = false;
|
|
@@ -3872,7 +4192,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3872
4192
|
}
|
|
3873
4193
|
return null;
|
|
3874
4194
|
}
|
|
3875
|
-
const cached =
|
|
4195
|
+
const cached = cache2.get(kid);
|
|
3876
4196
|
if (cached && cached.expiresAt > Date.now()) return cached.jwk;
|
|
3877
4197
|
try {
|
|
3878
4198
|
const res = await fetchFn(options.registryUrl);
|
|
@@ -3881,7 +4201,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3881
4201
|
const keys = body.keys ?? [];
|
|
3882
4202
|
for (const k of keys) {
|
|
3883
4203
|
if (k.kid === kid) {
|
|
3884
|
-
|
|
4204
|
+
cache2.set(kid, { jwk: k, expiresAt: Date.now() + ttlSec * 1e3 });
|
|
3885
4205
|
return k;
|
|
3886
4206
|
}
|
|
3887
4207
|
}
|
|
@@ -3896,7 +4216,7 @@ function createMastercardRegistry(options = {}) {
|
|
|
3896
4216
|
// src/transport/registry/web-bot-auth.ts
|
|
3897
4217
|
var DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
3898
4218
|
function createWebBotAuthRegistry(options = {}) {
|
|
3899
|
-
const
|
|
4219
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
3900
4220
|
const ttlSec = options.cacheTtlSec ?? 3600;
|
|
3901
4221
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
3902
4222
|
return {
|
|
@@ -3905,7 +4225,7 @@ function createWebBotAuthRegistry(options = {}) {
|
|
|
3905
4225
|
if (!kid) return null;
|
|
3906
4226
|
const directoryUrl = resolveDirectoryUrl(options.directoryUrl, context?.origin);
|
|
3907
4227
|
if (!directoryUrl) return null;
|
|
3908
|
-
const cached =
|
|
4228
|
+
const cached = cache2.get(directoryUrl);
|
|
3909
4229
|
const now = Date.now();
|
|
3910
4230
|
if (cached && cached.expiresAt > now) {
|
|
3911
4231
|
return findKeyByKid(cached.keys, kid);
|
|
@@ -3915,7 +4235,7 @@ function createWebBotAuthRegistry(options = {}) {
|
|
|
3915
4235
|
if (!res.ok) return null;
|
|
3916
4236
|
const body = await res.json();
|
|
3917
4237
|
const keys = body.keys ?? [];
|
|
3918
|
-
|
|
4238
|
+
cache2.set(directoryUrl, { keys, expiresAt: now + ttlSec * 1e3 });
|
|
3919
4239
|
return findKeyByKid(keys, kid);
|
|
3920
4240
|
} catch {
|
|
3921
4241
|
return null;
|
|
@@ -4054,19 +4374,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
|
|
|
4054
4374
|
}
|
|
4055
4375
|
return { value: void 0, source: void 0 };
|
|
4056
4376
|
}
|
|
4057
|
-
function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
4058
|
-
const resource =
|
|
4377
|
+
function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
|
|
4378
|
+
const resource = toolGate?.resource ?? requestPath;
|
|
4059
4379
|
let purpose;
|
|
4060
4380
|
let purposeSource;
|
|
4061
|
-
if (
|
|
4381
|
+
if (toolGate?.purpose !== void 0) {
|
|
4382
|
+
purpose = toolGate.purpose;
|
|
4383
|
+
purposeSource = "tool_gate";
|
|
4384
|
+
} else if (headerPurpose) {
|
|
4062
4385
|
purpose = headerPurpose;
|
|
4063
4386
|
purposeSource = "header";
|
|
4064
4387
|
} else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
|
|
4065
4388
|
purpose = parsed.purposeFromBody;
|
|
4066
4389
|
purposeSource = parsed.purposeSourceFromBody;
|
|
4067
4390
|
} else {
|
|
4068
|
-
purpose =
|
|
4069
|
-
purposeSource =
|
|
4391
|
+
purpose = void 0;
|
|
4392
|
+
purposeSource = void 0;
|
|
4070
4393
|
}
|
|
4071
4394
|
let action;
|
|
4072
4395
|
let actionSource;
|
|
@@ -4077,7 +4400,7 @@ function mcpToPdlss(parsed, headerPurpose, headerAction) {
|
|
|
4077
4400
|
action = parsed.actionFromBody;
|
|
4078
4401
|
actionSource = parsed.actionSourceFromBody;
|
|
4079
4402
|
} else {
|
|
4080
|
-
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4403
|
+
action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4081
4404
|
actionSource = "transport_layer";
|
|
4082
4405
|
}
|
|
4083
4406
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -4091,11 +4414,28 @@ function mcpRiskTier(parsed) {
|
|
|
4091
4414
|
}
|
|
4092
4415
|
|
|
4093
4416
|
// src/adapters/mcp.ts
|
|
4417
|
+
function normalizeToolGate(gate) {
|
|
4418
|
+
return typeof gate === "string" ? { minAccessLevel: gate } : gate;
|
|
4419
|
+
}
|
|
4094
4420
|
function readSingleHeader(value) {
|
|
4095
4421
|
if (typeof value === "string") return value;
|
|
4096
4422
|
if (Array.isArray(value)) return value[0];
|
|
4097
4423
|
return void 0;
|
|
4098
4424
|
}
|
|
4425
|
+
function dedupeFailures2(result) {
|
|
4426
|
+
if (result.failures && result.failures.length > 1) {
|
|
4427
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4428
|
+
result.failures = result.failures.filter((f) => {
|
|
4429
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
4430
|
+
if (seen.has(key)) return false;
|
|
4431
|
+
seen.add(key);
|
|
4432
|
+
return true;
|
|
4433
|
+
});
|
|
4434
|
+
}
|
|
4435
|
+
if (result.denialReasons && result.denialReasons.length > 1) {
|
|
4436
|
+
result.denialReasons = [...new Set(result.denialReasons)];
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4099
4439
|
function defaultMcpDenied(result, req, res) {
|
|
4100
4440
|
const id = req.body?.id ?? null;
|
|
4101
4441
|
const status = !result.identityVerified ? 401 : 403;
|
|
@@ -4120,11 +4460,17 @@ function defaultMcpDenied(result, req, res) {
|
|
|
4120
4460
|
});
|
|
4121
4461
|
}
|
|
4122
4462
|
function resolveMinAccessLevel(parsed, opts) {
|
|
4123
|
-
if (parsed.toolName
|
|
4124
|
-
|
|
4463
|
+
if (!parsed.toolName) {
|
|
4464
|
+
if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
|
|
4465
|
+
return { level: opts.methodGates[parsed.method], source: "methodGate" };
|
|
4466
|
+
}
|
|
4467
|
+
return { level: "none", source: "discovery_default" };
|
|
4125
4468
|
}
|
|
4126
|
-
if (opts.
|
|
4127
|
-
return {
|
|
4469
|
+
if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
|
|
4470
|
+
return {
|
|
4471
|
+
level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
|
|
4472
|
+
source: "toolGate"
|
|
4473
|
+
};
|
|
4128
4474
|
}
|
|
4129
4475
|
return { level: mcpRiskTier(parsed), source: "tier" };
|
|
4130
4476
|
}
|
|
@@ -4135,12 +4481,17 @@ function createMcpMiddleware(options) {
|
|
|
4135
4481
|
onAgentIdMismatch = "reject",
|
|
4136
4482
|
skip = false,
|
|
4137
4483
|
onDenied = defaultMcpDenied,
|
|
4138
|
-
trustVerifiedHop =
|
|
4484
|
+
trustVerifiedHop = false,
|
|
4139
4485
|
verifiedHopMaxAgeMs,
|
|
4140
4486
|
recordDecisions,
|
|
4141
4487
|
enableRuntimeChallenge = true,
|
|
4488
|
+
failOnError = "open",
|
|
4142
4489
|
...config
|
|
4143
4490
|
} = options;
|
|
4491
|
+
if (config.apiBaseUrl) {
|
|
4492
|
+
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
4493
|
+
});
|
|
4494
|
+
}
|
|
4144
4495
|
return async (req, res, next) => {
|
|
4145
4496
|
try {
|
|
4146
4497
|
if (skip) return next();
|
|
@@ -4153,6 +4504,7 @@ function createMcpMiddleware(options) {
|
|
|
4153
4504
|
return next();
|
|
4154
4505
|
}
|
|
4155
4506
|
req.mcpRequest = parsed;
|
|
4507
|
+
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
4156
4508
|
const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
|
|
4157
4509
|
const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
|
|
4158
4510
|
const bodyAstraId = parsed.agentIdFromBody;
|
|
@@ -4206,9 +4558,17 @@ function createMcpMiddleware(options) {
|
|
|
4206
4558
|
}
|
|
4207
4559
|
return next();
|
|
4208
4560
|
}
|
|
4561
|
+
const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
|
|
4562
|
+
const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
|
|
4209
4563
|
const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
|
|
4210
4564
|
const headerAction = readSingleHeader(req.headers["x-astra-action"]);
|
|
4211
|
-
const pdlss = mcpToPdlss(
|
|
4565
|
+
const pdlss = mcpToPdlss(
|
|
4566
|
+
parsed,
|
|
4567
|
+
req.path,
|
|
4568
|
+
headerPurpose,
|
|
4569
|
+
headerAction,
|
|
4570
|
+
gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
|
|
4571
|
+
);
|
|
4212
4572
|
if (config.debug) {
|
|
4213
4573
|
console.debug("[mcp-middleware] pdlss resolved", {
|
|
4214
4574
|
purpose_source: pdlss.purposeSource,
|
|
@@ -4246,6 +4606,7 @@ function createMcpMiddleware(options) {
|
|
|
4246
4606
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
4247
4607
|
});
|
|
4248
4608
|
}
|
|
4609
|
+
dedupeFailures2(result);
|
|
4249
4610
|
onDenied(result, req, res);
|
|
4250
4611
|
return;
|
|
4251
4612
|
}
|
|
@@ -4268,6 +4629,13 @@ function createMcpMiddleware(options) {
|
|
|
4268
4629
|
};
|
|
4269
4630
|
result.failures = [...result.failures ?? [], insufficientFailure];
|
|
4270
4631
|
result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
|
|
4632
|
+
if (!result.guidance && wellKnownUrls) {
|
|
4633
|
+
result.guidance = {
|
|
4634
|
+
message: insufficientFailure.message,
|
|
4635
|
+
registrationUrl: wellKnownUrls.registrationUrl,
|
|
4636
|
+
documentationUrl: wellKnownUrls.documentationUrl
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4271
4639
|
if (shouldRecordDecisions) {
|
|
4272
4640
|
const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
|
|
4273
4641
|
const override = {
|
|
@@ -4291,6 +4659,7 @@ function createMcpMiddleware(options) {
|
|
|
4291
4659
|
});
|
|
4292
4660
|
}
|
|
4293
4661
|
}
|
|
4662
|
+
dedupeFailures2(result);
|
|
4294
4663
|
onDenied(result, req, res);
|
|
4295
4664
|
return;
|
|
4296
4665
|
}
|
|
@@ -4314,7 +4683,38 @@ function createMcpMiddleware(options) {
|
|
|
4314
4683
|
}
|
|
4315
4684
|
next();
|
|
4316
4685
|
} catch (error) {
|
|
4686
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
4687
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4317
4688
|
console.error("[VerificationGateway/MCP] Middleware error:", error);
|
|
4689
|
+
console.warn(
|
|
4690
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
4691
|
+
);
|
|
4692
|
+
if (failOnError === "closed") {
|
|
4693
|
+
const result = {
|
|
4694
|
+
identityVerified: false,
|
|
4695
|
+
policyAllowed: false,
|
|
4696
|
+
accessLevel: "none",
|
|
4697
|
+
denialReasons: [`MCP middleware internal error: ${errorClass}`],
|
|
4698
|
+
failures: [
|
|
4699
|
+
{
|
|
4700
|
+
dimension: "middleware.internal_error",
|
|
4701
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
4702
|
+
}
|
|
4703
|
+
],
|
|
4704
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
4705
|
+
correlationId
|
|
4706
|
+
};
|
|
4707
|
+
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
4708
|
+
if (catchUrls) {
|
|
4709
|
+
result.guidance = {
|
|
4710
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
4711
|
+
registrationUrl: catchUrls.registrationUrl,
|
|
4712
|
+
documentationUrl: catchUrls.documentationUrl
|
|
4713
|
+
};
|
|
4714
|
+
}
|
|
4715
|
+
dedupeFailures2(result);
|
|
4716
|
+
return onDenied(result, req, res);
|
|
4717
|
+
}
|
|
4318
4718
|
next();
|
|
4319
4719
|
}
|
|
4320
4720
|
};
|
|
@@ -5021,11 +5421,14 @@ export {
|
|
|
5021
5421
|
extractCredentials,
|
|
5022
5422
|
extractMcpCredentials,
|
|
5023
5423
|
getAccessLevelForScore,
|
|
5424
|
+
getCachedWellKnownUrls,
|
|
5024
5425
|
getCapabilities,
|
|
5025
5426
|
getTrustLevel,
|
|
5427
|
+
getWellKnownUrls,
|
|
5026
5428
|
hasCredentials,
|
|
5027
5429
|
hasMinimumAccess,
|
|
5028
5430
|
nextjs_exports as nextjs,
|
|
5431
|
+
prefetchWellKnown,
|
|
5029
5432
|
quickVerify,
|
|
5030
5433
|
recordDecision2 as recordDecision,
|
|
5031
5434
|
sdk_exports as sdk,
|