@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.mjs
CHANGED
|
@@ -126,7 +126,7 @@ function getCapabilities(accessLevel) {
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
// src/version.ts
|
|
129
|
-
var SDK_VERSION = "
|
|
129
|
+
var SDK_VERSION = "4.1.0";
|
|
130
130
|
|
|
131
131
|
// src/well-known.ts
|
|
132
132
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -204,7 +204,7 @@ var DEFAULT_CONFIG = {
|
|
|
204
204
|
};
|
|
205
205
|
var initCheckPerformed = false;
|
|
206
206
|
var deprecationWarningShown = false;
|
|
207
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
207
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
208
208
|
initCheckPerformed = true;
|
|
209
209
|
try {
|
|
210
210
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -221,6 +221,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
221
221
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
222
222
|
);
|
|
223
223
|
}
|
|
224
|
+
if (apiKey) {
|
|
225
|
+
const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
226
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
227
|
+
});
|
|
228
|
+
if (selfCheck.status === 401) {
|
|
229
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
230
|
+
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}` : "") + `.`;
|
|
231
|
+
if (strictInit) {
|
|
232
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
233
|
+
}
|
|
234
|
+
console.error(message);
|
|
235
|
+
} else if (selfCheck.ok) {
|
|
236
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
237
|
+
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
238
|
+
console.warn(
|
|
239
|
+
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
240
|
+
);
|
|
241
|
+
} else if (debug) {
|
|
242
|
+
console.log("[VerificationGateway] API key self-check passed");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
224
246
|
} catch (err) {
|
|
225
247
|
if (strictInit) {
|
|
226
248
|
throw err;
|
|
@@ -338,8 +360,17 @@ function hasCredentials(credentials) {
|
|
|
338
360
|
function createGuidanceResponse(_config, reason, options = {}) {
|
|
339
361
|
const source = options.source ?? "no_credentials";
|
|
340
362
|
const isApiError = source === "api_error";
|
|
363
|
+
const isMisconfig = source === "merchant_misconfig";
|
|
341
364
|
const urls = options.urls;
|
|
342
|
-
const guidance =
|
|
365
|
+
const guidance = isMisconfig ? {
|
|
366
|
+
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.",
|
|
367
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
368
|
+
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
369
|
+
steps: [
|
|
370
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
|
|
371
|
+
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
372
|
+
]
|
|
373
|
+
} : isApiError ? {
|
|
343
374
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
344
375
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
345
376
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
@@ -377,13 +408,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
377
408
|
// partners (and their custom onDenied handlers) can branch on
|
|
378
409
|
// dimension. Without this, the synthesised stub was indistinguishable
|
|
379
410
|
// from a real policy deny.
|
|
380
|
-
failures:
|
|
411
|
+
failures: isMisconfig ? [
|
|
412
|
+
{
|
|
413
|
+
dimension: "verify_access.merchant_misconfigured",
|
|
414
|
+
message: reason ?? "The integration API key was rejected by AstraSync",
|
|
415
|
+
guidance: guidance.message
|
|
416
|
+
}
|
|
417
|
+
] : isApiError ? [
|
|
381
418
|
{
|
|
382
419
|
dimension: "verify_access.api_error",
|
|
383
420
|
message: reason ?? "Verification temporarily unavailable",
|
|
384
421
|
guidance: guidance.message
|
|
385
422
|
}
|
|
386
423
|
] : void 0,
|
|
424
|
+
// 4.0.0: typed marker so adapters render the misconfig envelope (503 +
|
|
425
|
+
// MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
|
|
426
|
+
...isMisconfig && { misconfigured: true },
|
|
387
427
|
correlationId: options.correlationId,
|
|
388
428
|
attemptId: options.attemptId,
|
|
389
429
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -438,7 +478,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
438
478
|
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
439
479
|
method: "POST",
|
|
440
480
|
headers,
|
|
441
|
-
body: JSON.stringify(body)
|
|
481
|
+
body: JSON.stringify(body),
|
|
482
|
+
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
442
483
|
});
|
|
443
484
|
const data = await response.json();
|
|
444
485
|
if (response.status === 410) {
|
|
@@ -462,6 +503,17 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
462
503
|
};
|
|
463
504
|
}
|
|
464
505
|
if (!response.ok) {
|
|
506
|
+
if (response.status === 401) {
|
|
507
|
+
return {
|
|
508
|
+
success: false,
|
|
509
|
+
error: data.message || data.error || "Integration API key rejected",
|
|
510
|
+
misconfig: true,
|
|
511
|
+
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
512
|
+
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
513
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
514
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
515
|
+
};
|
|
516
|
+
}
|
|
465
517
|
return {
|
|
466
518
|
success: false,
|
|
467
519
|
error: data.message || data.error || `API returned ${response.status}`,
|
|
@@ -483,9 +535,19 @@ async function verify(config, request, options) {
|
|
|
483
535
|
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
484
536
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
485
537
|
if (mergedConfig.strictInit) {
|
|
486
|
-
await performInitCheck(
|
|
538
|
+
await performInitCheck(
|
|
539
|
+
mergedConfig.apiBaseUrl,
|
|
540
|
+
mergedConfig.debug,
|
|
541
|
+
true,
|
|
542
|
+
mergedConfig.apiKey
|
|
543
|
+
);
|
|
487
544
|
} else {
|
|
488
|
-
void performInitCheck(
|
|
545
|
+
void performInitCheck(
|
|
546
|
+
mergedConfig.apiBaseUrl,
|
|
547
|
+
mergedConfig.debug,
|
|
548
|
+
false,
|
|
549
|
+
mergedConfig.apiKey
|
|
550
|
+
);
|
|
489
551
|
}
|
|
490
552
|
}
|
|
491
553
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
@@ -524,9 +586,10 @@ async function verify(config, request, options) {
|
|
|
524
586
|
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
525
587
|
if (!apiResponse.success) {
|
|
526
588
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
527
|
-
source: "api_error",
|
|
589
|
+
source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
|
|
528
590
|
correlationId: apiResponse.correlationId,
|
|
529
591
|
attemptId: apiResponse.attemptId,
|
|
592
|
+
remediation: apiResponse.remediation,
|
|
530
593
|
urls
|
|
531
594
|
});
|
|
532
595
|
}
|
|
@@ -1061,6 +1124,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
|
1061
1124
|
}
|
|
1062
1125
|
|
|
1063
1126
|
// src/metadata-capture.ts
|
|
1127
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
1064
1128
|
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
1065
1129
|
"cookie",
|
|
1066
1130
|
"set-cookie",
|
|
@@ -1205,6 +1269,373 @@ function extractPlatformHeaders(sanitized) {
|
|
|
1205
1269
|
}
|
|
1206
1270
|
return out;
|
|
1207
1271
|
}
|
|
1272
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
1273
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
1274
|
+
[
|
|
1275
|
+
"country",
|
|
1276
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
1277
|
+
],
|
|
1278
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
1279
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
1280
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
1281
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
1282
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
1283
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
1284
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
1285
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
1286
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
1287
|
+
["ja4", ["cf-ja4"]]
|
|
1288
|
+
];
|
|
1289
|
+
function deriveConnectionFromHeaders(headers) {
|
|
1290
|
+
if (!headers) return void 0;
|
|
1291
|
+
const lower = {};
|
|
1292
|
+
for (const name of Object.keys(headers)) {
|
|
1293
|
+
const value = headers[name];
|
|
1294
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
1295
|
+
}
|
|
1296
|
+
const connection = {};
|
|
1297
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
1298
|
+
for (const source of sources) {
|
|
1299
|
+
const value = lower[source]?.trim();
|
|
1300
|
+
if (value) {
|
|
1301
|
+
connection[key] = value;
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (!connection.ip) {
|
|
1307
|
+
const xff = lower["x-forwarded-for"];
|
|
1308
|
+
const client = xff?.split(",")[0]?.trim();
|
|
1309
|
+
if (client) connection.ip = client;
|
|
1310
|
+
}
|
|
1311
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
1312
|
+
}
|
|
1313
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
1314
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1315
|
+
const flat = {};
|
|
1316
|
+
if (rawHeaders) {
|
|
1317
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
1318
|
+
const value = rawHeaders[name];
|
|
1319
|
+
if (value === void 0) continue;
|
|
1320
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
1324
|
+
return {
|
|
1325
|
+
headers,
|
|
1326
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
1327
|
+
...platformHeaders && { platformHeaders },
|
|
1328
|
+
...connection && { connection },
|
|
1329
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/edge-config.ts
|
|
1334
|
+
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
1335
|
+
version: 1,
|
|
1336
|
+
mode: "observe",
|
|
1337
|
+
depth: "classify",
|
|
1338
|
+
pathRules: [],
|
|
1339
|
+
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
1340
|
+
failurePosture: "open"
|
|
1341
|
+
});
|
|
1342
|
+
var EDGE_MODES = ["observe", "enforce"];
|
|
1343
|
+
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
1344
|
+
function isEdgeConfig(value) {
|
|
1345
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1346
|
+
const v = value;
|
|
1347
|
+
if (v.version !== 1) return false;
|
|
1348
|
+
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
1349
|
+
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
1350
|
+
if (!Array.isArray(v.pathRules)) return false;
|
|
1351
|
+
for (const rule of v.pathRules) {
|
|
1352
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
1353
|
+
const r = rule;
|
|
1354
|
+
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
1355
|
+
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
1356
|
+
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
1357
|
+
}
|
|
1358
|
+
const sampling = v.sampling;
|
|
1359
|
+
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
return v.failurePosture === "open";
|
|
1363
|
+
}
|
|
1364
|
+
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
1365
|
+
if (!counterpartyId) return null;
|
|
1366
|
+
const headers = { Accept: "application/json" };
|
|
1367
|
+
if (config.apiKey) {
|
|
1368
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1369
|
+
headers["X-API-Key"] = config.apiKey;
|
|
1370
|
+
}
|
|
1371
|
+
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
1372
|
+
try {
|
|
1373
|
+
const response = await fetch(
|
|
1374
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
1375
|
+
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
1376
|
+
);
|
|
1377
|
+
if (response.status === 304) return { notModified: true };
|
|
1378
|
+
if (!response.ok) return null;
|
|
1379
|
+
const body = await response.json();
|
|
1380
|
+
const edgeConfig = body.data?.edgeConfig;
|
|
1381
|
+
if (!isEdgeConfig(edgeConfig)) return null;
|
|
1382
|
+
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
1383
|
+
} catch {
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
1388
|
+
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1389
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1390
|
+
var inflight2 = /* @__PURE__ */ new Map();
|
|
1391
|
+
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
1392
|
+
return `${apiBaseUrl}|${counterpartyId}`;
|
|
1393
|
+
}
|
|
1394
|
+
function degradeToObserve(config) {
|
|
1395
|
+
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
1396
|
+
return config;
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
...config,
|
|
1400
|
+
mode: "observe",
|
|
1401
|
+
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
async function revalidate(config, counterpartyId) {
|
|
1405
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1406
|
+
const existing = inflight2.get(key);
|
|
1407
|
+
if (existing) return existing;
|
|
1408
|
+
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
1409
|
+
if (result === null) return;
|
|
1410
|
+
const now = Date.now();
|
|
1411
|
+
if ("notModified" in result) {
|
|
1412
|
+
const entry = cache2.get(key);
|
|
1413
|
+
if (entry) entry.lastSuccessAt = now;
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
1417
|
+
}).finally(() => {
|
|
1418
|
+
inflight2.delete(key);
|
|
1419
|
+
});
|
|
1420
|
+
inflight2.set(key, promise);
|
|
1421
|
+
return promise;
|
|
1422
|
+
}
|
|
1423
|
+
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
1424
|
+
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
1425
|
+
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
1426
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1427
|
+
const entry = cache2.get(key);
|
|
1428
|
+
if (entry) {
|
|
1429
|
+
const age = Date.now() - entry.lastSuccessAt;
|
|
1430
|
+
if (age > ttlMs) {
|
|
1431
|
+
revalidate(config, counterpartyId).catch(() => {
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
1435
|
+
}
|
|
1436
|
+
await revalidate(config, counterpartyId).catch(() => {
|
|
1437
|
+
});
|
|
1438
|
+
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
1439
|
+
}
|
|
1440
|
+
function matchEdgePathRule(rules, path) {
|
|
1441
|
+
for (const rule of rules) {
|
|
1442
|
+
if (globToRegExp(rule.pattern).test(path)) return rule;
|
|
1443
|
+
}
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
function globToRegExp(pattern) {
|
|
1447
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
1448
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
1449
|
+
}
|
|
1450
|
+
function _resetEdgeConfigCache() {
|
|
1451
|
+
cache2.clear();
|
|
1452
|
+
inflight2.clear();
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// src/platform-signatures.ts
|
|
1456
|
+
var PLATFORM_AGENT_SIGNATURES = [
|
|
1457
|
+
{
|
|
1458
|
+
vendor: "claude",
|
|
1459
|
+
displayName: "Anthropic Claude",
|
|
1460
|
+
uaPatterns: [/claude/i, /anthropic/i],
|
|
1461
|
+
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
1462
|
+
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
1463
|
+
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
vendor: "chatgpt",
|
|
1467
|
+
displayName: "OpenAI ChatGPT",
|
|
1468
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
1469
|
+
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1470
|
+
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1471
|
+
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
1472
|
+
},
|
|
1473
|
+
{
|
|
1474
|
+
vendor: "gemini",
|
|
1475
|
+
displayName: "Google Gemini",
|
|
1476
|
+
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
1477
|
+
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
1478
|
+
apiKeyFormatPatterns: [/^aiza/i],
|
|
1479
|
+
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
vendor: "cursor",
|
|
1483
|
+
displayName: "Cursor",
|
|
1484
|
+
uaPatterns: [/cursor/i],
|
|
1485
|
+
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
vendor: "goose",
|
|
1489
|
+
displayName: "Block Goose",
|
|
1490
|
+
uaPatterns: [/goose/i, /block-goose/i],
|
|
1491
|
+
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
vendor: "perplexity",
|
|
1495
|
+
displayName: "Perplexity",
|
|
1496
|
+
uaPatterns: [/perplexity/i],
|
|
1497
|
+
agentCardPatterns: [/perplexity\.ai/i],
|
|
1498
|
+
apiKeyFormatPatterns: [/^pplx-/i]
|
|
1499
|
+
}
|
|
1500
|
+
];
|
|
1501
|
+
function matchPlatformSignature(signatures, input) {
|
|
1502
|
+
const ua = input.userAgent ?? "";
|
|
1503
|
+
const cardUrl = input.agentCardUrl ?? "";
|
|
1504
|
+
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
1505
|
+
const headerLines = [];
|
|
1506
|
+
if (input.platformHeaders) {
|
|
1507
|
+
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
1508
|
+
headerLines.push(name, `${name}: ${value}`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
for (const sig of signatures) {
|
|
1512
|
+
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
1513
|
+
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
1514
|
+
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
1515
|
+
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
1516
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
1517
|
+
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
1518
|
+
const cardHost = (() => {
|
|
1519
|
+
try {
|
|
1520
|
+
return cardUrl ? new URL(cardUrl).host : "";
|
|
1521
|
+
} catch {
|
|
1522
|
+
return "";
|
|
1523
|
+
}
|
|
1524
|
+
})();
|
|
1525
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
1526
|
+
return {
|
|
1527
|
+
vendor: sig.vendor,
|
|
1528
|
+
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
1529
|
+
displayName: sig.displayName
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return void 0;
|
|
1534
|
+
}
|
|
1535
|
+
function detectPlatformFingerprint(input) {
|
|
1536
|
+
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/edge-core/classify.ts
|
|
1540
|
+
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;
|
|
1541
|
+
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;
|
|
1542
|
+
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
1543
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1544
|
+
|
|
1545
|
+
// src/edge-core/events.ts
|
|
1546
|
+
function sampled(rate, random = Math.random) {
|
|
1547
|
+
if (rate <= 0) return false;
|
|
1548
|
+
if (rate >= 1) return true;
|
|
1549
|
+
return random() < rate;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/adapters/anonymous-beacon.ts
|
|
1553
|
+
function readHeader(raw, name) {
|
|
1554
|
+
const value = raw[name];
|
|
1555
|
+
if (typeof value === "string") return value;
|
|
1556
|
+
if (Array.isArray(value)) return value[0];
|
|
1557
|
+
return void 0;
|
|
1558
|
+
}
|
|
1559
|
+
function classifyPassThroughTraffic(path, rawHeaders) {
|
|
1560
|
+
if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
|
|
1561
|
+
const credentials = extractCredentials(rawHeaders, {});
|
|
1562
|
+
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
1563
|
+
return { kind: "never" };
|
|
1564
|
+
}
|
|
1565
|
+
const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
|
|
1566
|
+
const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1567
|
+
const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
|
|
1568
|
+
if (fingerprint) {
|
|
1569
|
+
return {
|
|
1570
|
+
kind: "platform",
|
|
1571
|
+
protocol: "platform-ua",
|
|
1572
|
+
evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
|
|
1576
|
+
return {
|
|
1577
|
+
kind: "anonymous-bot",
|
|
1578
|
+
protocol: "ua-heuristic",
|
|
1579
|
+
evidence: { userAgent: userAgent || "(none)" }
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
|
|
1583
|
+
return {
|
|
1584
|
+
kind: "anonymous-bot",
|
|
1585
|
+
protocol: "ua-heuristic",
|
|
1586
|
+
evidence: { userAgent, spoofedBrowser: true }
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
return { kind: "never" };
|
|
1590
|
+
}
|
|
1591
|
+
function createAnonymousBeacon(config, options) {
|
|
1592
|
+
const counterpartyId = config.counterpartyId;
|
|
1593
|
+
if (!counterpartyId) return () => {
|
|
1594
|
+
};
|
|
1595
|
+
const random = options?.random ?? Math.random;
|
|
1596
|
+
let edgeConfig;
|
|
1597
|
+
let refreshing = false;
|
|
1598
|
+
const refreshConfig = () => {
|
|
1599
|
+
if (refreshing) return;
|
|
1600
|
+
refreshing = true;
|
|
1601
|
+
getEdgeConfig(config, counterpartyId).then((cfg) => {
|
|
1602
|
+
edgeConfig = cfg;
|
|
1603
|
+
}).catch(() => {
|
|
1604
|
+
}).finally(() => {
|
|
1605
|
+
refreshing = false;
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
refreshConfig();
|
|
1609
|
+
return (input) => {
|
|
1610
|
+
try {
|
|
1611
|
+
refreshConfig();
|
|
1612
|
+
const cfg = edgeConfig;
|
|
1613
|
+
if (!cfg) return;
|
|
1614
|
+
const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
|
|
1615
|
+
if (traffic.kind === "never") return;
|
|
1616
|
+
if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const userAgent = readHeader(input.rawHeaders, "user-agent");
|
|
1620
|
+
const xff = input.rawHeaders["x-forwarded-for"];
|
|
1621
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1622
|
+
const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
|
|
1623
|
+
reportUnregisteredAttempt(config, {
|
|
1624
|
+
...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
|
|
1625
|
+
counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
|
|
1626
|
+
...sourceIp && { sourceIp },
|
|
1627
|
+
...userAgent && { userAgent },
|
|
1628
|
+
requestPath: input.requestPath,
|
|
1629
|
+
requestMethod: input.requestMethod,
|
|
1630
|
+
...traffic.protocol && { protocol: traffic.protocol },
|
|
1631
|
+
...traffic.evidence && { evidence: traffic.evidence },
|
|
1632
|
+
observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
|
|
1633
|
+
}).catch(() => {
|
|
1634
|
+
});
|
|
1635
|
+
} catch {
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1208
1639
|
|
|
1209
1640
|
// src/adapters/express.ts
|
|
1210
1641
|
function defaultExtractCredentials(req) {
|
|
@@ -1216,7 +1647,7 @@ function defaultExtractCredentials(req) {
|
|
|
1216
1647
|
function extractAstraSyncCredentials(req) {
|
|
1217
1648
|
return extractHttpCredentials(req.headers);
|
|
1218
1649
|
}
|
|
1219
|
-
function
|
|
1650
|
+
function headerValue2(value) {
|
|
1220
1651
|
if (typeof value === "string") return value;
|
|
1221
1652
|
if (Array.isArray(value)) return value[0];
|
|
1222
1653
|
return void 0;
|
|
@@ -1224,9 +1655,9 @@ function headerValue(value) {
|
|
|
1224
1655
|
function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
|
|
1225
1656
|
return resolveHttpPdlss({
|
|
1226
1657
|
method: req.method,
|
|
1227
|
-
astraPurpose:
|
|
1228
|
-
astraAction:
|
|
1229
|
-
legacyPurpose:
|
|
1658
|
+
astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
|
|
1659
|
+
astraAction: headerValue2(req.headers["x-astra-action"]),
|
|
1660
|
+
legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
|
|
1230
1661
|
queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
|
|
1231
1662
|
routePurpose: routeConfig?.purpose,
|
|
1232
1663
|
routeAction: routeConfig?.action,
|
|
@@ -1274,6 +1705,20 @@ function dedupeFailures(result) {
|
|
|
1274
1705
|
}
|
|
1275
1706
|
}
|
|
1276
1707
|
function defaultOnDenied(result, _req, res) {
|
|
1708
|
+
if (result.misconfigured) {
|
|
1709
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1710
|
+
res.status(503).json({
|
|
1711
|
+
success: false,
|
|
1712
|
+
error: {
|
|
1713
|
+
code: "MERCHANT_VERIFICATION_MISCONFIGURED",
|
|
1714
|
+
message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
|
|
1715
|
+
guidance: result.guidance,
|
|
1716
|
+
failures: result.failures,
|
|
1717
|
+
correlationId: result.correlationId
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1277
1722
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
1278
1723
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1279
1724
|
res.status(statusCode).json({
|
|
@@ -1295,6 +1740,11 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1295
1740
|
}
|
|
1296
1741
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1297
1742
|
function createMiddleware(options) {
|
|
1743
|
+
if ("routes" in options) {
|
|
1744
|
+
console.warn(
|
|
1745
|
+
"[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1298
1748
|
const {
|
|
1299
1749
|
extractCredentials: customExtractCredentials,
|
|
1300
1750
|
extractPurpose: customExtractPurpose,
|
|
@@ -1306,12 +1756,25 @@ function createMiddleware(options) {
|
|
|
1306
1756
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
1307
1757
|
failOnError = "open",
|
|
1308
1758
|
caseInsensitiveRouteMatch = false,
|
|
1759
|
+
anonymousBeacon = true,
|
|
1309
1760
|
...config
|
|
1310
1761
|
} = options;
|
|
1311
1762
|
if (config.apiBaseUrl) {
|
|
1312
1763
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1313
1764
|
});
|
|
1314
1765
|
}
|
|
1766
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
|
|
1767
|
+
const beaconPassThrough = emitAnonymousBeacon ? (req) => {
|
|
1768
|
+
const xff = req.headers["x-forwarded-for"];
|
|
1769
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1770
|
+
emitAnonymousBeacon({
|
|
1771
|
+
requestPath: req.path,
|
|
1772
|
+
requestMethod: req.method,
|
|
1773
|
+
counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
|
|
1774
|
+
sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
|
|
1775
|
+
rawHeaders: req.headers
|
|
1776
|
+
});
|
|
1777
|
+
} : void 0;
|
|
1315
1778
|
let cachedRoutes = [];
|
|
1316
1779
|
let lastFetchAt = 0;
|
|
1317
1780
|
let refreshing = null;
|
|
@@ -1372,6 +1835,7 @@ function createMiddleware(options) {
|
|
|
1372
1835
|
cachedRoutes.length === 0 ? "no-policy" : "no-match"
|
|
1373
1836
|
);
|
|
1374
1837
|
}
|
|
1838
|
+
beaconPassThrough?.(req);
|
|
1375
1839
|
return next();
|
|
1376
1840
|
}
|
|
1377
1841
|
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
@@ -1382,6 +1846,7 @@ function createMiddleware(options) {
|
|
|
1382
1846
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
1383
1847
|
res.setHeader("X-Astra-Gateway-Reason", "route-none");
|
|
1384
1848
|
}
|
|
1849
|
+
beaconPassThrough?.(req);
|
|
1385
1850
|
return next();
|
|
1386
1851
|
}
|
|
1387
1852
|
const pdlssPair = resolveRequestPdlss(
|
|
@@ -1474,16 +1939,10 @@ function createMiddleware(options) {
|
|
|
1474
1939
|
forwardedFor: forwardedForStr,
|
|
1475
1940
|
agentCardUrl,
|
|
1476
1941
|
// Workstream D — capture EVERY inbound header (SDK-only merchants
|
|
1477
|
-
// have no edge to do it upstream)
|
|
1478
|
-
// stripped; out of the verify
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
return {
|
|
1482
|
-
headers,
|
|
1483
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
1484
|
-
...platformHeaders && { platformHeaders }
|
|
1485
|
-
};
|
|
1486
|
-
})()
|
|
1942
|
+
// have no edge to do it upstream) plus the CDN-derived connection
|
|
1943
|
+
// block. Local string work, secrets stripped; out of the verify
|
|
1944
|
+
// cache key so it doesn't defeat caching.
|
|
1945
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
1487
1946
|
}
|
|
1488
1947
|
};
|
|
1489
1948
|
let result = await verify(config, verificationRequest);
|
|
@@ -1677,7 +2136,7 @@ function resolveNextPdlss(request, routeConfig) {
|
|
|
1677
2136
|
routeAction: routeConfig?.action
|
|
1678
2137
|
});
|
|
1679
2138
|
}
|
|
1680
|
-
function
|
|
2139
|
+
function generateInterstitialHtml(result, options) {
|
|
1681
2140
|
const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
|
|
1682
2141
|
const message = escapeHtml(
|
|
1683
2142
|
options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
|
|
@@ -1833,13 +2292,31 @@ function generateCommerceShieldHtml(result, options) {
|
|
|
1833
2292
|
}
|
|
1834
2293
|
var DEFAULT_ROUTES_REFRESH_MS2 = 5 * 60 * 1e3;
|
|
1835
2294
|
function createMiddleware2(options) {
|
|
2295
|
+
if ("routes" in options) {
|
|
2296
|
+
console.warn(
|
|
2297
|
+
"[VerificationGateway/Next.js] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
2298
|
+
);
|
|
2299
|
+
}
|
|
1836
2300
|
const {
|
|
1837
2301
|
skipPaths = [],
|
|
1838
|
-
|
|
2302
|
+
showInterstitial,
|
|
2303
|
+
showCommerceShield,
|
|
1839
2304
|
enableRuntimeChallenge = true,
|
|
1840
2305
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS2,
|
|
2306
|
+
anonymousBeacon = true,
|
|
1841
2307
|
...config
|
|
1842
2308
|
} = options;
|
|
2309
|
+
const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
|
|
2310
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "website" }) : void 0;
|
|
2311
|
+
const beaconPassThrough = emitAnonymousBeacon ? (request, pathname) => {
|
|
2312
|
+
emitAnonymousBeacon({
|
|
2313
|
+
requestPath: pathname,
|
|
2314
|
+
requestMethod: request.method,
|
|
2315
|
+
counterpartyUrl: config.counterpartyUrl || request.nextUrl.origin,
|
|
2316
|
+
sourceIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
|
|
2317
|
+
rawHeaders: Object.fromEntries(request.headers.entries())
|
|
2318
|
+
});
|
|
2319
|
+
} : void 0;
|
|
1843
2320
|
let cachedRoutes = [];
|
|
1844
2321
|
let lastFetchAt = 0;
|
|
1845
2322
|
let refreshing = null;
|
|
@@ -1881,9 +2358,11 @@ function createMiddleware2(options) {
|
|
|
1881
2358
|
}
|
|
1882
2359
|
const routeConfig = findRouteConfig2(cachedRoutes, pathname, request.method);
|
|
1883
2360
|
if (!routeConfig) {
|
|
2361
|
+
beaconPassThrough?.(request, pathname);
|
|
1884
2362
|
return NextResponse.next();
|
|
1885
2363
|
}
|
|
1886
2364
|
if (routeConfig.minAccessLevel === "none") {
|
|
2365
|
+
beaconPassThrough?.(request, pathname);
|
|
1887
2366
|
return NextResponse.next();
|
|
1888
2367
|
}
|
|
1889
2368
|
const credentials = extractCredentialsFromNextRequest(request);
|
|
@@ -1935,8 +2414,8 @@ function createMiddleware2(options) {
|
|
|
1935
2414
|
{ status: 403 }
|
|
1936
2415
|
);
|
|
1937
2416
|
}
|
|
1938
|
-
if (
|
|
1939
|
-
return new NextResponse(
|
|
2417
|
+
if (interstitialEnabled) {
|
|
2418
|
+
return new NextResponse(generateInterstitialHtml(preCheckResult, options), {
|
|
1940
2419
|
status: 200,
|
|
1941
2420
|
headers: {
|
|
1942
2421
|
"Content-Type": "text/html",
|
|
@@ -1964,17 +2443,9 @@ function createMiddleware2(options) {
|
|
|
1964
2443
|
host: request.headers.get("host") || void 0,
|
|
1965
2444
|
forwardedFor,
|
|
1966
2445
|
agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0,
|
|
1967
|
-
// Workstream D — full sanitised header capture
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
Object.fromEntries(request.headers.entries())
|
|
1971
|
-
);
|
|
1972
|
-
return {
|
|
1973
|
-
headers,
|
|
1974
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
1975
|
-
...platformHeaders && { platformHeaders }
|
|
1976
|
-
};
|
|
1977
|
-
})()
|
|
2446
|
+
// Workstream D — full sanitised header capture + CDN-derived
|
|
2447
|
+
// connection block (Web Headers → plain map).
|
|
2448
|
+
observedMetadata: buildSdkObservedMetadata(Object.fromEntries(request.headers.entries()))
|
|
1978
2449
|
}
|
|
1979
2450
|
};
|
|
1980
2451
|
let result = await verify(config, verificationRequest);
|
|
@@ -1990,6 +2461,21 @@ function createMiddleware2(options) {
|
|
|
1990
2461
|
const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
|
|
1991
2462
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
1992
2463
|
if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
|
|
2464
|
+
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
2465
|
+
return NextResponse.json(
|
|
2466
|
+
{
|
|
2467
|
+
success: false,
|
|
2468
|
+
error: {
|
|
2469
|
+
code: "MERCHANT_VERIFICATION_MISCONFIGURED",
|
|
2470
|
+
message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
|
|
2471
|
+
guidance: result.guidance,
|
|
2472
|
+
failures: result.failures,
|
|
2473
|
+
correlationId: result.correlationId
|
|
2474
|
+
}
|
|
2475
|
+
},
|
|
2476
|
+
{ status: 503 }
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
1993
2479
|
if (pathname.startsWith("/api/")) {
|
|
1994
2480
|
return NextResponse.json(
|
|
1995
2481
|
{
|
|
@@ -2014,8 +2500,8 @@ function createMiddleware2(options) {
|
|
|
2014
2500
|
}
|
|
2015
2501
|
);
|
|
2016
2502
|
}
|
|
2017
|
-
if (
|
|
2018
|
-
return new NextResponse(
|
|
2503
|
+
if (interstitialEnabled) {
|
|
2504
|
+
return new NextResponse(generateInterstitialHtml(result, options), {
|
|
2019
2505
|
status: 200,
|
|
2020
2506
|
headers: {
|
|
2021
2507
|
"Content-Type": "text/html",
|
|
@@ -2103,7 +2589,7 @@ var VerificationGatewayClient = class {
|
|
|
2103
2589
|
/**
|
|
2104
2590
|
* Quick verification — checks credentials and policy in one call.
|
|
2105
2591
|
*
|
|
2106
|
-
*
|
|
2592
|
+
* The return shape mirrors `VerificationResult`'s identity/policy
|
|
2107
2593
|
* split. Map to HTTP status the same way: `!identityVerified` → 401,
|
|
2108
2594
|
* `identityVerified && !policyAllowed` → 403.
|
|
2109
2595
|
*/
|
|
@@ -2533,8 +3019,8 @@ function extractX402TransactionValue(req) {
|
|
|
2533
3019
|
// src/transport/rfc9421.ts
|
|
2534
3020
|
import { parseDictionary } from "structured-headers";
|
|
2535
3021
|
function parseRFC9421(headers) {
|
|
2536
|
-
const sigInput =
|
|
2537
|
-
const sig =
|
|
3022
|
+
const sigInput = readHeader2(headers, "signature-input");
|
|
3023
|
+
const sig = readHeader2(headers, "signature");
|
|
2538
3024
|
if (!sigInput || !sig) return null;
|
|
2539
3025
|
let inputDict;
|
|
2540
3026
|
let sigDict;
|
|
@@ -2578,7 +3064,7 @@ function parseRFC9421(headers) {
|
|
|
2578
3064
|
if (signatures.length === 0) return null;
|
|
2579
3065
|
return { signatures };
|
|
2580
3066
|
}
|
|
2581
|
-
function
|
|
3067
|
+
function readHeader2(headers, name) {
|
|
2582
3068
|
for (const key of Object.keys(headers)) {
|
|
2583
3069
|
if (key.toLowerCase() === name) {
|
|
2584
3070
|
const raw = headers[key];
|
|
@@ -2896,11 +3382,11 @@ function extractACPContext(request) {
|
|
|
2896
3382
|
const purpose = mapACPRequestToPurpose(method, path);
|
|
2897
3383
|
const sessionId = extractSessionId2(path);
|
|
2898
3384
|
const headers = request.headers ?? {};
|
|
2899
|
-
const signatureHeader =
|
|
2900
|
-
const timestampHeader =
|
|
2901
|
-
const idempotencyKey =
|
|
2902
|
-
const apiVersion =
|
|
2903
|
-
const bearer = extractBearer(
|
|
3385
|
+
const signatureHeader = readHeader3(headers, "signature");
|
|
3386
|
+
const timestampHeader = readHeader3(headers, "timestamp");
|
|
3387
|
+
const idempotencyKey = readHeader3(headers, "idempotency-key");
|
|
3388
|
+
const apiVersion = readHeader3(headers, "api-version");
|
|
3389
|
+
const bearer = extractBearer(readHeader3(headers, "authorization"));
|
|
2904
3390
|
const body = request.body ?? {};
|
|
2905
3391
|
const merchantId = coerceString3(body.merchant_id ?? body.merchantId);
|
|
2906
3392
|
const totals = Array.isArray(body.totals) ? body.totals : void 0;
|
|
@@ -2968,7 +3454,7 @@ function extractFulfillmentOption(body) {
|
|
|
2968
3454
|
}
|
|
2969
3455
|
return void 0;
|
|
2970
3456
|
}
|
|
2971
|
-
function
|
|
3457
|
+
function readHeader3(headers, name) {
|
|
2972
3458
|
for (const key of Object.keys(headers)) {
|
|
2973
3459
|
if (key.toLowerCase() === name) {
|
|
2974
3460
|
const raw = headers[key];
|
|
@@ -3762,9 +4248,9 @@ function toBuf(bytes) {
|
|
|
3762
4248
|
new Uint8Array(out).set(bytes);
|
|
3763
4249
|
return out;
|
|
3764
4250
|
}
|
|
3765
|
-
function checkTimestamp(
|
|
3766
|
-
if (!
|
|
3767
|
-
const ts = parseTimestamp(
|
|
4251
|
+
function checkTimestamp(headerValue3, toleranceSec, nowFn) {
|
|
4252
|
+
if (!headerValue3) return { ok: false, error: "missing Timestamp header" };
|
|
4253
|
+
const ts = parseTimestamp(headerValue3);
|
|
3768
4254
|
if (ts === null) return { ok: false, error: "unparseable Timestamp header" };
|
|
3769
4255
|
const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
|
|
3770
4256
|
if (Math.abs(now - ts) > toleranceSec) {
|
|
@@ -3808,7 +4294,7 @@ async function getSubtle() {
|
|
|
3808
4294
|
// src/transport/mpp.ts
|
|
3809
4295
|
import { Challenge, Credential, Receipt } from "mppx";
|
|
3810
4296
|
function extractMPPFromRequest(request) {
|
|
3811
|
-
const auth =
|
|
4297
|
+
const auth = readHeader4(request.headers, "authorization");
|
|
3812
4298
|
if (!auth || !/^\s*Payment\s+/i.test(auth)) return null;
|
|
3813
4299
|
try {
|
|
3814
4300
|
const credential = Credential.deserialize(auth);
|
|
@@ -3836,7 +4322,7 @@ function extractMPPFromResponse(response) {
|
|
|
3836
4322
|
offeredMethods: methods
|
|
3837
4323
|
};
|
|
3838
4324
|
}
|
|
3839
|
-
const receiptHeader =
|
|
4325
|
+
const receiptHeader = readHeader4(response.headers, "payment-receipt");
|
|
3840
4326
|
if (receiptHeader) {
|
|
3841
4327
|
try {
|
|
3842
4328
|
const parsed = Receipt.deserialize(receiptHeader);
|
|
@@ -3856,7 +4342,7 @@ function extractMPPFromResponse(response) {
|
|
|
3856
4342
|
return { kind: "error", error: { type: "invalid-receipt-encoding" } };
|
|
3857
4343
|
}
|
|
3858
4344
|
}
|
|
3859
|
-
const contentType =
|
|
4345
|
+
const contentType = readHeader4(response.headers, "content-type");
|
|
3860
4346
|
if (contentType && /application\/problem\+json/i.test(contentType)) {
|
|
3861
4347
|
const body = typeof response.body === "object" && response.body !== null ? response.body : {};
|
|
3862
4348
|
return {
|
|
@@ -3879,7 +4365,7 @@ function extractMPPContext(message) {
|
|
|
3879
4365
|
return extractMPPFromRequest(message);
|
|
3880
4366
|
}
|
|
3881
4367
|
function collectChallenges(response) {
|
|
3882
|
-
const wwwAuth =
|
|
4368
|
+
const wwwAuth = readHeader4(response.headers, "www-authenticate");
|
|
3883
4369
|
if (!wwwAuth) return [];
|
|
3884
4370
|
const headers = new Headers();
|
|
3885
4371
|
headers.set("www-authenticate", wwwAuth);
|
|
@@ -3907,7 +4393,7 @@ function summarizeChallenge(ch) {
|
|
|
3907
4393
|
opaque: raw.opaque
|
|
3908
4394
|
};
|
|
3909
4395
|
}
|
|
3910
|
-
function
|
|
4396
|
+
function readHeader4(headers, name) {
|
|
3911
4397
|
for (const key of Object.keys(headers)) {
|
|
3912
4398
|
if (key.toLowerCase() === name) {
|
|
3913
4399
|
const raw = headers[key];
|
|
@@ -3999,14 +4485,14 @@ import {
|
|
|
3999
4485
|
} from "@x402/core/schemas";
|
|
4000
4486
|
import { safeBase64Decode } from "@x402/core/utils";
|
|
4001
4487
|
function extractX402FromRequest(request) {
|
|
4002
|
-
const
|
|
4488
|
+
const headerValue3 = readHeader5(request.headers, "x-payment");
|
|
4003
4489
|
if (request.body && typeof request.body === "object") {
|
|
4004
4490
|
const parsed = tryParsePayload(request.body);
|
|
4005
4491
|
if (parsed) return buildPayloadContext(parsed, "body");
|
|
4006
4492
|
}
|
|
4007
|
-
if (
|
|
4493
|
+
if (headerValue3) {
|
|
4008
4494
|
try {
|
|
4009
|
-
const decoded = safeBase64Decode(
|
|
4495
|
+
const decoded = safeBase64Decode(headerValue3);
|
|
4010
4496
|
if (decoded) {
|
|
4011
4497
|
const json = JSON.parse(decoded);
|
|
4012
4498
|
const parsed = tryParsePayload(json);
|
|
@@ -4029,10 +4515,10 @@ function extractX402FromResponse(response) {
|
|
|
4029
4515
|
const parsed = tryParseRequired(response.body);
|
|
4030
4516
|
if (parsed) return buildRequiredContext(parsed, "body");
|
|
4031
4517
|
}
|
|
4032
|
-
const
|
|
4033
|
-
if (
|
|
4518
|
+
const headerValue3 = readHeader5(response.headers, "x-payment-required");
|
|
4519
|
+
if (headerValue3) {
|
|
4034
4520
|
try {
|
|
4035
|
-
const decoded = safeBase64Decode(
|
|
4521
|
+
const decoded = safeBase64Decode(headerValue3);
|
|
4036
4522
|
if (decoded) {
|
|
4037
4523
|
const json = JSON.parse(decoded);
|
|
4038
4524
|
const parsed = tryParseRequired(json);
|
|
@@ -4129,7 +4615,7 @@ function coerceVersion(v) {
|
|
|
4129
4615
|
if (v === 1 || v === 2) return v;
|
|
4130
4616
|
return null;
|
|
4131
4617
|
}
|
|
4132
|
-
function
|
|
4618
|
+
function readHeader5(headers, name) {
|
|
4133
4619
|
if (!headers) return void 0;
|
|
4134
4620
|
for (const key of Object.keys(headers)) {
|
|
4135
4621
|
if (key.toLowerCase() === name) {
|
|
@@ -4877,8 +5363,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
4877
5363
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
4878
5364
|
action = parsed.actionFromBody;
|
|
4879
5365
|
actionSource = parsed.actionSourceFromBody;
|
|
5366
|
+
} else if (parsed.method === "tools/call") {
|
|
5367
|
+
action = void 0;
|
|
5368
|
+
actionSource = void 0;
|
|
4880
5369
|
} else {
|
|
4881
|
-
action = parsed.toolName ?
|
|
5370
|
+
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
4882
5371
|
actionSource = "transport_layer";
|
|
4883
5372
|
}
|
|
4884
5373
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -4916,6 +5405,24 @@ function dedupeFailures2(result) {
|
|
|
4916
5405
|
}
|
|
4917
5406
|
function defaultMcpDenied(result, req, res) {
|
|
4918
5407
|
const id = req.body?.id ?? null;
|
|
5408
|
+
if (result.misconfigured) {
|
|
5409
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5410
|
+
res.status(503).json({
|
|
5411
|
+
jsonrpc: "2.0",
|
|
5412
|
+
id,
|
|
5413
|
+
error: {
|
|
5414
|
+
// -32002 → merchant verification misconfigured (see -32000/-32001 below)
|
|
5415
|
+
code: -32002,
|
|
5416
|
+
message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
|
|
5417
|
+
data: {
|
|
5418
|
+
guidance: result.guidance,
|
|
5419
|
+
failures: result.failures,
|
|
5420
|
+
correlationId: result.correlationId
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
});
|
|
5424
|
+
return;
|
|
5425
|
+
}
|
|
4919
5426
|
const status = !result.identityVerified ? 401 : 403;
|
|
4920
5427
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
4921
5428
|
res.status(status).json({
|
|
@@ -4925,6 +5432,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
4925
5432
|
// JSON-RPC error codes:
|
|
4926
5433
|
// -32000 → unauthorized (no identity resolved)
|
|
4927
5434
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
5435
|
+
// -32002 → merchant verification misconfigured (handled above)
|
|
4928
5436
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
4929
5437
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
4930
5438
|
data: {
|
|
@@ -4974,6 +5482,15 @@ function createMcpMiddleware(options) {
|
|
|
4974
5482
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
4975
5483
|
});
|
|
4976
5484
|
}
|
|
5485
|
+
if (toolGates) {
|
|
5486
|
+
for (const [toolName, gate] of Object.entries(toolGates)) {
|
|
5487
|
+
if (typeof gate === "string") {
|
|
5488
|
+
console.warn(
|
|
5489
|
+
`[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: '...' }.`
|
|
5490
|
+
);
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
4977
5494
|
return async (req, res, next) => {
|
|
4978
5495
|
try {
|
|
4979
5496
|
if (skip) return next();
|
|
@@ -5093,7 +5610,13 @@ function createMcpMiddleware(options) {
|
|
|
5093
5610
|
callerMetadata: {
|
|
5094
5611
|
sourceIp: req.ip,
|
|
5095
5612
|
userAgent: req.headers["user-agent"],
|
|
5096
|
-
host: req.headers.host
|
|
5613
|
+
host: req.headers.host,
|
|
5614
|
+
// Capture EVERY inbound header (parity with the express adapter —
|
|
5615
|
+
// MCP-only merchants have no edge to do it upstream) plus the
|
|
5616
|
+
// CDN-derived connection block. Local string work, secrets
|
|
5617
|
+
// stripped; out of the verify cache key so it doesn't defeat
|
|
5618
|
+
// caching.
|
|
5619
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
5097
5620
|
}
|
|
5098
5621
|
};
|
|
5099
5622
|
let result = await verify(config, verificationRequest);
|
|
@@ -5878,212 +6401,6 @@ async function recordDecision2(config, params) {
|
|
|
5878
6401
|
blockchainTxHash: result.blockchainTxHash
|
|
5879
6402
|
};
|
|
5880
6403
|
}
|
|
5881
|
-
|
|
5882
|
-
// src/edge-config.ts
|
|
5883
|
-
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
5884
|
-
version: 1,
|
|
5885
|
-
mode: "observe",
|
|
5886
|
-
depth: "classify",
|
|
5887
|
-
pathRules: [],
|
|
5888
|
-
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
5889
|
-
failurePosture: "open"
|
|
5890
|
-
});
|
|
5891
|
-
var EDGE_MODES = ["observe", "enforce"];
|
|
5892
|
-
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
5893
|
-
function isEdgeConfig(value) {
|
|
5894
|
-
if (typeof value !== "object" || value === null) return false;
|
|
5895
|
-
const v = value;
|
|
5896
|
-
if (v.version !== 1) return false;
|
|
5897
|
-
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
5898
|
-
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
5899
|
-
if (!Array.isArray(v.pathRules)) return false;
|
|
5900
|
-
for (const rule of v.pathRules) {
|
|
5901
|
-
if (typeof rule !== "object" || rule === null) return false;
|
|
5902
|
-
const r = rule;
|
|
5903
|
-
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
5904
|
-
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
5905
|
-
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
5906
|
-
}
|
|
5907
|
-
const sampling = v.sampling;
|
|
5908
|
-
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
5909
|
-
return false;
|
|
5910
|
-
}
|
|
5911
|
-
return v.failurePosture === "open";
|
|
5912
|
-
}
|
|
5913
|
-
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
5914
|
-
if (!counterpartyId) return null;
|
|
5915
|
-
const headers = { Accept: "application/json" };
|
|
5916
|
-
if (config.apiKey) {
|
|
5917
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
5918
|
-
headers["X-API-Key"] = config.apiKey;
|
|
5919
|
-
}
|
|
5920
|
-
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
5921
|
-
try {
|
|
5922
|
-
const response = await fetch(
|
|
5923
|
-
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
5924
|
-
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
5925
|
-
);
|
|
5926
|
-
if (response.status === 304) return { notModified: true };
|
|
5927
|
-
if (!response.ok) return null;
|
|
5928
|
-
const body = await response.json();
|
|
5929
|
-
const edgeConfig = body.data?.edgeConfig;
|
|
5930
|
-
if (!isEdgeConfig(edgeConfig)) return null;
|
|
5931
|
-
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
5932
|
-
} catch {
|
|
5933
|
-
return null;
|
|
5934
|
-
}
|
|
5935
|
-
}
|
|
5936
|
-
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
5937
|
-
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
5938
|
-
var cache2 = /* @__PURE__ */ new Map();
|
|
5939
|
-
var inflight2 = /* @__PURE__ */ new Map();
|
|
5940
|
-
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
5941
|
-
return `${apiBaseUrl}|${counterpartyId}`;
|
|
5942
|
-
}
|
|
5943
|
-
function degradeToObserve(config) {
|
|
5944
|
-
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
5945
|
-
return config;
|
|
5946
|
-
}
|
|
5947
|
-
return {
|
|
5948
|
-
...config,
|
|
5949
|
-
mode: "observe",
|
|
5950
|
-
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
5951
|
-
};
|
|
5952
|
-
}
|
|
5953
|
-
async function revalidate(config, counterpartyId) {
|
|
5954
|
-
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
5955
|
-
const existing = inflight2.get(key);
|
|
5956
|
-
if (existing) return existing;
|
|
5957
|
-
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
5958
|
-
if (result === null) return;
|
|
5959
|
-
const now = Date.now();
|
|
5960
|
-
if ("notModified" in result) {
|
|
5961
|
-
const entry = cache2.get(key);
|
|
5962
|
-
if (entry) entry.lastSuccessAt = now;
|
|
5963
|
-
return;
|
|
5964
|
-
}
|
|
5965
|
-
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
5966
|
-
}).finally(() => {
|
|
5967
|
-
inflight2.delete(key);
|
|
5968
|
-
});
|
|
5969
|
-
inflight2.set(key, promise);
|
|
5970
|
-
return promise;
|
|
5971
|
-
}
|
|
5972
|
-
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
5973
|
-
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
5974
|
-
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
5975
|
-
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
5976
|
-
const entry = cache2.get(key);
|
|
5977
|
-
if (entry) {
|
|
5978
|
-
const age = Date.now() - entry.lastSuccessAt;
|
|
5979
|
-
if (age > ttlMs) {
|
|
5980
|
-
revalidate(config, counterpartyId).catch(() => {
|
|
5981
|
-
});
|
|
5982
|
-
}
|
|
5983
|
-
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
5984
|
-
}
|
|
5985
|
-
await revalidate(config, counterpartyId).catch(() => {
|
|
5986
|
-
});
|
|
5987
|
-
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
5988
|
-
}
|
|
5989
|
-
function matchEdgePathRule(rules, path) {
|
|
5990
|
-
for (const rule of rules) {
|
|
5991
|
-
if (globToRegExp(rule.pattern).test(path)) return rule;
|
|
5992
|
-
}
|
|
5993
|
-
return void 0;
|
|
5994
|
-
}
|
|
5995
|
-
function globToRegExp(pattern) {
|
|
5996
|
-
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
5997
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
5998
|
-
}
|
|
5999
|
-
function _resetEdgeConfigCache() {
|
|
6000
|
-
cache2.clear();
|
|
6001
|
-
inflight2.clear();
|
|
6002
|
-
}
|
|
6003
|
-
|
|
6004
|
-
// src/platform-signatures.ts
|
|
6005
|
-
var PLATFORM_AGENT_SIGNATURES = [
|
|
6006
|
-
{
|
|
6007
|
-
vendor: "claude",
|
|
6008
|
-
displayName: "Anthropic Claude",
|
|
6009
|
-
uaPatterns: [/claude/i, /anthropic/i],
|
|
6010
|
-
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
6011
|
-
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
6012
|
-
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
6013
|
-
},
|
|
6014
|
-
{
|
|
6015
|
-
vendor: "chatgpt",
|
|
6016
|
-
displayName: "OpenAI ChatGPT",
|
|
6017
|
-
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
6018
|
-
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
6019
|
-
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
6020
|
-
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
6021
|
-
},
|
|
6022
|
-
{
|
|
6023
|
-
vendor: "gemini",
|
|
6024
|
-
displayName: "Google Gemini",
|
|
6025
|
-
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
6026
|
-
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
6027
|
-
apiKeyFormatPatterns: [/^aiza/i],
|
|
6028
|
-
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
6029
|
-
},
|
|
6030
|
-
{
|
|
6031
|
-
vendor: "cursor",
|
|
6032
|
-
displayName: "Cursor",
|
|
6033
|
-
uaPatterns: [/cursor/i],
|
|
6034
|
-
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
6035
|
-
},
|
|
6036
|
-
{
|
|
6037
|
-
vendor: "goose",
|
|
6038
|
-
displayName: "Block Goose",
|
|
6039
|
-
uaPatterns: [/goose/i, /block-goose/i],
|
|
6040
|
-
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
6041
|
-
},
|
|
6042
|
-
{
|
|
6043
|
-
vendor: "perplexity",
|
|
6044
|
-
displayName: "Perplexity",
|
|
6045
|
-
uaPatterns: [/perplexity/i],
|
|
6046
|
-
agentCardPatterns: [/perplexity\.ai/i],
|
|
6047
|
-
apiKeyFormatPatterns: [/^pplx-/i]
|
|
6048
|
-
}
|
|
6049
|
-
];
|
|
6050
|
-
function matchPlatformSignature(signatures, input) {
|
|
6051
|
-
const ua = input.userAgent ?? "";
|
|
6052
|
-
const cardUrl = input.agentCardUrl ?? "";
|
|
6053
|
-
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
6054
|
-
const headerLines = [];
|
|
6055
|
-
if (input.platformHeaders) {
|
|
6056
|
-
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
6057
|
-
headerLines.push(name, `${name}: ${value}`);
|
|
6058
|
-
}
|
|
6059
|
-
}
|
|
6060
|
-
for (const sig of signatures) {
|
|
6061
|
-
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
6062
|
-
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
6063
|
-
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
6064
|
-
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
6065
|
-
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
6066
|
-
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
6067
|
-
const cardHost = (() => {
|
|
6068
|
-
try {
|
|
6069
|
-
return cardUrl ? new URL(cardUrl).host : "";
|
|
6070
|
-
} catch {
|
|
6071
|
-
return "";
|
|
6072
|
-
}
|
|
6073
|
-
})();
|
|
6074
|
-
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
6075
|
-
return {
|
|
6076
|
-
vendor: sig.vendor,
|
|
6077
|
-
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
6078
|
-
displayName: sig.displayName
|
|
6079
|
-
};
|
|
6080
|
-
}
|
|
6081
|
-
}
|
|
6082
|
-
return void 0;
|
|
6083
|
-
}
|
|
6084
|
-
function detectPlatformFingerprint(input) {
|
|
6085
|
-
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
6086
|
-
}
|
|
6087
6404
|
export {
|
|
6088
6405
|
ACCESS_LEVEL_DESCRIPTIONS,
|
|
6089
6406
|
ACCESS_LEVEL_HIERARCHY,
|
|
@@ -6091,6 +6408,7 @@ export {
|
|
|
6091
6408
|
AstraSync,
|
|
6092
6409
|
AstraSyncError,
|
|
6093
6410
|
AuthenticationError,
|
|
6411
|
+
CAPTURE_SCHEMA_VERSION,
|
|
6094
6412
|
ChallengeHandler,
|
|
6095
6413
|
DEFAULT_EDGE_CONFIG,
|
|
6096
6414
|
DEFAULT_TRUST_THRESHOLDS,
|
|
@@ -6109,9 +6427,11 @@ export {
|
|
|
6109
6427
|
authorizeSettlement,
|
|
6110
6428
|
awaitStepUpApproval,
|
|
6111
6429
|
buildGuidance,
|
|
6430
|
+
buildSdkObservedMetadata,
|
|
6112
6431
|
clearCache,
|
|
6113
6432
|
createMcpMiddleware,
|
|
6114
6433
|
degradeToObserve,
|
|
6434
|
+
deriveConnectionFromHeaders,
|
|
6115
6435
|
detectPlatformFingerprint,
|
|
6116
6436
|
determineAccessLevel,
|
|
6117
6437
|
express_exports as express,
|