@astrasyncai/verification-gateway 3.12.0 → 4.1.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/README.md +106 -29
- 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 +482 -22
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +482 -22
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +3 -3
- package/dist/adapters/http-pdlss.d.ts +3 -3
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +56 -36
- package/dist/adapters/mcp.d.ts +56 -36
- package/dist/adapters/mcp.js +309 -10
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +309 -10
- 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 +518 -25
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +518 -25
- 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 +72 -9
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +72 -9
- 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.map +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +83 -20
- package/dist/bin/astrasync-codex-hook.js +83 -20
- package/dist/bin/astrasync-guard.js +83 -20
- package/dist/bin/astrasync.js +150 -33
- package/dist/browser/background.js +83 -20
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +83 -20
- 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/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/codex/index.d.mts +3 -3
- package/dist/codex/index.d.ts +3 -3
- package/dist/codex/index.js +83 -20
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +83 -20
- package/dist/codex/index.mjs.map +1 -1
- 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 +83 -20
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +83 -20
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +22 -7
- package/dist/edge-config.d.ts +22 -7
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +425 -0
- package/dist/edge-core/index.d.ts +425 -0
- package/dist/edge-core/index.js +1482 -0
- package/dist/edge-core/index.js.map +1 -0
- package/dist/edge-core/index.mjs +1437 -0
- package/dist/edge-core/index.mjs.map +1 -0
- package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
- package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +83 -20
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +83 -20
- 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-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
- package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
- package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
- package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
- package/dist/index.d.mts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +595 -272
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +592 -272
- 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/metadata-capture.d.mts +63 -4
- package/dist/metadata-capture.d.ts +63 -4
- package/dist/metadata-capture.js +67 -0
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +64 -0
- package/dist/metadata-capture.mjs.map +1 -1
- package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
- package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
- package/dist/platform-signatures.d.mts +5 -6
- package/dist/platform-signatures.d.ts +5 -6
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.d.mts +10 -11
- package/dist/registration/index.d.ts +10 -11
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
- package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
- package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
- package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
- package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
- package/dist/ui/index.d.mts +11 -7
- package/dist/ui/index.d.ts +11 -7
- package/dist/ui/index.js +13 -9
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +10 -8
- package/dist/ui/index.mjs.map +1 -1
- package/dist/verify.d.mts +11 -8
- package/dist/verify.d.ts +11 -8
- package/dist/verify.js +76 -8
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +75 -8
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(src_exports, {
|
|
|
36
36
|
AstraSync: () => AstraSync,
|
|
37
37
|
AstraSyncError: () => AstraSyncError,
|
|
38
38
|
AuthenticationError: () => AuthenticationError,
|
|
39
|
+
CAPTURE_SCHEMA_VERSION: () => CAPTURE_SCHEMA_VERSION,
|
|
39
40
|
ChallengeHandler: () => ChallengeHandler,
|
|
40
41
|
DEFAULT_EDGE_CONFIG: () => DEFAULT_EDGE_CONFIG,
|
|
41
42
|
DEFAULT_TRUST_THRESHOLDS: () => DEFAULT_TRUST_THRESHOLDS,
|
|
@@ -54,9 +55,11 @@ __export(src_exports, {
|
|
|
54
55
|
authorizeSettlement: () => authorizeSettlement,
|
|
55
56
|
awaitStepUpApproval: () => awaitStepUpApproval,
|
|
56
57
|
buildGuidance: () => buildGuidance,
|
|
58
|
+
buildSdkObservedMetadata: () => buildSdkObservedMetadata,
|
|
57
59
|
clearCache: () => clearCache,
|
|
58
60
|
createMcpMiddleware: () => createMcpMiddleware,
|
|
59
61
|
degradeToObserve: () => degradeToObserve,
|
|
62
|
+
deriveConnectionFromHeaders: () => deriveConnectionFromHeaders,
|
|
60
63
|
detectPlatformFingerprint: () => detectPlatformFingerprint,
|
|
61
64
|
determineAccessLevel: () => determineAccessLevel,
|
|
62
65
|
express: () => express_exports,
|
|
@@ -214,7 +217,7 @@ function getCapabilities(accessLevel) {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
// src/version.ts
|
|
217
|
-
var SDK_VERSION = "
|
|
220
|
+
var SDK_VERSION = "4.1.0";
|
|
218
221
|
|
|
219
222
|
// src/well-known.ts
|
|
220
223
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -292,7 +295,7 @@ var DEFAULT_CONFIG = {
|
|
|
292
295
|
};
|
|
293
296
|
var initCheckPerformed = false;
|
|
294
297
|
var deprecationWarningShown = false;
|
|
295
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
298
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
296
299
|
initCheckPerformed = true;
|
|
297
300
|
try {
|
|
298
301
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -309,6 +312,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
309
312
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
310
313
|
);
|
|
311
314
|
}
|
|
315
|
+
if (apiKey) {
|
|
316
|
+
const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
317
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
318
|
+
});
|
|
319
|
+
if (selfCheck.status === 401) {
|
|
320
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
321
|
+
const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
|
|
322
|
+
if (strictInit) {
|
|
323
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
324
|
+
}
|
|
325
|
+
console.error(message);
|
|
326
|
+
} else if (selfCheck.ok) {
|
|
327
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
328
|
+
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
329
|
+
console.warn(
|
|
330
|
+
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
331
|
+
);
|
|
332
|
+
} else if (debug) {
|
|
333
|
+
console.log("[VerificationGateway] API key self-check passed");
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
312
337
|
} catch (err) {
|
|
313
338
|
if (strictInit) {
|
|
314
339
|
throw err;
|
|
@@ -426,8 +451,17 @@ function hasCredentials(credentials) {
|
|
|
426
451
|
function createGuidanceResponse(_config, reason, options = {}) {
|
|
427
452
|
const source = options.source ?? "no_credentials";
|
|
428
453
|
const isApiError = source === "api_error";
|
|
454
|
+
const isMisconfig = source === "merchant_misconfig";
|
|
429
455
|
const urls = options.urls;
|
|
430
|
-
const guidance =
|
|
456
|
+
const guidance = isMisconfig ? {
|
|
457
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
458
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
459
|
+
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
460
|
+
steps: [
|
|
461
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
|
|
462
|
+
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
463
|
+
]
|
|
464
|
+
} : isApiError ? {
|
|
431
465
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
432
466
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
433
467
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
@@ -465,13 +499,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
465
499
|
// partners (and their custom onDenied handlers) can branch on
|
|
466
500
|
// dimension. Without this, the synthesised stub was indistinguishable
|
|
467
501
|
// from a real policy deny.
|
|
468
|
-
failures:
|
|
502
|
+
failures: isMisconfig ? [
|
|
503
|
+
{
|
|
504
|
+
dimension: "verify_access.merchant_misconfigured",
|
|
505
|
+
message: reason ?? "The integration API key was rejected by AstraSync",
|
|
506
|
+
guidance: guidance.message
|
|
507
|
+
}
|
|
508
|
+
] : isApiError ? [
|
|
469
509
|
{
|
|
470
510
|
dimension: "verify_access.api_error",
|
|
471
511
|
message: reason ?? "Verification temporarily unavailable",
|
|
472
512
|
guidance: guidance.message
|
|
473
513
|
}
|
|
474
514
|
] : void 0,
|
|
515
|
+
// 4.0.0: typed marker so adapters render the misconfig envelope (503 +
|
|
516
|
+
// MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
|
|
517
|
+
...isMisconfig && { misconfigured: true },
|
|
475
518
|
correlationId: options.correlationId,
|
|
476
519
|
attemptId: options.attemptId,
|
|
477
520
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -526,7 +569,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
526
569
|
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
527
570
|
method: "POST",
|
|
528
571
|
headers,
|
|
529
|
-
body: JSON.stringify(body)
|
|
572
|
+
body: JSON.stringify(body),
|
|
573
|
+
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
530
574
|
});
|
|
531
575
|
const data = await response.json();
|
|
532
576
|
if (response.status === 410) {
|
|
@@ -550,6 +594,17 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
550
594
|
};
|
|
551
595
|
}
|
|
552
596
|
if (!response.ok) {
|
|
597
|
+
if (response.status === 401) {
|
|
598
|
+
return {
|
|
599
|
+
success: false,
|
|
600
|
+
error: data.message || data.error || "Integration API key rejected",
|
|
601
|
+
misconfig: true,
|
|
602
|
+
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
603
|
+
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
604
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
605
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
606
|
+
};
|
|
607
|
+
}
|
|
553
608
|
return {
|
|
554
609
|
success: false,
|
|
555
610
|
error: data.message || data.error || `API returned ${response.status}`,
|
|
@@ -571,9 +626,19 @@ async function verify(config, request, options) {
|
|
|
571
626
|
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
572
627
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
573
628
|
if (mergedConfig.strictInit) {
|
|
574
|
-
await performInitCheck(
|
|
629
|
+
await performInitCheck(
|
|
630
|
+
mergedConfig.apiBaseUrl,
|
|
631
|
+
mergedConfig.debug,
|
|
632
|
+
true,
|
|
633
|
+
mergedConfig.apiKey
|
|
634
|
+
);
|
|
575
635
|
} else {
|
|
576
|
-
void performInitCheck(
|
|
636
|
+
void performInitCheck(
|
|
637
|
+
mergedConfig.apiBaseUrl,
|
|
638
|
+
mergedConfig.debug,
|
|
639
|
+
false,
|
|
640
|
+
mergedConfig.apiKey
|
|
641
|
+
);
|
|
577
642
|
}
|
|
578
643
|
}
|
|
579
644
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
@@ -612,9 +677,10 @@ async function verify(config, request, options) {
|
|
|
612
677
|
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
613
678
|
if (!apiResponse.success) {
|
|
614
679
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
615
|
-
source: "api_error",
|
|
680
|
+
source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
|
|
616
681
|
correlationId: apiResponse.correlationId,
|
|
617
682
|
attemptId: apiResponse.attemptId,
|
|
683
|
+
remediation: apiResponse.remediation,
|
|
618
684
|
urls
|
|
619
685
|
});
|
|
620
686
|
}
|
|
@@ -1149,6 +1215,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
|
1149
1215
|
}
|
|
1150
1216
|
|
|
1151
1217
|
// src/metadata-capture.ts
|
|
1218
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
1152
1219
|
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
1153
1220
|
"cookie",
|
|
1154
1221
|
"set-cookie",
|
|
@@ -1293,6 +1360,373 @@ function extractPlatformHeaders(sanitized) {
|
|
|
1293
1360
|
}
|
|
1294
1361
|
return out;
|
|
1295
1362
|
}
|
|
1363
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
1364
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
1365
|
+
[
|
|
1366
|
+
"country",
|
|
1367
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
1368
|
+
],
|
|
1369
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
1370
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
1371
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
1372
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
1373
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
1374
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
1375
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
1376
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
1377
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
1378
|
+
["ja4", ["cf-ja4"]]
|
|
1379
|
+
];
|
|
1380
|
+
function deriveConnectionFromHeaders(headers) {
|
|
1381
|
+
if (!headers) return void 0;
|
|
1382
|
+
const lower = {};
|
|
1383
|
+
for (const name of Object.keys(headers)) {
|
|
1384
|
+
const value = headers[name];
|
|
1385
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
1386
|
+
}
|
|
1387
|
+
const connection = {};
|
|
1388
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
1389
|
+
for (const source of sources) {
|
|
1390
|
+
const value = lower[source]?.trim();
|
|
1391
|
+
if (value) {
|
|
1392
|
+
connection[key] = value;
|
|
1393
|
+
break;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
if (!connection.ip) {
|
|
1398
|
+
const xff = lower["x-forwarded-for"];
|
|
1399
|
+
const client = xff?.split(",")[0]?.trim();
|
|
1400
|
+
if (client) connection.ip = client;
|
|
1401
|
+
}
|
|
1402
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
1403
|
+
}
|
|
1404
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
1405
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1406
|
+
const flat = {};
|
|
1407
|
+
if (rawHeaders) {
|
|
1408
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
1409
|
+
const value = rawHeaders[name];
|
|
1410
|
+
if (value === void 0) continue;
|
|
1411
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
1415
|
+
return {
|
|
1416
|
+
headers,
|
|
1417
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
1418
|
+
...platformHeaders && { platformHeaders },
|
|
1419
|
+
...connection && { connection },
|
|
1420
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// src/edge-config.ts
|
|
1425
|
+
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
1426
|
+
version: 1,
|
|
1427
|
+
mode: "observe",
|
|
1428
|
+
depth: "classify",
|
|
1429
|
+
pathRules: [],
|
|
1430
|
+
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
1431
|
+
failurePosture: "open"
|
|
1432
|
+
});
|
|
1433
|
+
var EDGE_MODES = ["observe", "enforce"];
|
|
1434
|
+
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
1435
|
+
function isEdgeConfig(value) {
|
|
1436
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1437
|
+
const v = value;
|
|
1438
|
+
if (v.version !== 1) return false;
|
|
1439
|
+
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
1440
|
+
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
1441
|
+
if (!Array.isArray(v.pathRules)) return false;
|
|
1442
|
+
for (const rule of v.pathRules) {
|
|
1443
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
1444
|
+
const r = rule;
|
|
1445
|
+
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
1446
|
+
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
1447
|
+
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
1448
|
+
}
|
|
1449
|
+
const sampling = v.sampling;
|
|
1450
|
+
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
return v.failurePosture === "open";
|
|
1454
|
+
}
|
|
1455
|
+
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
1456
|
+
if (!counterpartyId) return null;
|
|
1457
|
+
const headers = { Accept: "application/json" };
|
|
1458
|
+
if (config.apiKey) {
|
|
1459
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1460
|
+
headers["X-API-Key"] = config.apiKey;
|
|
1461
|
+
}
|
|
1462
|
+
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
1463
|
+
try {
|
|
1464
|
+
const response = await fetch(
|
|
1465
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
1466
|
+
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
1467
|
+
);
|
|
1468
|
+
if (response.status === 304) return { notModified: true };
|
|
1469
|
+
if (!response.ok) return null;
|
|
1470
|
+
const body = await response.json();
|
|
1471
|
+
const edgeConfig = body.data?.edgeConfig;
|
|
1472
|
+
if (!isEdgeConfig(edgeConfig)) return null;
|
|
1473
|
+
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
1474
|
+
} catch {
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
1479
|
+
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1480
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1481
|
+
var inflight2 = /* @__PURE__ */ new Map();
|
|
1482
|
+
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
1483
|
+
return `${apiBaseUrl}|${counterpartyId}`;
|
|
1484
|
+
}
|
|
1485
|
+
function degradeToObserve(config) {
|
|
1486
|
+
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
1487
|
+
return config;
|
|
1488
|
+
}
|
|
1489
|
+
return {
|
|
1490
|
+
...config,
|
|
1491
|
+
mode: "observe",
|
|
1492
|
+
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
async function revalidate(config, counterpartyId) {
|
|
1496
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1497
|
+
const existing = inflight2.get(key);
|
|
1498
|
+
if (existing) return existing;
|
|
1499
|
+
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
1500
|
+
if (result === null) return;
|
|
1501
|
+
const now = Date.now();
|
|
1502
|
+
if ("notModified" in result) {
|
|
1503
|
+
const entry = cache2.get(key);
|
|
1504
|
+
if (entry) entry.lastSuccessAt = now;
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
1508
|
+
}).finally(() => {
|
|
1509
|
+
inflight2.delete(key);
|
|
1510
|
+
});
|
|
1511
|
+
inflight2.set(key, promise);
|
|
1512
|
+
return promise;
|
|
1513
|
+
}
|
|
1514
|
+
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
1515
|
+
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
1516
|
+
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
1517
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1518
|
+
const entry = cache2.get(key);
|
|
1519
|
+
if (entry) {
|
|
1520
|
+
const age = Date.now() - entry.lastSuccessAt;
|
|
1521
|
+
if (age > ttlMs) {
|
|
1522
|
+
revalidate(config, counterpartyId).catch(() => {
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
1526
|
+
}
|
|
1527
|
+
await revalidate(config, counterpartyId).catch(() => {
|
|
1528
|
+
});
|
|
1529
|
+
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
1530
|
+
}
|
|
1531
|
+
function matchEdgePathRule(rules, path) {
|
|
1532
|
+
for (const rule of rules) {
|
|
1533
|
+
if (globToRegExp(rule.pattern).test(path)) return rule;
|
|
1534
|
+
}
|
|
1535
|
+
return void 0;
|
|
1536
|
+
}
|
|
1537
|
+
function globToRegExp(pattern) {
|
|
1538
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1539
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
1540
|
+
}
|
|
1541
|
+
function _resetEdgeConfigCache() {
|
|
1542
|
+
cache2.clear();
|
|
1543
|
+
inflight2.clear();
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/platform-signatures.ts
|
|
1547
|
+
var PLATFORM_AGENT_SIGNATURES = [
|
|
1548
|
+
{
|
|
1549
|
+
vendor: "claude",
|
|
1550
|
+
displayName: "Anthropic Claude",
|
|
1551
|
+
uaPatterns: [/claude/i, /anthropic/i],
|
|
1552
|
+
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
1553
|
+
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
1554
|
+
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
vendor: "chatgpt",
|
|
1558
|
+
displayName: "OpenAI ChatGPT",
|
|
1559
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
1560
|
+
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1561
|
+
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1562
|
+
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
vendor: "gemini",
|
|
1566
|
+
displayName: "Google Gemini",
|
|
1567
|
+
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
1568
|
+
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
1569
|
+
apiKeyFormatPatterns: [/^aiza/i],
|
|
1570
|
+
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
vendor: "cursor",
|
|
1574
|
+
displayName: "Cursor",
|
|
1575
|
+
uaPatterns: [/cursor/i],
|
|
1576
|
+
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
vendor: "goose",
|
|
1580
|
+
displayName: "Block Goose",
|
|
1581
|
+
uaPatterns: [/goose/i, /block-goose/i],
|
|
1582
|
+
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
1583
|
+
},
|
|
1584
|
+
{
|
|
1585
|
+
vendor: "perplexity",
|
|
1586
|
+
displayName: "Perplexity",
|
|
1587
|
+
uaPatterns: [/perplexity/i],
|
|
1588
|
+
agentCardPatterns: [/perplexity\.ai/i],
|
|
1589
|
+
apiKeyFormatPatterns: [/^pplx-/i]
|
|
1590
|
+
}
|
|
1591
|
+
];
|
|
1592
|
+
function matchPlatformSignature(signatures, input) {
|
|
1593
|
+
const ua = input.userAgent ?? "";
|
|
1594
|
+
const cardUrl = input.agentCardUrl ?? "";
|
|
1595
|
+
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
1596
|
+
const headerLines = [];
|
|
1597
|
+
if (input.platformHeaders) {
|
|
1598
|
+
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
1599
|
+
headerLines.push(name, `${name}: ${value}`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
for (const sig of signatures) {
|
|
1603
|
+
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
1604
|
+
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
1605
|
+
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
1606
|
+
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
1607
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
1608
|
+
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
1609
|
+
const cardHost = (() => {
|
|
1610
|
+
try {
|
|
1611
|
+
return cardUrl ? new URL(cardUrl).host : "";
|
|
1612
|
+
} catch {
|
|
1613
|
+
return "";
|
|
1614
|
+
}
|
|
1615
|
+
})();
|
|
1616
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
1617
|
+
return {
|
|
1618
|
+
vendor: sig.vendor,
|
|
1619
|
+
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
1620
|
+
displayName: sig.displayName
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
return void 0;
|
|
1625
|
+
}
|
|
1626
|
+
function detectPlatformFingerprint(input) {
|
|
1627
|
+
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// src/edge-core/classify.ts
|
|
1631
|
+
var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
|
|
1632
|
+
var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
|
|
1633
|
+
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
1634
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1635
|
+
|
|
1636
|
+
// src/edge-core/events.ts
|
|
1637
|
+
function sampled(rate, random = Math.random) {
|
|
1638
|
+
if (rate <= 0) return false;
|
|
1639
|
+
if (rate >= 1) return true;
|
|
1640
|
+
return random() < rate;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// src/adapters/anonymous-beacon.ts
|
|
1644
|
+
function readHeader(raw, name) {
|
|
1645
|
+
const value = raw[name];
|
|
1646
|
+
if (typeof value === "string") return value;
|
|
1647
|
+
if (Array.isArray(value)) return value[0];
|
|
1648
|
+
return void 0;
|
|
1649
|
+
}
|
|
1650
|
+
function classifyPassThroughTraffic(path, rawHeaders) {
|
|
1651
|
+
if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
|
|
1652
|
+
const credentials = extractCredentials(rawHeaders, {});
|
|
1653
|
+
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
1654
|
+
return { kind: "never" };
|
|
1655
|
+
}
|
|
1656
|
+
const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
|
|
1657
|
+
const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1658
|
+
const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
|
|
1659
|
+
if (fingerprint) {
|
|
1660
|
+
return {
|
|
1661
|
+
kind: "platform",
|
|
1662
|
+
protocol: "platform-ua",
|
|
1663
|
+
evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
|
|
1667
|
+
return {
|
|
1668
|
+
kind: "anonymous-bot",
|
|
1669
|
+
protocol: "ua-heuristic",
|
|
1670
|
+
evidence: { userAgent: userAgent || "(none)" }
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
|
|
1674
|
+
return {
|
|
1675
|
+
kind: "anonymous-bot",
|
|
1676
|
+
protocol: "ua-heuristic",
|
|
1677
|
+
evidence: { userAgent, spoofedBrowser: true }
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
return { kind: "never" };
|
|
1681
|
+
}
|
|
1682
|
+
function createAnonymousBeacon(config, options) {
|
|
1683
|
+
const counterpartyId = config.counterpartyId;
|
|
1684
|
+
if (!counterpartyId) return () => {
|
|
1685
|
+
};
|
|
1686
|
+
const random = options?.random ?? Math.random;
|
|
1687
|
+
let edgeConfig;
|
|
1688
|
+
let refreshing = false;
|
|
1689
|
+
const refreshConfig = () => {
|
|
1690
|
+
if (refreshing) return;
|
|
1691
|
+
refreshing = true;
|
|
1692
|
+
getEdgeConfig(config, counterpartyId).then((cfg) => {
|
|
1693
|
+
edgeConfig = cfg;
|
|
1694
|
+
}).catch(() => {
|
|
1695
|
+
}).finally(() => {
|
|
1696
|
+
refreshing = false;
|
|
1697
|
+
});
|
|
1698
|
+
};
|
|
1699
|
+
refreshConfig();
|
|
1700
|
+
return (input) => {
|
|
1701
|
+
try {
|
|
1702
|
+
refreshConfig();
|
|
1703
|
+
const cfg = edgeConfig;
|
|
1704
|
+
if (!cfg) return;
|
|
1705
|
+
const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
|
|
1706
|
+
if (traffic.kind === "never") return;
|
|
1707
|
+
if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const userAgent = readHeader(input.rawHeaders, "user-agent");
|
|
1711
|
+
const xff = input.rawHeaders["x-forwarded-for"];
|
|
1712
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1713
|
+
const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
|
|
1714
|
+
reportUnregisteredAttempt(config, {
|
|
1715
|
+
...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
|
|
1716
|
+
counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
|
|
1717
|
+
...sourceIp && { sourceIp },
|
|
1718
|
+
...userAgent && { userAgent },
|
|
1719
|
+
requestPath: input.requestPath,
|
|
1720
|
+
requestMethod: input.requestMethod,
|
|
1721
|
+
...traffic.protocol && { protocol: traffic.protocol },
|
|
1722
|
+
...traffic.evidence && { evidence: traffic.evidence },
|
|
1723
|
+
observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
|
|
1724
|
+
}).catch(() => {
|
|
1725
|
+
});
|
|
1726
|
+
} catch {
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1296
1730
|
|
|
1297
1731
|
// src/adapters/express.ts
|
|
1298
1732
|
function defaultExtractCredentials(req) {
|
|
@@ -1304,7 +1738,7 @@ function defaultExtractCredentials(req) {
|
|
|
1304
1738
|
function extractAstraSyncCredentials(req) {
|
|
1305
1739
|
return extractHttpCredentials(req.headers);
|
|
1306
1740
|
}
|
|
1307
|
-
function
|
|
1741
|
+
function headerValue2(value) {
|
|
1308
1742
|
if (typeof value === "string") return value;
|
|
1309
1743
|
if (Array.isArray(value)) return value[0];
|
|
1310
1744
|
return void 0;
|
|
@@ -1312,9 +1746,9 @@ function headerValue(value) {
|
|
|
1312
1746
|
function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
|
|
1313
1747
|
return resolveHttpPdlss({
|
|
1314
1748
|
method: req.method,
|
|
1315
|
-
astraPurpose:
|
|
1316
|
-
astraAction:
|
|
1317
|
-
legacyPurpose:
|
|
1749
|
+
astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
|
|
1750
|
+
astraAction: headerValue2(req.headers["x-astra-action"]),
|
|
1751
|
+
legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
|
|
1318
1752
|
queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
|
|
1319
1753
|
routePurpose: routeConfig?.purpose,
|
|
1320
1754
|
routeAction: routeConfig?.action,
|
|
@@ -1362,6 +1796,20 @@ function dedupeFailures(result) {
|
|
|
1362
1796
|
}
|
|
1363
1797
|
}
|
|
1364
1798
|
function defaultOnDenied(result, _req, res) {
|
|
1799
|
+
if (result.misconfigured) {
|
|
1800
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1801
|
+
res.status(503).json({
|
|
1802
|
+
success: false,
|
|
1803
|
+
error: {
|
|
1804
|
+
code: "MERCHANT_VERIFICATION_MISCONFIGURED",
|
|
1805
|
+
message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
|
|
1806
|
+
guidance: result.guidance,
|
|
1807
|
+
failures: result.failures,
|
|
1808
|
+
correlationId: result.correlationId
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1365
1813
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
1366
1814
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1367
1815
|
res.status(statusCode).json({
|
|
@@ -1383,6 +1831,11 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1383
1831
|
}
|
|
1384
1832
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1385
1833
|
function createMiddleware(options) {
|
|
1834
|
+
if ("routes" in options) {
|
|
1835
|
+
console.warn(
|
|
1836
|
+
"[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1386
1839
|
const {
|
|
1387
1840
|
extractCredentials: customExtractCredentials,
|
|
1388
1841
|
extractPurpose: customExtractPurpose,
|
|
@@ -1394,12 +1847,25 @@ function createMiddleware(options) {
|
|
|
1394
1847
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
1395
1848
|
failOnError = "open",
|
|
1396
1849
|
caseInsensitiveRouteMatch = false,
|
|
1850
|
+
anonymousBeacon = true,
|
|
1397
1851
|
...config
|
|
1398
1852
|
} = options;
|
|
1399
1853
|
if (config.apiBaseUrl) {
|
|
1400
1854
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1401
1855
|
});
|
|
1402
1856
|
}
|
|
1857
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
|
|
1858
|
+
const beaconPassThrough = emitAnonymousBeacon ? (req) => {
|
|
1859
|
+
const xff = req.headers["x-forwarded-for"];
|
|
1860
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1861
|
+
emitAnonymousBeacon({
|
|
1862
|
+
requestPath: req.path,
|
|
1863
|
+
requestMethod: req.method,
|
|
1864
|
+
counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
|
|
1865
|
+
sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
|
|
1866
|
+
rawHeaders: req.headers
|
|
1867
|
+
});
|
|
1868
|
+
} : void 0;
|
|
1403
1869
|
let cachedRoutes = [];
|
|
1404
1870
|
let lastFetchAt = 0;
|
|
1405
1871
|
let refreshing = null;
|
|
@@ -1460,6 +1926,7 @@ function createMiddleware(options) {
|
|
|
1460
1926
|
cachedRoutes.length === 0 ? "no-policy" : "no-match"
|
|
1461
1927
|
);
|
|
1462
1928
|
}
|
|
1929
|
+
beaconPassThrough?.(req);
|
|
1463
1930
|
return next();
|
|
1464
1931
|
}
|
|
1465
1932
|
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
@@ -1470,6 +1937,7 @@ function createMiddleware(options) {
|
|
|
1470
1937
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
1471
1938
|
res.setHeader("X-Astra-Gateway-Reason", "route-none");
|
|
1472
1939
|
}
|
|
1940
|
+
beaconPassThrough?.(req);
|
|
1473
1941
|
return next();
|
|
1474
1942
|
}
|
|
1475
1943
|
const pdlssPair = resolveRequestPdlss(
|
|
@@ -1562,16 +2030,10 @@ function createMiddleware(options) {
|
|
|
1562
2030
|
forwardedFor: forwardedForStr,
|
|
1563
2031
|
agentCardUrl,
|
|
1564
2032
|
// Workstream D — capture EVERY inbound header (SDK-only merchants
|
|
1565
|
-
// have no edge to do it upstream)
|
|
1566
|
-
// stripped; out of the verify
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return {
|
|
1570
|
-
headers,
|
|
1571
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
1572
|
-
...platformHeaders && { platformHeaders }
|
|
1573
|
-
};
|
|
1574
|
-
})()
|
|
2033
|
+
// have no edge to do it upstream) plus the CDN-derived connection
|
|
2034
|
+
// block. Local string work, secrets stripped; out of the verify
|
|
2035
|
+
// cache key so it doesn't defeat caching.
|
|
2036
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
1575
2037
|
}
|
|
1576
2038
|
};
|
|
1577
2039
|
let result = await verify(config, verificationRequest);
|
|
@@ -1765,7 +2227,7 @@ function resolveNextPdlss(request, routeConfig) {
|
|
|
1765
2227
|
routeAction: routeConfig?.action
|
|
1766
2228
|
});
|
|
1767
2229
|
}
|
|
1768
|
-
function
|
|
2230
|
+
function generateInterstitialHtml(result, options) {
|
|
1769
2231
|
const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
|
|
1770
2232
|
const message = escapeHtml(
|
|
1771
2233
|
options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
|
|
@@ -1921,13 +2383,31 @@ function generateCommerceShieldHtml(result, options) {
|
|
|
1921
2383
|
}
|
|
1922
2384
|
var DEFAULT_ROUTES_REFRESH_MS2 = 5 * 60 * 1e3;
|
|
1923
2385
|
function createMiddleware2(options) {
|
|
2386
|
+
if ("routes" in options) {
|
|
2387
|
+
console.warn(
|
|
2388
|
+
"[VerificationGateway/Next.js] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
2389
|
+
);
|
|
2390
|
+
}
|
|
1924
2391
|
const {
|
|
1925
2392
|
skipPaths = [],
|
|
1926
|
-
|
|
2393
|
+
showInterstitial,
|
|
2394
|
+
showCommerceShield,
|
|
1927
2395
|
enableRuntimeChallenge = true,
|
|
1928
2396
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS2,
|
|
2397
|
+
anonymousBeacon = true,
|
|
1929
2398
|
...config
|
|
1930
2399
|
} = options;
|
|
2400
|
+
const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
|
|
2401
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "website" }) : void 0;
|
|
2402
|
+
const beaconPassThrough = emitAnonymousBeacon ? (request, pathname) => {
|
|
2403
|
+
emitAnonymousBeacon({
|
|
2404
|
+
requestPath: pathname,
|
|
2405
|
+
requestMethod: request.method,
|
|
2406
|
+
counterpartyUrl: config.counterpartyUrl || request.nextUrl.origin,
|
|
2407
|
+
sourceIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
|
|
2408
|
+
rawHeaders: Object.fromEntries(request.headers.entries())
|
|
2409
|
+
});
|
|
2410
|
+
} : void 0;
|
|
1931
2411
|
let cachedRoutes = [];
|
|
1932
2412
|
let lastFetchAt = 0;
|
|
1933
2413
|
let refreshing = null;
|
|
@@ -1969,9 +2449,11 @@ function createMiddleware2(options) {
|
|
|
1969
2449
|
}
|
|
1970
2450
|
const routeConfig = findRouteConfig2(cachedRoutes, pathname, request.method);
|
|
1971
2451
|
if (!routeConfig) {
|
|
2452
|
+
beaconPassThrough?.(request, pathname);
|
|
1972
2453
|
return NextResponse.next();
|
|
1973
2454
|
}
|
|
1974
2455
|
if (routeConfig.minAccessLevel === "none") {
|
|
2456
|
+
beaconPassThrough?.(request, pathname);
|
|
1975
2457
|
return NextResponse.next();
|
|
1976
2458
|
}
|
|
1977
2459
|
const credentials = extractCredentialsFromNextRequest(request);
|
|
@@ -2023,8 +2505,8 @@ function createMiddleware2(options) {
|
|
|
2023
2505
|
{ status: 403 }
|
|
2024
2506
|
);
|
|
2025
2507
|
}
|
|
2026
|
-
if (
|
|
2027
|
-
return new NextResponse(
|
|
2508
|
+
if (interstitialEnabled) {
|
|
2509
|
+
return new NextResponse(generateInterstitialHtml(preCheckResult, options), {
|
|
2028
2510
|
status: 200,
|
|
2029
2511
|
headers: {
|
|
2030
2512
|
"Content-Type": "text/html",
|
|
@@ -2052,17 +2534,9 @@ function createMiddleware2(options) {
|
|
|
2052
2534
|
host: request.headers.get("host") || void 0,
|
|
2053
2535
|
forwardedFor,
|
|
2054
2536
|
agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0,
|
|
2055
|
-
// Workstream D — full sanitised header capture
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
Object.fromEntries(request.headers.entries())
|
|
2059
|
-
);
|
|
2060
|
-
return {
|
|
2061
|
-
headers,
|
|
2062
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
2063
|
-
...platformHeaders && { platformHeaders }
|
|
2064
|
-
};
|
|
2065
|
-
})()
|
|
2537
|
+
// Workstream D — full sanitised header capture + CDN-derived
|
|
2538
|
+
// connection block (Web Headers → plain map).
|
|
2539
|
+
observedMetadata: buildSdkObservedMetadata(Object.fromEntries(request.headers.entries()))
|
|
2066
2540
|
}
|
|
2067
2541
|
};
|
|
2068
2542
|
let result = await verify(config, verificationRequest);
|
|
@@ -2078,6 +2552,21 @@ function createMiddleware2(options) {
|
|
|
2078
2552
|
const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
|
|
2079
2553
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2080
2554
|
if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
|
|
2555
|
+
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
2556
|
+
return NextResponse.json(
|
|
2557
|
+
{
|
|
2558
|
+
success: false,
|
|
2559
|
+
error: {
|
|
2560
|
+
code: "MERCHANT_VERIFICATION_MISCONFIGURED",
|
|
2561
|
+
message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
|
|
2562
|
+
guidance: result.guidance,
|
|
2563
|
+
failures: result.failures,
|
|
2564
|
+
correlationId: result.correlationId
|
|
2565
|
+
}
|
|
2566
|
+
},
|
|
2567
|
+
{ status: 503 }
|
|
2568
|
+
);
|
|
2569
|
+
}
|
|
2081
2570
|
if (pathname.startsWith("/api/")) {
|
|
2082
2571
|
return NextResponse.json(
|
|
2083
2572
|
{
|
|
@@ -2102,8 +2591,8 @@ function createMiddleware2(options) {
|
|
|
2102
2591
|
}
|
|
2103
2592
|
);
|
|
2104
2593
|
}
|
|
2105
|
-
if (
|
|
2106
|
-
return new NextResponse(
|
|
2594
|
+
if (interstitialEnabled) {
|
|
2595
|
+
return new NextResponse(generateInterstitialHtml(result, options), {
|
|
2107
2596
|
status: 200,
|
|
2108
2597
|
headers: {
|
|
2109
2598
|
"Content-Type": "text/html",
|
|
@@ -2191,7 +2680,7 @@ var VerificationGatewayClient = class {
|
|
|
2191
2680
|
/**
|
|
2192
2681
|
* Quick verification — checks credentials and policy in one call.
|
|
2193
2682
|
*
|
|
2194
|
-
*
|
|
2683
|
+
* The return shape mirrors `VerificationResult`'s identity/policy
|
|
2195
2684
|
* split. Map to HTTP status the same way: `!identityVerified` → 401,
|
|
2196
2685
|
* `identityVerified && !policyAllowed` → 403.
|
|
2197
2686
|
*/
|
|
@@ -2621,8 +3110,8 @@ function extractX402TransactionValue(req) {
|
|
|
2621
3110
|
// src/transport/rfc9421.ts
|
|
2622
3111
|
var import_structured_headers = require("structured-headers");
|
|
2623
3112
|
function parseRFC9421(headers) {
|
|
2624
|
-
const sigInput =
|
|
2625
|
-
const sig =
|
|
3113
|
+
const sigInput = readHeader2(headers, "signature-input");
|
|
3114
|
+
const sig = readHeader2(headers, "signature");
|
|
2626
3115
|
if (!sigInput || !sig) return null;
|
|
2627
3116
|
let inputDict;
|
|
2628
3117
|
let sigDict;
|
|
@@ -2666,7 +3155,7 @@ function parseRFC9421(headers) {
|
|
|
2666
3155
|
if (signatures.length === 0) return null;
|
|
2667
3156
|
return { signatures };
|
|
2668
3157
|
}
|
|
2669
|
-
function
|
|
3158
|
+
function readHeader2(headers, name) {
|
|
2670
3159
|
for (const key of Object.keys(headers)) {
|
|
2671
3160
|
if (key.toLowerCase() === name) {
|
|
2672
3161
|
const raw = headers[key];
|
|
@@ -2984,11 +3473,11 @@ function extractACPContext(request) {
|
|
|
2984
3473
|
const purpose = mapACPRequestToPurpose(method, path);
|
|
2985
3474
|
const sessionId = extractSessionId2(path);
|
|
2986
3475
|
const headers = request.headers ?? {};
|
|
2987
|
-
const signatureHeader =
|
|
2988
|
-
const timestampHeader =
|
|
2989
|
-
const idempotencyKey =
|
|
2990
|
-
const apiVersion =
|
|
2991
|
-
const bearer = extractBearer(
|
|
3476
|
+
const signatureHeader = readHeader3(headers, "signature");
|
|
3477
|
+
const timestampHeader = readHeader3(headers, "timestamp");
|
|
3478
|
+
const idempotencyKey = readHeader3(headers, "idempotency-key");
|
|
3479
|
+
const apiVersion = readHeader3(headers, "api-version");
|
|
3480
|
+
const bearer = extractBearer(readHeader3(headers, "authorization"));
|
|
2992
3481
|
const body = request.body ?? {};
|
|
2993
3482
|
const merchantId = coerceString3(body.merchant_id ?? body.merchantId);
|
|
2994
3483
|
const totals = Array.isArray(body.totals) ? body.totals : void 0;
|
|
@@ -3056,7 +3545,7 @@ function extractFulfillmentOption(body) {
|
|
|
3056
3545
|
}
|
|
3057
3546
|
return void 0;
|
|
3058
3547
|
}
|
|
3059
|
-
function
|
|
3548
|
+
function readHeader3(headers, name) {
|
|
3060
3549
|
for (const key of Object.keys(headers)) {
|
|
3061
3550
|
if (key.toLowerCase() === name) {
|
|
3062
3551
|
const raw = headers[key];
|
|
@@ -3850,9 +4339,9 @@ function toBuf(bytes) {
|
|
|
3850
4339
|
new Uint8Array(out).set(bytes);
|
|
3851
4340
|
return out;
|
|
3852
4341
|
}
|
|
3853
|
-
function checkTimestamp(
|
|
3854
|
-
if (!
|
|
3855
|
-
const ts = parseTimestamp(
|
|
4342
|
+
function checkTimestamp(headerValue3, toleranceSec, nowFn) {
|
|
4343
|
+
if (!headerValue3) return { ok: false, error: "missing Timestamp header" };
|
|
4344
|
+
const ts = parseTimestamp(headerValue3);
|
|
3856
4345
|
if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
|
|
3857
4346
|
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
3858
4347
|
if (Math.abs(now - ts) > toleranceSec) {
|
|
@@ -3896,7 +4385,7 @@ async function getSubtle() {
|
|
|
3896
4385
|
// src/transport/mpp.ts
|
|
3897
4386
|
var import_mppx = require("mppx");
|
|
3898
4387
|
function extractMPPFromRequest(request) {
|
|
3899
|
-
const auth =
|
|
4388
|
+
const auth = readHeader4(request.headers, "authorization");
|
|
3900
4389
|
if (!auth || !/^\s*Payment\s+/i.test(auth)) return null;
|
|
3901
4390
|
try {
|
|
3902
4391
|
const credential = import_mppx.Credential.deserialize(auth);
|
|
@@ -3924,7 +4413,7 @@ function extractMPPFromResponse(response) {
|
|
|
3924
4413
|
offeredMethods: methods
|
|
3925
4414
|
};
|
|
3926
4415
|
}
|
|
3927
|
-
const receiptHeader =
|
|
4416
|
+
const receiptHeader = readHeader4(response.headers, "payment-receipt");
|
|
3928
4417
|
if (receiptHeader) {
|
|
3929
4418
|
try {
|
|
3930
4419
|
const parsed = import_mppx.Receipt.deserialize(receiptHeader);
|
|
@@ -3944,7 +4433,7 @@ function extractMPPFromResponse(response) {
|
|
|
3944
4433
|
return { kind: "error", error: { type: "invalid-receipt-encoding" } };
|
|
3945
4434
|
}
|
|
3946
4435
|
}
|
|
3947
|
-
const contentType =
|
|
4436
|
+
const contentType = readHeader4(response.headers, "content-type");
|
|
3948
4437
|
if (contentType && /application\/problem\+json/i.test(contentType)) {
|
|
3949
4438
|
const body = typeof response.body === "object" && response.body !== null ? response.body : {};
|
|
3950
4439
|
return {
|
|
@@ -3967,7 +4456,7 @@ function extractMPPContext(message) {
|
|
|
3967
4456
|
return extractMPPFromRequest(message);
|
|
3968
4457
|
}
|
|
3969
4458
|
function collectChallenges(response) {
|
|
3970
|
-
const wwwAuth =
|
|
4459
|
+
const wwwAuth = readHeader4(response.headers, "www-authenticate");
|
|
3971
4460
|
if (!wwwAuth) return [];
|
|
3972
4461
|
const headers = new Headers();
|
|
3973
4462
|
headers.set("www-authenticate", wwwAuth);
|
|
@@ -3995,7 +4484,7 @@ function summarizeChallenge(ch) {
|
|
|
3995
4484
|
opaque: raw.opaque
|
|
3996
4485
|
};
|
|
3997
4486
|
}
|
|
3998
|
-
function
|
|
4487
|
+
function readHeader4(headers, name) {
|
|
3999
4488
|
for (const key of Object.keys(headers)) {
|
|
4000
4489
|
if (key.toLowerCase() === name) {
|
|
4001
4490
|
const raw = headers[key];
|
|
@@ -4084,14 +4573,14 @@ function verifyMPP(input) {
|
|
|
4084
4573
|
var import_schemas = require("@x402/core/schemas");
|
|
4085
4574
|
var import_utils = require("@x402/core/utils");
|
|
4086
4575
|
function extractX402FromRequest(request) {
|
|
4087
|
-
const
|
|
4576
|
+
const headerValue3 = readHeader5(request.headers, "x-payment");
|
|
4088
4577
|
if (request.body && typeof request.body === "object") {
|
|
4089
4578
|
const parsed = tryParsePayload(request.body);
|
|
4090
4579
|
if (parsed) return buildPayloadContext(parsed, "body");
|
|
4091
4580
|
}
|
|
4092
|
-
if (
|
|
4581
|
+
if (headerValue3) {
|
|
4093
4582
|
try {
|
|
4094
|
-
const decoded = (0, import_utils.safeBase64Decode)(
|
|
4583
|
+
const decoded = (0, import_utils.safeBase64Decode)(headerValue3);
|
|
4095
4584
|
if (decoded) {
|
|
4096
4585
|
const json = JSON.parse(decoded);
|
|
4097
4586
|
const parsed = tryParsePayload(json);
|
|
@@ -4114,10 +4603,10 @@ function extractX402FromResponse(response) {
|
|
|
4114
4603
|
const parsed = tryParseRequired(response.body);
|
|
4115
4604
|
if (parsed) return buildRequiredContext(parsed, "body");
|
|
4116
4605
|
}
|
|
4117
|
-
const
|
|
4118
|
-
if (
|
|
4606
|
+
const headerValue3 = readHeader5(response.headers, "x-payment-required");
|
|
4607
|
+
if (headerValue3) {
|
|
4119
4608
|
try {
|
|
4120
|
-
const decoded = (0, import_utils.safeBase64Decode)(
|
|
4609
|
+
const decoded = (0, import_utils.safeBase64Decode)(headerValue3);
|
|
4121
4610
|
if (decoded) {
|
|
4122
4611
|
const json = JSON.parse(decoded);
|
|
4123
4612
|
const parsed = tryParseRequired(json);
|
|
@@ -4214,7 +4703,7 @@ function coerceVersion(v) {
|
|
|
4214
4703
|
if (v === 1 || v === 2) return v;
|
|
4215
4704
|
return null;
|
|
4216
4705
|
}
|
|
4217
|
-
function
|
|
4706
|
+
function readHeader5(headers, name) {
|
|
4218
4707
|
if (!headers) return void 0;
|
|
4219
4708
|
for (const key of Object.keys(headers)) {
|
|
4220
4709
|
if (key.toLowerCase() === name) {
|
|
@@ -4962,8 +5451,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
4962
5451
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
4963
5452
|
action = parsed.actionFromBody;
|
|
4964
5453
|
actionSource = parsed.actionSourceFromBody;
|
|
5454
|
+
} else if (parsed.method === "tools/call") {
|
|
5455
|
+
action = void 0;
|
|
5456
|
+
actionSource = void 0;
|
|
4965
5457
|
} else {
|
|
4966
|
-
action = parsed.toolName ?
|
|
5458
|
+
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4967
5459
|
actionSource = "transport_layer";
|
|
4968
5460
|
}
|
|
4969
5461
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -5001,6 +5493,24 @@ function dedupeFailures2(result) {
|
|
|
5001
5493
|
}
|
|
5002
5494
|
function defaultMcpDenied(result, req, res) {
|
|
5003
5495
|
const id = req.body?.id ?? null;
|
|
5496
|
+
if (result.misconfigured) {
|
|
5497
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5498
|
+
res.status(503).json({
|
|
5499
|
+
jsonrpc: "2.0",
|
|
5500
|
+
id,
|
|
5501
|
+
error: {
|
|
5502
|
+
// -32002 → merchant verification misconfigured (see -32000/-32001 below)
|
|
5503
|
+
code: -32002,
|
|
5504
|
+
message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
|
|
5505
|
+
data: {
|
|
5506
|
+
guidance: result.guidance,
|
|
5507
|
+
failures: result.failures,
|
|
5508
|
+
correlationId: result.correlationId
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
});
|
|
5512
|
+
return;
|
|
5513
|
+
}
|
|
5004
5514
|
const status = !result.identityVerified ? 401 : 403;
|
|
5005
5515
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5006
5516
|
res.status(status).json({
|
|
@@ -5010,6 +5520,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
5010
5520
|
// JSON-RPC error codes:
|
|
5011
5521
|
// -32000 → unauthorized (no identity resolved)
|
|
5012
5522
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
5523
|
+
// -32002 → merchant verification misconfigured (handled above)
|
|
5013
5524
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
5014
5525
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
5015
5526
|
data: {
|
|
@@ -5059,6 +5570,15 @@ function createMcpMiddleware(options) {
|
|
|
5059
5570
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
5060
5571
|
});
|
|
5061
5572
|
}
|
|
5573
|
+
if (toolGates) {
|
|
5574
|
+
for (const [toolName, gate] of Object.entries(toolGates)) {
|
|
5575
|
+
if (typeof gate === "string") {
|
|
5576
|
+
console.warn(
|
|
5577
|
+
`[VerificationGateway/MCP] toolGates['${toolName}'] uses the bare-string shorthand ('${gate}'). Gated tools need a PDLSS purpose (else PDLSS_PURPOSE_REQUIRED 400) and evaluate purpose-only without a declared action. Use the object form instead: { minAccessLevel: '${gate}', purpose: '...', action: '...' }.`
|
|
5578
|
+
);
|
|
5579
|
+
}
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5062
5582
|
return async (req, res, next) => {
|
|
5063
5583
|
try {
|
|
5064
5584
|
if (skip) return next();
|
|
@@ -5178,7 +5698,13 @@ function createMcpMiddleware(options) {
|
|
|
5178
5698
|
callerMetadata: {
|
|
5179
5699
|
sourceIp: req.ip,
|
|
5180
5700
|
userAgent: req.headers["user-agent"],
|
|
5181
|
-
host: req.headers.host
|
|
5701
|
+
host: req.headers.host,
|
|
5702
|
+
// Capture EVERY inbound header (parity with the express adapter —
|
|
5703
|
+
// MCP-only merchants have no edge to do it upstream) plus the
|
|
5704
|
+
// CDN-derived connection block. Local string work, secrets
|
|
5705
|
+
// stripped; out of the verify cache key so it doesn't defeat
|
|
5706
|
+
// caching.
|
|
5707
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
5182
5708
|
}
|
|
5183
5709
|
};
|
|
5184
5710
|
let result = await verify(config, verificationRequest);
|
|
@@ -5963,212 +6489,6 @@ async function recordDecision2(config, params) {
|
|
|
5963
6489
|
blockchainTxHash: result.blockchainTxHash
|
|
5964
6490
|
};
|
|
5965
6491
|
}
|
|
5966
|
-
|
|
5967
|
-
// src/edge-config.ts
|
|
5968
|
-
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
5969
|
-
version: 1,
|
|
5970
|
-
mode: "observe",
|
|
5971
|
-
depth: "classify",
|
|
5972
|
-
pathRules: [],
|
|
5973
|
-
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
5974
|
-
failurePosture: "open"
|
|
5975
|
-
});
|
|
5976
|
-
var EDGE_MODES = ["observe", "enforce"];
|
|
5977
|
-
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
5978
|
-
function isEdgeConfig(value) {
|
|
5979
|
-
if (typeof value !== "object" || value === null) return false;
|
|
5980
|
-
const v = value;
|
|
5981
|
-
if (v.version !== 1) return false;
|
|
5982
|
-
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
5983
|
-
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
5984
|
-
if (!Array.isArray(v.pathRules)) return false;
|
|
5985
|
-
for (const rule of v.pathRules) {
|
|
5986
|
-
if (typeof rule !== "object" || rule === null) return false;
|
|
5987
|
-
const r = rule;
|
|
5988
|
-
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
5989
|
-
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
5990
|
-
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
5991
|
-
}
|
|
5992
|
-
const sampling = v.sampling;
|
|
5993
|
-
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
5994
|
-
return false;
|
|
5995
|
-
}
|
|
5996
|
-
return v.failurePosture === "open";
|
|
5997
|
-
}
|
|
5998
|
-
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
5999
|
-
if (!counterpartyId) return null;
|
|
6000
|
-
const headers = { Accept: "application/json" };
|
|
6001
|
-
if (config.apiKey) {
|
|
6002
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
6003
|
-
headers["X-API-Key"] = config.apiKey;
|
|
6004
|
-
}
|
|
6005
|
-
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
6006
|
-
try {
|
|
6007
|
-
const response = await fetch(
|
|
6008
|
-
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
6009
|
-
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
6010
|
-
);
|
|
6011
|
-
if (response.status === 304) return { notModified: true };
|
|
6012
|
-
if (!response.ok) return null;
|
|
6013
|
-
const body = await response.json();
|
|
6014
|
-
const edgeConfig = body.data?.edgeConfig;
|
|
6015
|
-
if (!isEdgeConfig(edgeConfig)) return null;
|
|
6016
|
-
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
6017
|
-
} catch {
|
|
6018
|
-
return null;
|
|
6019
|
-
}
|
|
6020
|
-
}
|
|
6021
|
-
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
6022
|
-
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
6023
|
-
var cache2 = /* @__PURE__ */ new Map();
|
|
6024
|
-
var inflight2 = /* @__PURE__ */ new Map();
|
|
6025
|
-
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
6026
|
-
return `${apiBaseUrl}|${counterpartyId}`;
|
|
6027
|
-
}
|
|
6028
|
-
function degradeToObserve(config) {
|
|
6029
|
-
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
6030
|
-
return config;
|
|
6031
|
-
}
|
|
6032
|
-
return {
|
|
6033
|
-
...config,
|
|
6034
|
-
mode: "observe",
|
|
6035
|
-
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
6036
|
-
};
|
|
6037
|
-
}
|
|
6038
|
-
async function revalidate(config, counterpartyId) {
|
|
6039
|
-
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
6040
|
-
const existing = inflight2.get(key);
|
|
6041
|
-
if (existing) return existing;
|
|
6042
|
-
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
6043
|
-
if (result === null) return;
|
|
6044
|
-
const now = Date.now();
|
|
6045
|
-
if ("notModified" in result) {
|
|
6046
|
-
const entry = cache2.get(key);
|
|
6047
|
-
if (entry) entry.lastSuccessAt = now;
|
|
6048
|
-
return;
|
|
6049
|
-
}
|
|
6050
|
-
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
6051
|
-
}).finally(() => {
|
|
6052
|
-
inflight2.delete(key);
|
|
6053
|
-
});
|
|
6054
|
-
inflight2.set(key, promise);
|
|
6055
|
-
return promise;
|
|
6056
|
-
}
|
|
6057
|
-
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
6058
|
-
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
6059
|
-
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
6060
|
-
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
6061
|
-
const entry = cache2.get(key);
|
|
6062
|
-
if (entry) {
|
|
6063
|
-
const age = Date.now() - entry.lastSuccessAt;
|
|
6064
|
-
if (age > ttlMs) {
|
|
6065
|
-
revalidate(config, counterpartyId).catch(() => {
|
|
6066
|
-
});
|
|
6067
|
-
}
|
|
6068
|
-
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
6069
|
-
}
|
|
6070
|
-
await revalidate(config, counterpartyId).catch(() => {
|
|
6071
|
-
});
|
|
6072
|
-
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
6073
|
-
}
|
|
6074
|
-
function matchEdgePathRule(rules, path) {
|
|
6075
|
-
for (const rule of rules) {
|
|
6076
|
-
if (globToRegExp(rule.pattern).test(path)) return rule;
|
|
6077
|
-
}
|
|
6078
|
-
return void 0;
|
|
6079
|
-
}
|
|
6080
|
-
function globToRegExp(pattern) {
|
|
6081
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
6082
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
6083
|
-
}
|
|
6084
|
-
function _resetEdgeConfigCache() {
|
|
6085
|
-
cache2.clear();
|
|
6086
|
-
inflight2.clear();
|
|
6087
|
-
}
|
|
6088
|
-
|
|
6089
|
-
// src/platform-signatures.ts
|
|
6090
|
-
var PLATFORM_AGENT_SIGNATURES = [
|
|
6091
|
-
{
|
|
6092
|
-
vendor: "claude",
|
|
6093
|
-
displayName: "Anthropic Claude",
|
|
6094
|
-
uaPatterns: [/claude/i, /anthropic/i],
|
|
6095
|
-
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
6096
|
-
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
6097
|
-
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
6098
|
-
},
|
|
6099
|
-
{
|
|
6100
|
-
vendor: "chatgpt",
|
|
6101
|
-
displayName: "OpenAI ChatGPT",
|
|
6102
|
-
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
6103
|
-
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
6104
|
-
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
6105
|
-
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
6106
|
-
},
|
|
6107
|
-
{
|
|
6108
|
-
vendor: "gemini",
|
|
6109
|
-
displayName: "Google Gemini",
|
|
6110
|
-
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
6111
|
-
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
6112
|
-
apiKeyFormatPatterns: [/^aiza/i],
|
|
6113
|
-
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
6114
|
-
},
|
|
6115
|
-
{
|
|
6116
|
-
vendor: "cursor",
|
|
6117
|
-
displayName: "Cursor",
|
|
6118
|
-
uaPatterns: [/cursor/i],
|
|
6119
|
-
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
6120
|
-
},
|
|
6121
|
-
{
|
|
6122
|
-
vendor: "goose",
|
|
6123
|
-
displayName: "Block Goose",
|
|
6124
|
-
uaPatterns: [/goose/i, /block-goose/i],
|
|
6125
|
-
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
6126
|
-
},
|
|
6127
|
-
{
|
|
6128
|
-
vendor: "perplexity",
|
|
6129
|
-
displayName: "Perplexity",
|
|
6130
|
-
uaPatterns: [/perplexity/i],
|
|
6131
|
-
agentCardPatterns: [/perplexity\.ai/i],
|
|
6132
|
-
apiKeyFormatPatterns: [/^pplx-/i]
|
|
6133
|
-
}
|
|
6134
|
-
];
|
|
6135
|
-
function matchPlatformSignature(signatures, input) {
|
|
6136
|
-
const ua = input.userAgent ?? "";
|
|
6137
|
-
const cardUrl = input.agentCardUrl ?? "";
|
|
6138
|
-
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
6139
|
-
const headerLines = [];
|
|
6140
|
-
if (input.platformHeaders) {
|
|
6141
|
-
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
6142
|
-
headerLines.push(name, `${name}: ${value}`);
|
|
6143
|
-
}
|
|
6144
|
-
}
|
|
6145
|
-
for (const sig of signatures) {
|
|
6146
|
-
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
6147
|
-
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
6148
|
-
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
6149
|
-
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
6150
|
-
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
6151
|
-
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
6152
|
-
const cardHost = (() => {
|
|
6153
|
-
try {
|
|
6154
|
-
return cardUrl ? new URL(cardUrl).host : "";
|
|
6155
|
-
} catch {
|
|
6156
|
-
return "";
|
|
6157
|
-
}
|
|
6158
|
-
})();
|
|
6159
|
-
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
6160
|
-
return {
|
|
6161
|
-
vendor: sig.vendor,
|
|
6162
|
-
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
6163
|
-
displayName: sig.displayName
|
|
6164
|
-
};
|
|
6165
|
-
}
|
|
6166
|
-
}
|
|
6167
|
-
return void 0;
|
|
6168
|
-
}
|
|
6169
|
-
function detectPlatformFingerprint(input) {
|
|
6170
|
-
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
6171
|
-
}
|
|
6172
6492
|
// Annotate the CommonJS export names for ESM import in node:
|
|
6173
6493
|
0 && (module.exports = {
|
|
6174
6494
|
ACCESS_LEVEL_DESCRIPTIONS,
|
|
@@ -6177,6 +6497,7 @@ function detectPlatformFingerprint(input) {
|
|
|
6177
6497
|
AstraSync,
|
|
6178
6498
|
AstraSyncError,
|
|
6179
6499
|
AuthenticationError,
|
|
6500
|
+
CAPTURE_SCHEMA_VERSION,
|
|
6180
6501
|
ChallengeHandler,
|
|
6181
6502
|
DEFAULT_EDGE_CONFIG,
|
|
6182
6503
|
DEFAULT_TRUST_THRESHOLDS,
|
|
@@ -6195,9 +6516,11 @@ function detectPlatformFingerprint(input) {
|
|
|
6195
6516
|
authorizeSettlement,
|
|
6196
6517
|
awaitStepUpApproval,
|
|
6197
6518
|
buildGuidance,
|
|
6519
|
+
buildSdkObservedMetadata,
|
|
6198
6520
|
clearCache,
|
|
6199
6521
|
createMcpMiddleware,
|
|
6200
6522
|
degradeToObserve,
|
|
6523
|
+
deriveConnectionFromHeaders,
|
|
6201
6524
|
detectPlatformFingerprint,
|
|
6202
6525
|
determineAccessLevel,
|
|
6203
6526
|
express,
|