@astrasyncai/verification-gateway 5.11.0 → 5.12.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 +4 -0
- package/dist/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +359 -30
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +359 -30
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +5 -1
- package/dist/adapters/mcp.d.ts +5 -1
- package/dist/adapters/mcp.js +168 -41
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +168 -41
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +352 -13
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +352 -13
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +9 -1
- package/dist/adapters/sdk.d.ts +9 -1
- package/dist/adapters/sdk.js +100 -8
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +100 -8
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.js +1 -1
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +284 -7
- package/dist/bin/astrasync-codex-hook.js +284 -7
- package/dist/bin/astrasync-guard.js +284 -7
- package/dist/bin/astrasync.js +297 -9
- package/dist/browser/background.js +284 -7
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +284 -7
- package/dist/browser/background.mjs.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.js +284 -7
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +284 -7
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +284 -7
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +284 -7
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +1 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +128 -13
- package/dist/edge-core/index.d.ts +128 -13
- package/dist/edge-core/index.js +337 -55
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +334 -55
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +284 -7
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +284 -7
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +118 -5
- package/dist/index.d.ts +118 -5
- package/dist/index.js +486 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +480 -63
- package/dist/index.mjs.map +1 -1
- package/dist/platform-signatures.js +4 -1
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs +4 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.js +1 -1
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/transport/index.js +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
- package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
- package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
- package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +35 -4
- package/dist/verify.d.ts +35 -4
- package/dist/verify.js +130 -16
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +129 -16
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ function getTrustLevel(score) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var SDK_VERSION = "5.
|
|
22
|
+
var SDK_VERSION = "5.12.0";
|
|
23
23
|
|
|
24
24
|
// src/http.ts
|
|
25
25
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -98,6 +98,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
98
98
|
return cache.get(apiBaseUrl)?.data;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// src/failure-posture.ts
|
|
102
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
103
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
104
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
105
|
+
"identity.invalid_agent_id",
|
|
106
|
+
"identity.unknown_agent",
|
|
107
|
+
"agent.lookup"
|
|
108
|
+
]);
|
|
109
|
+
var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
|
|
110
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
111
|
+
"verify_access.api_error",
|
|
112
|
+
"middleware.internal_error"
|
|
113
|
+
]);
|
|
114
|
+
function isVerificationUnavailable(result) {
|
|
115
|
+
if (result.misconfigured) return false;
|
|
116
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
117
|
+
}
|
|
118
|
+
function unavailableRetryAfterSeconds(result) {
|
|
119
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
120
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
121
|
+
}
|
|
122
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
123
|
+
}
|
|
124
|
+
function isAnonymousRead(method, credentials) {
|
|
125
|
+
const m = (method ?? "").toUpperCase();
|
|
126
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
127
|
+
return !credentials.astraId && !credentials.jwt;
|
|
128
|
+
}
|
|
129
|
+
function buildVerificationUnavailableBody(result) {
|
|
130
|
+
return {
|
|
131
|
+
success: false,
|
|
132
|
+
error: {
|
|
133
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
134
|
+
message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry after the Retry-After interval.",
|
|
135
|
+
retryable: true,
|
|
136
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
137
|
+
...result.failures && { failures: result.failures },
|
|
138
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
139
|
+
guidance: {
|
|
140
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function isRejectedIdentityClaim(result) {
|
|
146
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
147
|
+
}
|
|
148
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
149
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
150
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
151
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
152
|
+
}
|
|
153
|
+
|
|
101
154
|
// src/verify.ts
|
|
102
155
|
var DEFAULT_CONFIG = {
|
|
103
156
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -349,11 +402,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
349
402
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
350
403
|
// deterministic and intentionally NOT marked retryable.
|
|
351
404
|
...isApiError && { retryable: true },
|
|
405
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
406
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
352
407
|
correlationId: options.correlationId,
|
|
353
408
|
attemptId: options.attemptId,
|
|
354
409
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
355
410
|
};
|
|
356
411
|
}
|
|
412
|
+
async function readJsonSafe(response) {
|
|
413
|
+
try {
|
|
414
|
+
const parsed = await response.json();
|
|
415
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
416
|
+
} catch {
|
|
417
|
+
return {};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function rejectedIdentityGuidance(failures) {
|
|
421
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
422
|
+
if (!f) return void 0;
|
|
423
|
+
if (f.dimension === "agent.lookup") {
|
|
424
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
425
|
+
}
|
|
426
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
427
|
+
}
|
|
428
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
429
|
+
function parseRetryAfterMs(response) {
|
|
430
|
+
const raw = response.headers?.get?.("retry-after");
|
|
431
|
+
if (!raw) return void 0;
|
|
432
|
+
const trimmed = raw.trim();
|
|
433
|
+
let ms;
|
|
434
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
435
|
+
ms = Number(trimmed) * 1e3;
|
|
436
|
+
} else {
|
|
437
|
+
const at = Date.parse(trimmed);
|
|
438
|
+
if (Number.isNaN(at)) return void 0;
|
|
439
|
+
ms = at - Date.now();
|
|
440
|
+
}
|
|
441
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
442
|
+
}
|
|
357
443
|
async function callVerifyAccessAPI(config, request) {
|
|
358
444
|
const { credentials, ...requestData } = request;
|
|
359
445
|
const body = {
|
|
@@ -410,7 +496,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
410
496
|
body: JSON.stringify(body),
|
|
411
497
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
412
498
|
});
|
|
413
|
-
const data = await response
|
|
499
|
+
const data = await readJsonSafe(response);
|
|
500
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
414
501
|
if (response.status === 410) {
|
|
415
502
|
return {
|
|
416
503
|
success: true,
|
|
@@ -448,6 +535,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
448
535
|
}
|
|
449
536
|
};
|
|
450
537
|
}
|
|
538
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
539
|
+
return {
|
|
540
|
+
success: true,
|
|
541
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
542
|
+
access: {
|
|
543
|
+
allowed: false,
|
|
544
|
+
reason: "unknown_agent",
|
|
545
|
+
failures: [
|
|
546
|
+
{
|
|
547
|
+
dimension: "identity.unknown_agent",
|
|
548
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
549
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
451
555
|
if (!response.ok) {
|
|
452
556
|
if (response.status === 401) {
|
|
453
557
|
return {
|
|
@@ -462,9 +566,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
462
566
|
}
|
|
463
567
|
return {
|
|
464
568
|
success: false,
|
|
465
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
569
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
466
570
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
467
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
571
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
572
|
+
httpStatus: response.status,
|
|
573
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
468
574
|
};
|
|
469
575
|
}
|
|
470
576
|
return data;
|
|
@@ -536,12 +642,14 @@ async function verify(config, request, options) {
|
|
|
536
642
|
correlationId: apiResponse.correlationId,
|
|
537
643
|
attemptId: apiResponse.attemptId,
|
|
538
644
|
remediation: apiResponse.remediation,
|
|
645
|
+
httpStatus: apiResponse.httpStatus,
|
|
646
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
539
647
|
urls
|
|
540
648
|
});
|
|
541
649
|
}
|
|
542
650
|
if (!apiResponse.access?.allowed) {
|
|
543
651
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
544
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
652
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
545
653
|
const result2 = {
|
|
546
654
|
identityVerified: idVerifiedFromBackend,
|
|
547
655
|
policyAllowed: false,
|
|
@@ -553,7 +661,10 @@ async function verify(config, request, options) {
|
|
|
553
661
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
554
662
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
555
663
|
guidance: {
|
|
556
|
-
|
|
664
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
665
|
+
// actionable registration guidance on its failure — surface it
|
|
666
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
667
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
557
668
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
558
669
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
559
670
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -653,7 +764,7 @@ async function verify(config, request, options) {
|
|
|
653
764
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
654
765
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
655
766
|
};
|
|
656
|
-
} else if (result.recommendation === "step_up_required") {
|
|
767
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
657
768
|
result.requiresStepUp = true;
|
|
658
769
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
659
770
|
}
|
|
@@ -676,7 +787,7 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
676
787
|
}).catch(() => {
|
|
677
788
|
});
|
|
678
789
|
}
|
|
679
|
-
async function reportAttempt(config, report) {
|
|
790
|
+
async function reportAttempt(config, report, options = {}) {
|
|
680
791
|
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
681
792
|
const headers = { "Content-Type": "application/json" };
|
|
682
793
|
if (config.apiKey) {
|
|
@@ -687,13 +798,37 @@ async function reportAttempt(config, report) {
|
|
|
687
798
|
...report,
|
|
688
799
|
...!report.counterpartyId && config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
689
800
|
};
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
801
|
+
const url = `${apiBaseUrl}/agents/verify-access/attempt-report`;
|
|
802
|
+
const init = { method: "POST", headers, body: JSON.stringify(body) };
|
|
803
|
+
const fail = (failure) => {
|
|
804
|
+
try {
|
|
805
|
+
options.onFailure?.(failure);
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
let response;
|
|
810
|
+
try {
|
|
811
|
+
response = await sdkFetch(url, init);
|
|
812
|
+
} catch (err) {
|
|
813
|
+
fail({ error: err instanceof Error ? err.message : String(err), retried: false });
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (response.ok) return;
|
|
817
|
+
if (response.status !== 429 && response.status < 500) {
|
|
818
|
+
fail({ status: response.status, retried: false });
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const delayMs = Math.min(ATTEMPT_REPORT_MAX_RETRY_DELAY_MS, parseRetryAfterMs(response) ?? 500);
|
|
822
|
+
const timer = setTimeout(() => {
|
|
823
|
+
sdkFetch(url, init).then((retry) => {
|
|
824
|
+
if (!retry.ok) fail({ status: retry.status, retried: true });
|
|
825
|
+
}).catch((err) => {
|
|
826
|
+
fail({ error: err instanceof Error ? err.message : String(err), retried: true });
|
|
827
|
+
});
|
|
828
|
+
}, delayMs);
|
|
829
|
+
timer.unref?.();
|
|
696
830
|
}
|
|
831
|
+
var ATTEMPT_REPORT_MAX_RETRY_DELAY_MS = 2e3;
|
|
697
832
|
async function fetchRoutes(config, counterpartyId) {
|
|
698
833
|
if (!counterpartyId) return null;
|
|
699
834
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -723,7 +858,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
723
858
|
};
|
|
724
859
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
725
860
|
method: "POST",
|
|
726
|
-
headers: {
|
|
861
|
+
headers: {
|
|
862
|
+
"Content-Type": "application/json",
|
|
863
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
864
|
+
},
|
|
727
865
|
body: JSON.stringify(body)
|
|
728
866
|
}).catch(() => {
|
|
729
867
|
});
|
|
@@ -745,7 +883,11 @@ async function quickVerify(config, credentials) {
|
|
|
745
883
|
return {
|
|
746
884
|
identityVerified: result.identityVerified,
|
|
747
885
|
policyAllowed: result.policyAllowed,
|
|
748
|
-
reason: result.denialReasons?.[0]
|
|
886
|
+
reason: result.denialReasons?.[0],
|
|
887
|
+
...isVerificationUnavailable(result) && {
|
|
888
|
+
unavailable: true,
|
|
889
|
+
retryAfterMs: unavailableRetryAfterSeconds(result) * 1e3
|
|
890
|
+
}
|
|
749
891
|
};
|
|
750
892
|
}
|
|
751
893
|
|
|
@@ -1493,7 +1635,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1493
1635
|
{
|
|
1494
1636
|
vendor: "chatgpt",
|
|
1495
1637
|
displayName: "OpenAI ChatGPT",
|
|
1496
|
-
|
|
1638
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1639
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1640
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1641
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1497
1642
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1498
1643
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1499
1644
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1628,6 +1773,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1628
1773
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1629
1774
|
|
|
1630
1775
|
// src/edge-core/events.ts
|
|
1776
|
+
async function withBudget(promise, ms) {
|
|
1777
|
+
let timer;
|
|
1778
|
+
try {
|
|
1779
|
+
return await Promise.race([
|
|
1780
|
+
promise,
|
|
1781
|
+
new Promise((resolve) => {
|
|
1782
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1783
|
+
})
|
|
1784
|
+
]);
|
|
1785
|
+
} catch {
|
|
1786
|
+
return void 0;
|
|
1787
|
+
} finally {
|
|
1788
|
+
if (timer) clearTimeout(timer);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1792
|
+
var BeaconBuffer = class {
|
|
1793
|
+
constructor(options = {}) {
|
|
1794
|
+
this.queue = [];
|
|
1795
|
+
this.droppedSinceLast = 0;
|
|
1796
|
+
this.droppedTotal = 0;
|
|
1797
|
+
this.deliveredTotal = 0;
|
|
1798
|
+
this.backoffUntil = 0;
|
|
1799
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1800
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1801
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1802
|
+
this.opts = {
|
|
1803
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1804
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1805
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1806
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1807
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1808
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1809
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1810
|
+
now: options.now ?? Date.now
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1815
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1816
|
+
* becoming its own item.
|
|
1817
|
+
*/
|
|
1818
|
+
enqueue(dest, item, collapseKey) {
|
|
1819
|
+
const now = this.opts.now();
|
|
1820
|
+
if (collapseKey) {
|
|
1821
|
+
const entry = this.collapse.get(collapseKey);
|
|
1822
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1823
|
+
for (const target of [entry.first, entry.held]) {
|
|
1824
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1825
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
const held = this.push(
|
|
1830
|
+
dest,
|
|
1831
|
+
{ ...item, repeatCount: 1 },
|
|
1832
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1833
|
+
);
|
|
1834
|
+
entry.held = held;
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
this.collapse.delete(collapseKey);
|
|
1838
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1839
|
+
const oldest = this.collapse.keys().next().value;
|
|
1840
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1841
|
+
}
|
|
1842
|
+
const fresh = { firstSeenAt: now };
|
|
1843
|
+
this.collapse.set(collapseKey, fresh);
|
|
1844
|
+
fresh.first = this.push(dest, item, now);
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1847
|
+
this.push(dest, item, now);
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1851
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1852
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1853
|
+
* picked up by the same drain.
|
|
1854
|
+
*/
|
|
1855
|
+
flush() {
|
|
1856
|
+
if (!this.inFlight) {
|
|
1857
|
+
this.inFlight = this.drain().finally(() => {
|
|
1858
|
+
this.inFlight = void 0;
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
return this.inFlight;
|
|
1862
|
+
}
|
|
1863
|
+
/** Counters for tests and diagnostics. */
|
|
1864
|
+
stats() {
|
|
1865
|
+
return {
|
|
1866
|
+
queueDepth: this.queue.length,
|
|
1867
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1868
|
+
droppedTotal: this.droppedTotal,
|
|
1869
|
+
deliveredTotal: this.deliveredTotal,
|
|
1870
|
+
flushing: this.inFlight !== void 0
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
push(dest, item, eligibleAt) {
|
|
1874
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1875
|
+
const entry = {
|
|
1876
|
+
dest,
|
|
1877
|
+
destKey: destKeyOf(dest),
|
|
1878
|
+
item,
|
|
1879
|
+
attempts: 0,
|
|
1880
|
+
eligibleAt,
|
|
1881
|
+
inFlight: false
|
|
1882
|
+
};
|
|
1883
|
+
this.queue.push(entry);
|
|
1884
|
+
return entry;
|
|
1885
|
+
}
|
|
1886
|
+
dropOldest() {
|
|
1887
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1888
|
+
}
|
|
1889
|
+
countDropped(n) {
|
|
1890
|
+
this.droppedSinceLast += n;
|
|
1891
|
+
this.droppedTotal += n;
|
|
1892
|
+
}
|
|
1893
|
+
async drain() {
|
|
1894
|
+
for (; ; ) {
|
|
1895
|
+
const now = this.opts.now();
|
|
1896
|
+
if (now < this.backoffUntil) return;
|
|
1897
|
+
const batch = this.takeBatch(now);
|
|
1898
|
+
if (batch.length === 0) return;
|
|
1899
|
+
const ok = await this.send(batch);
|
|
1900
|
+
if (!ok) return;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
1904
|
+
takeBatch(now) {
|
|
1905
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
1906
|
+
if (!first) return [];
|
|
1907
|
+
const batch = [];
|
|
1908
|
+
const rest = [];
|
|
1909
|
+
for (const q of this.queue) {
|
|
1910
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
1911
|
+
q.inFlight = true;
|
|
1912
|
+
batch.push(q);
|
|
1913
|
+
} else {
|
|
1914
|
+
rest.push(q);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
this.queue = rest;
|
|
1918
|
+
return batch;
|
|
1919
|
+
}
|
|
1920
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
1921
|
+
async send(batch) {
|
|
1922
|
+
const dest = batch[0].dest;
|
|
1923
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
1924
|
+
const reportedDropped = this.droppedSinceLast;
|
|
1925
|
+
const meta = {
|
|
1926
|
+
droppedSinceLast: reportedDropped,
|
|
1927
|
+
queueDepth: this.queue.length,
|
|
1928
|
+
adapter: dest.adapter ?? "edge-core",
|
|
1929
|
+
version: SDK_VERSION
|
|
1930
|
+
};
|
|
1931
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1932
|
+
const response = await withBudget(
|
|
1933
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
1934
|
+
method: "POST",
|
|
1935
|
+
headers: {
|
|
1936
|
+
"Content-Type": "application/json",
|
|
1937
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
1938
|
+
},
|
|
1939
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
1940
|
+
...controller && { signal: controller.signal }
|
|
1941
|
+
}),
|
|
1942
|
+
this.opts.requestTimeoutMs
|
|
1943
|
+
);
|
|
1944
|
+
if (!response) controller?.abort();
|
|
1945
|
+
const status = response?.status;
|
|
1946
|
+
for (const q of batch) q.inFlight = false;
|
|
1947
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
1948
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
1949
|
+
this.deliveredTotal += batch.length;
|
|
1950
|
+
return true;
|
|
1951
|
+
}
|
|
1952
|
+
if (status === 401 && useKey) {
|
|
1953
|
+
this.rejectedKeys.add(useKey);
|
|
1954
|
+
this.requeue(batch, false);
|
|
1955
|
+
return true;
|
|
1956
|
+
}
|
|
1957
|
+
if (status === 429) {
|
|
1958
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
1959
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
1960
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
1961
|
+
this.requeue(batch, false);
|
|
1962
|
+
return false;
|
|
1963
|
+
}
|
|
1964
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
1965
|
+
this.countDropped(batch.length);
|
|
1966
|
+
return true;
|
|
1967
|
+
}
|
|
1968
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
1969
|
+
this.requeue(batch, true);
|
|
1970
|
+
return false;
|
|
1971
|
+
}
|
|
1972
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
1973
|
+
requeue(batch, countAttempt) {
|
|
1974
|
+
const keep = [];
|
|
1975
|
+
for (const q of batch) {
|
|
1976
|
+
if (countAttempt) q.attempts += 1;
|
|
1977
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
1978
|
+
else keep.push(q);
|
|
1979
|
+
}
|
|
1980
|
+
this.queue = [...keep, ...this.queue];
|
|
1981
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
1982
|
+
}
|
|
1983
|
+
};
|
|
1984
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1631
1985
|
function sampled(rate, random = Math.random) {
|
|
1632
1986
|
if (rate <= 0) return false;
|
|
1633
1987
|
if (rate >= 1) return true;
|
|
@@ -1804,12 +2158,19 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1804
2158
|
});
|
|
1805
2159
|
return;
|
|
1806
2160
|
}
|
|
1807
|
-
|
|
2161
|
+
if (isVerificationUnavailable(result)) {
|
|
2162
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2163
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2164
|
+
res.setHeader("Retry-After", String(unavailableRetryAfterSeconds(result)));
|
|
2165
|
+
res.status(503).json(buildVerificationUnavailableBody(result));
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
const { status: statusCode, code } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
1808
2169
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1809
2170
|
res.status(statusCode).json({
|
|
1810
2171
|
success: false,
|
|
1811
2172
|
error: {
|
|
1812
|
-
code
|
|
2173
|
+
code,
|
|
1813
2174
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1814
2175
|
guidance: result.guidance,
|
|
1815
2176
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
@@ -2057,6 +2418,21 @@ function createMiddleware(options) {
|
|
|
2057
2418
|
result.stepUpOutcome = outcome;
|
|
2058
2419
|
}
|
|
2059
2420
|
}
|
|
2421
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isAnonymousRead(req.method, credentials))) {
|
|
2422
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2423
|
+
return next();
|
|
2424
|
+
}
|
|
2425
|
+
if (!shouldEnforce) {
|
|
2426
|
+
if (config.setPassThroughHeader) {
|
|
2427
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2428
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
2429
|
+
}
|
|
2430
|
+
if (shouldRecordDecisions && sessionId) {
|
|
2431
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
return next();
|
|
2435
|
+
}
|
|
2060
2436
|
if (!result.policyAllowed) {
|
|
2061
2437
|
if (shouldRecordDecisions && sessionId) {
|
|
2062
2438
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -2076,17 +2452,6 @@ function createMiddleware(options) {
|
|
|
2076
2452
|
onDenied(result, req, res);
|
|
2077
2453
|
return;
|
|
2078
2454
|
}
|
|
2079
|
-
if (!shouldEnforce) {
|
|
2080
|
-
if (config.setPassThroughHeader) {
|
|
2081
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2082
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
2083
|
-
}
|
|
2084
|
-
if (shouldRecordDecisions && sessionId) {
|
|
2085
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
return next();
|
|
2089
|
-
}
|
|
2090
2455
|
if (routeConfig.minTrustScore && result.agent) {
|
|
2091
2456
|
if (result.agent.trustScore < routeConfig.minTrustScore) {
|
|
2092
2457
|
const trustFailure = {
|
|
@@ -2142,14 +2507,6 @@ function createMiddleware(options) {
|
|
|
2142
2507
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
2143
2508
|
correlationId
|
|
2144
2509
|
};
|
|
2145
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
2146
|
-
if (catchUrls) {
|
|
2147
|
-
result.guidance = {
|
|
2148
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
2149
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
2150
|
-
documentationUrl: catchUrls.documentationUrl
|
|
2151
|
-
};
|
|
2152
|
-
}
|
|
2153
2510
|
dedupeFailures(result);
|
|
2154
2511
|
return onDenied(result, req, res);
|
|
2155
2512
|
}
|
|
@@ -2569,6 +2926,20 @@ function createMiddleware2(options) {
|
|
|
2569
2926
|
}
|
|
2570
2927
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
2571
2928
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
2929
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
2930
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
2931
|
+
const passThrough = NextResponse.next();
|
|
2932
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2933
|
+
return passThrough;
|
|
2934
|
+
}
|
|
2935
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
2936
|
+
status: 503,
|
|
2937
|
+
headers: {
|
|
2938
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
2939
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
2940
|
+
}
|
|
2941
|
+
});
|
|
2942
|
+
}
|
|
2572
2943
|
if (!result.policyAllowed || approvalRequired) {
|
|
2573
2944
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
2574
2945
|
return NextResponse.json(
|
|
@@ -2586,13 +2957,12 @@ function createMiddleware2(options) {
|
|
|
2586
2957
|
);
|
|
2587
2958
|
}
|
|
2588
2959
|
if (pathname.startsWith("/api/")) {
|
|
2960
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
2589
2961
|
return NextResponse.json(
|
|
2590
2962
|
{
|
|
2591
2963
|
success: false,
|
|
2592
2964
|
error: {
|
|
2593
|
-
|
|
2594
|
-
// OK, policy denied (update PDLSS / step up).
|
|
2595
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
2965
|
+
code: denyStatus.code,
|
|
2596
2966
|
message: result.denialReasons?.[0] || "Access denied",
|
|
2597
2967
|
guidance: result.guidance,
|
|
2598
2968
|
failures: result.failures,
|
|
@@ -2602,7 +2972,7 @@ function createMiddleware2(options) {
|
|
|
2602
2972
|
}
|
|
2603
2973
|
},
|
|
2604
2974
|
{
|
|
2605
|
-
status:
|
|
2975
|
+
status: denyStatus.status,
|
|
2606
2976
|
...stepUpHoldOutcome && {
|
|
2607
2977
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
2608
2978
|
}
|
|
@@ -2729,6 +3099,12 @@ var VerificationGatewayClient = class {
|
|
|
2729
3099
|
* fulfil against it only once the order is settled / pending_merchant. An
|
|
2730
3100
|
* explicit `buyerEmail` (here or on the bridge handoff body) wins over the
|
|
2731
3101
|
* account default.
|
|
3102
|
+
*
|
|
3103
|
+
* 5.12.0 approval binding: pass the bridge handoff body's `attemptId` (and
|
|
3104
|
+
* the same `checkoutSessionId`) so a step-up approval minted for this cart
|
|
3105
|
+
* is matched to — and only to — this confirm. A held cart's approval is
|
|
3106
|
+
* bound to its checkout session and exact value; a confirm for any other
|
|
3107
|
+
* cart is held for its own approval.
|
|
2732
3108
|
*/
|
|
2733
3109
|
async confirmCheckout(options) {
|
|
2734
3110
|
const credentials = {
|
|
@@ -2748,6 +3124,7 @@ var VerificationGatewayClient = class {
|
|
|
2748
3124
|
commercePhase: "confirm",
|
|
2749
3125
|
checkoutSessionId: options.checkoutSessionId,
|
|
2750
3126
|
checkoutItems: options.checkoutItems,
|
|
3127
|
+
...options.attemptId && { attemptId: options.attemptId },
|
|
2751
3128
|
buyerEmail: options.buyerEmail,
|
|
2752
3129
|
counterpartyUrl: options.counterpartyUrl,
|
|
2753
3130
|
counterpartyType: options.counterpartyType
|
|
@@ -5551,8 +5928,37 @@ function dedupeFailures2(result) {
|
|
|
5551
5928
|
result.denialReasons = [...new Set(result.denialReasons)];
|
|
5552
5929
|
}
|
|
5553
5930
|
}
|
|
5931
|
+
function isReadOnlyMcpMethod(parsed) {
|
|
5932
|
+
return parsed.method !== "tools/call";
|
|
5933
|
+
}
|
|
5554
5934
|
function defaultMcpDenied(result, req, res) {
|
|
5555
5935
|
const id = req.body?.id ?? null;
|
|
5936
|
+
if (isVerificationUnavailable(result)) {
|
|
5937
|
+
const retryAfterSeconds = unavailableRetryAfterSeconds(result);
|
|
5938
|
+
const body = buildVerificationUnavailableBody(result).error;
|
|
5939
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5940
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
5941
|
+
res.setHeader("Retry-After", String(retryAfterSeconds));
|
|
5942
|
+
res.status(503).json({
|
|
5943
|
+
jsonrpc: "2.0",
|
|
5944
|
+
id,
|
|
5945
|
+
error: {
|
|
5946
|
+
// -32003 → verification temporarily unavailable (retryable)
|
|
5947
|
+
code: -32003,
|
|
5948
|
+
message: body.message,
|
|
5949
|
+
data: {
|
|
5950
|
+
code: body.code,
|
|
5951
|
+
recommendation: "error",
|
|
5952
|
+
retryable: true,
|
|
5953
|
+
retryAfterSeconds,
|
|
5954
|
+
guidance: body.guidance,
|
|
5955
|
+
failures: result.failures,
|
|
5956
|
+
correlationId: result.correlationId
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
});
|
|
5960
|
+
return;
|
|
5961
|
+
}
|
|
5556
5962
|
if (result.misconfigured) {
|
|
5557
5963
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5558
5964
|
res.status(503).json({
|
|
@@ -5571,7 +5977,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
5571
5977
|
});
|
|
5572
5978
|
return;
|
|
5573
5979
|
}
|
|
5574
|
-
const status
|
|
5980
|
+
const { status, code: denyCode } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
5575
5981
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5576
5982
|
res.status(status).json({
|
|
5577
5983
|
jsonrpc: "2.0",
|
|
@@ -5581,9 +5987,11 @@ function defaultMcpDenied(result, req, res) {
|
|
|
5581
5987
|
// -32000 → unauthorized (no identity resolved)
|
|
5582
5988
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
5583
5989
|
// -32002 → merchant verification misconfigured (handled above)
|
|
5990
|
+
// -32003 → verification temporarily unavailable (handled above)
|
|
5584
5991
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
5585
5992
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
5586
5993
|
data: {
|
|
5994
|
+
code: denyCode,
|
|
5587
5995
|
guidance: result.guidance,
|
|
5588
5996
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
5589
5997
|
failures: result.failures,
|
|
@@ -5711,6 +6119,9 @@ function createMcpMiddleware(options) {
|
|
|
5711
6119
|
resolved_action: pdlss.action
|
|
5712
6120
|
});
|
|
5713
6121
|
}
|
|
6122
|
+
if (!pdlss.purpose && !shouldEnforce) {
|
|
6123
|
+
return next();
|
|
6124
|
+
}
|
|
5714
6125
|
if (!pdlss.purpose) {
|
|
5715
6126
|
const id = req.body?.id ?? null;
|
|
5716
6127
|
res.status(400).json({
|
|
@@ -5730,6 +6141,9 @@ function createMcpMiddleware(options) {
|
|
|
5730
6141
|
}
|
|
5731
6142
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
5732
6143
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
6144
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
6145
|
+
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
6146
|
+
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
5733
6147
|
const verificationRequest = {
|
|
5734
6148
|
credentials,
|
|
5735
6149
|
purpose: pdlss.purpose,
|
|
@@ -5744,9 +6158,10 @@ function createMcpMiddleware(options) {
|
|
|
5744
6158
|
counterpartyType: config.counterpartyType || "mcp_server",
|
|
5745
6159
|
enableRuntimeChallenge,
|
|
5746
6160
|
callerMetadata: {
|
|
5747
|
-
sourceIp
|
|
6161
|
+
sourceIp,
|
|
5748
6162
|
userAgent: req.headers["user-agent"],
|
|
5749
6163
|
host: req.headers.host,
|
|
6164
|
+
...forwardedForStr && { forwardedFor: forwardedForStr },
|
|
5750
6165
|
// Capture EVERY inbound header (parity with the express adapter —
|
|
5751
6166
|
// MCP-only merchants have no edge to do it upstream) plus the
|
|
5752
6167
|
// CDN-derived connection block. Local string work, secrets
|
|
@@ -5769,6 +6184,21 @@ function createMcpMiddleware(options) {
|
|
|
5769
6184
|
result.stepUpOutcome = outcome;
|
|
5770
6185
|
}
|
|
5771
6186
|
}
|
|
6187
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
6188
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
6189
|
+
return next();
|
|
6190
|
+
}
|
|
6191
|
+
if (!shouldEnforce) {
|
|
6192
|
+
if (config.setPassThroughHeader) {
|
|
6193
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
6194
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
6195
|
+
}
|
|
6196
|
+
if (shouldRecordDecisions && sessionId) {
|
|
6197
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
return next();
|
|
6201
|
+
}
|
|
5772
6202
|
if (!result.policyAllowed) {
|
|
5773
6203
|
if (shouldRecordDecisions && sessionId) {
|
|
5774
6204
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -5788,17 +6218,6 @@ function createMcpMiddleware(options) {
|
|
|
5788
6218
|
onDenied(result, req, res);
|
|
5789
6219
|
return;
|
|
5790
6220
|
}
|
|
5791
|
-
if (!shouldEnforce) {
|
|
5792
|
-
if (config.setPassThroughHeader) {
|
|
5793
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5794
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
5795
|
-
}
|
|
5796
|
-
if (shouldRecordDecisions && sessionId) {
|
|
5797
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
5798
|
-
});
|
|
5799
|
-
}
|
|
5800
|
-
return next();
|
|
5801
|
-
}
|
|
5802
6221
|
if (effectiveAstraId) {
|
|
5803
6222
|
res.setHeader(
|
|
5804
6223
|
MCP_VERIFIED_HOP_HEADER,
|
|
@@ -5839,14 +6258,6 @@ function createMcpMiddleware(options) {
|
|
|
5839
6258
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
5840
6259
|
correlationId
|
|
5841
6260
|
};
|
|
5842
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
5843
|
-
if (catchUrls) {
|
|
5844
|
-
result.guidance = {
|
|
5845
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
5846
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
5847
|
-
documentationUrl: catchUrls.documentationUrl
|
|
5848
|
-
};
|
|
5849
|
-
}
|
|
5850
6261
|
dedupeFailures2(result);
|
|
5851
6262
|
return onDenied(result, req, res);
|
|
5852
6263
|
}
|
|
@@ -9853,6 +10264,7 @@ export {
|
|
|
9853
10264
|
RegistrationTimeoutError,
|
|
9854
10265
|
SDK_USER_AGENT,
|
|
9855
10266
|
TRUST_LEVEL_RANGES,
|
|
10267
|
+
VERIFICATION_STATUS_HEADER,
|
|
9856
10268
|
SDK_VERSION as VERSION,
|
|
9857
10269
|
_resetEdgeConfigCache,
|
|
9858
10270
|
agent_exports as agent,
|
|
@@ -9860,9 +10272,11 @@ export {
|
|
|
9860
10272
|
awaitStepUpApproval,
|
|
9861
10273
|
buildGuidance,
|
|
9862
10274
|
buildSdkObservedMetadata,
|
|
10275
|
+
buildVerificationUnavailableBody,
|
|
9863
10276
|
clearCache,
|
|
9864
10277
|
createMcpMiddleware,
|
|
9865
10278
|
degradeToObserve,
|
|
10279
|
+
denyStatusFor,
|
|
9866
10280
|
deriveConnectionFromHeaders,
|
|
9867
10281
|
detectPlatformFingerprint,
|
|
9868
10282
|
detectRuntime,
|
|
@@ -9879,6 +10293,8 @@ export {
|
|
|
9879
10293
|
getWellKnownUrls,
|
|
9880
10294
|
hasCredentials,
|
|
9881
10295
|
isEdgeConfig,
|
|
10296
|
+
isRejectedIdentityClaim,
|
|
10297
|
+
isVerificationUnavailable,
|
|
9882
10298
|
isVerifiedHopValidFor,
|
|
9883
10299
|
matchEdgePathRule,
|
|
9884
10300
|
matchPlatformSignature,
|
|
@@ -9898,6 +10314,7 @@ export {
|
|
|
9898
10314
|
serializeVerifiedHop,
|
|
9899
10315
|
setMcpMeta,
|
|
9900
10316
|
transport_exports as transport,
|
|
10317
|
+
unavailableRetryAfterSeconds,
|
|
9901
10318
|
validatePolicy as validateLocalPolicy,
|
|
9902
10319
|
verify
|
|
9903
10320
|
};
|