@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.js
CHANGED
|
@@ -51,6 +51,7 @@ __export(src_exports, {
|
|
|
51
51
|
RegistrationTimeoutError: () => RegistrationTimeoutError,
|
|
52
52
|
SDK_USER_AGENT: () => SDK_USER_AGENT,
|
|
53
53
|
TRUST_LEVEL_RANGES: () => TRUST_LEVEL_RANGES,
|
|
54
|
+
VERIFICATION_STATUS_HEADER: () => VERIFICATION_STATUS_HEADER,
|
|
54
55
|
VERSION: () => SDK_VERSION,
|
|
55
56
|
_resetEdgeConfigCache: () => _resetEdgeConfigCache,
|
|
56
57
|
agent: () => agent_exports,
|
|
@@ -58,9 +59,11 @@ __export(src_exports, {
|
|
|
58
59
|
awaitStepUpApproval: () => awaitStepUpApproval,
|
|
59
60
|
buildGuidance: () => buildGuidance,
|
|
60
61
|
buildSdkObservedMetadata: () => buildSdkObservedMetadata,
|
|
62
|
+
buildVerificationUnavailableBody: () => buildVerificationUnavailableBody,
|
|
61
63
|
clearCache: () => clearCache,
|
|
62
64
|
createMcpMiddleware: () => createMcpMiddleware,
|
|
63
65
|
degradeToObserve: () => degradeToObserve,
|
|
66
|
+
denyStatusFor: () => denyStatusFor,
|
|
64
67
|
deriveConnectionFromHeaders: () => deriveConnectionFromHeaders,
|
|
65
68
|
detectPlatformFingerprint: () => detectPlatformFingerprint,
|
|
66
69
|
detectRuntime: () => detectRuntime,
|
|
@@ -77,6 +80,8 @@ __export(src_exports, {
|
|
|
77
80
|
getWellKnownUrls: () => getWellKnownUrls,
|
|
78
81
|
hasCredentials: () => hasCredentials,
|
|
79
82
|
isEdgeConfig: () => isEdgeConfig,
|
|
83
|
+
isRejectedIdentityClaim: () => isRejectedIdentityClaim,
|
|
84
|
+
isVerificationUnavailable: () => isVerificationUnavailable,
|
|
80
85
|
isVerifiedHopValidFor: () => isVerifiedHopValidFor,
|
|
81
86
|
matchEdgePathRule: () => matchEdgePathRule,
|
|
82
87
|
matchPlatformSignature: () => matchPlatformSignature,
|
|
@@ -96,6 +101,7 @@ __export(src_exports, {
|
|
|
96
101
|
serializeVerifiedHop: () => serializeVerifiedHop,
|
|
97
102
|
setMcpMeta: () => setMcpMeta,
|
|
98
103
|
transport: () => transport_exports,
|
|
104
|
+
unavailableRetryAfterSeconds: () => unavailableRetryAfterSeconds,
|
|
99
105
|
validateLocalPolicy: () => validatePolicy,
|
|
100
106
|
verify: () => verify
|
|
101
107
|
});
|
|
@@ -116,7 +122,7 @@ function getTrustLevel(score) {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
// src/version.ts
|
|
119
|
-
var SDK_VERSION = "5.
|
|
125
|
+
var SDK_VERSION = "5.12.0";
|
|
120
126
|
|
|
121
127
|
// src/http.ts
|
|
122
128
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -195,6 +201,59 @@ function getCachedWellKnownUrls(apiBaseUrl) {
|
|
|
195
201
|
return cache.get(apiBaseUrl)?.data;
|
|
196
202
|
}
|
|
197
203
|
|
|
204
|
+
// src/failure-posture.ts
|
|
205
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
206
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
207
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
208
|
+
"identity.invalid_agent_id",
|
|
209
|
+
"identity.unknown_agent",
|
|
210
|
+
"agent.lookup"
|
|
211
|
+
]);
|
|
212
|
+
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.";
|
|
213
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
214
|
+
"verify_access.api_error",
|
|
215
|
+
"middleware.internal_error"
|
|
216
|
+
]);
|
|
217
|
+
function isVerificationUnavailable(result) {
|
|
218
|
+
if (result.misconfigured) return false;
|
|
219
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
220
|
+
}
|
|
221
|
+
function unavailableRetryAfterSeconds(result) {
|
|
222
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
223
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
224
|
+
}
|
|
225
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
226
|
+
}
|
|
227
|
+
function isAnonymousRead(method, credentials) {
|
|
228
|
+
const m = (method ?? "").toUpperCase();
|
|
229
|
+
if (m !== "GET" && m !== "HEAD") return false;
|
|
230
|
+
return !credentials.astraId && !credentials.jwt;
|
|
231
|
+
}
|
|
232
|
+
function buildVerificationUnavailableBody(result) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: {
|
|
236
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
237
|
+
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.",
|
|
238
|
+
retryable: true,
|
|
239
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
240
|
+
...result.failures && { failures: result.failures },
|
|
241
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
242
|
+
guidance: {
|
|
243
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function isRejectedIdentityClaim(result) {
|
|
249
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
250
|
+
}
|
|
251
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
252
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
253
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
254
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
255
|
+
}
|
|
256
|
+
|
|
198
257
|
// src/verify.ts
|
|
199
258
|
var DEFAULT_CONFIG = {
|
|
200
259
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -446,11 +505,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
446
505
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
447
506
|
// deterministic and intentionally NOT marked retryable.
|
|
448
507
|
...isApiError && { retryable: true },
|
|
508
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
509
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
449
510
|
correlationId: options.correlationId,
|
|
450
511
|
attemptId: options.attemptId,
|
|
451
512
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
452
513
|
};
|
|
453
514
|
}
|
|
515
|
+
async function readJsonSafe(response) {
|
|
516
|
+
try {
|
|
517
|
+
const parsed = await response.json();
|
|
518
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
519
|
+
} catch {
|
|
520
|
+
return {};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function rejectedIdentityGuidance(failures) {
|
|
524
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
525
|
+
if (!f) return void 0;
|
|
526
|
+
if (f.dimension === "agent.lookup") {
|
|
527
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
528
|
+
}
|
|
529
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
530
|
+
}
|
|
531
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
532
|
+
function parseRetryAfterMs(response) {
|
|
533
|
+
const raw = response.headers?.get?.("retry-after");
|
|
534
|
+
if (!raw) return void 0;
|
|
535
|
+
const trimmed = raw.trim();
|
|
536
|
+
let ms;
|
|
537
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
538
|
+
ms = Number(trimmed) * 1e3;
|
|
539
|
+
} else {
|
|
540
|
+
const at = Date.parse(trimmed);
|
|
541
|
+
if (Number.isNaN(at)) return void 0;
|
|
542
|
+
ms = at - Date.now();
|
|
543
|
+
}
|
|
544
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
545
|
+
}
|
|
454
546
|
async function callVerifyAccessAPI(config, request) {
|
|
455
547
|
const { credentials, ...requestData } = request;
|
|
456
548
|
const body = {
|
|
@@ -507,7 +599,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
507
599
|
body: JSON.stringify(body),
|
|
508
600
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
509
601
|
});
|
|
510
|
-
const data = await response
|
|
602
|
+
const data = await readJsonSafe(response);
|
|
603
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
511
604
|
if (response.status === 410) {
|
|
512
605
|
return {
|
|
513
606
|
success: true,
|
|
@@ -545,6 +638,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
545
638
|
}
|
|
546
639
|
};
|
|
547
640
|
}
|
|
641
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
642
|
+
return {
|
|
643
|
+
success: true,
|
|
644
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
645
|
+
access: {
|
|
646
|
+
allowed: false,
|
|
647
|
+
reason: "unknown_agent",
|
|
648
|
+
failures: [
|
|
649
|
+
{
|
|
650
|
+
dimension: "identity.unknown_agent",
|
|
651
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
652
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
653
|
+
}
|
|
654
|
+
]
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
548
658
|
if (!response.ok) {
|
|
549
659
|
if (response.status === 401) {
|
|
550
660
|
return {
|
|
@@ -559,9 +669,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
559
669
|
}
|
|
560
670
|
return {
|
|
561
671
|
success: false,
|
|
562
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
672
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
563
673
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
564
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
674
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
675
|
+
httpStatus: response.status,
|
|
676
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
565
677
|
};
|
|
566
678
|
}
|
|
567
679
|
return data;
|
|
@@ -633,12 +745,14 @@ async function verify(config, request, options) {
|
|
|
633
745
|
correlationId: apiResponse.correlationId,
|
|
634
746
|
attemptId: apiResponse.attemptId,
|
|
635
747
|
remediation: apiResponse.remediation,
|
|
748
|
+
httpStatus: apiResponse.httpStatus,
|
|
749
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
636
750
|
urls
|
|
637
751
|
});
|
|
638
752
|
}
|
|
639
753
|
if (!apiResponse.access?.allowed) {
|
|
640
754
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
641
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
755
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
642
756
|
const result2 = {
|
|
643
757
|
identityVerified: idVerifiedFromBackend,
|
|
644
758
|
policyAllowed: false,
|
|
@@ -650,7 +764,10 @@ async function verify(config, request, options) {
|
|
|
650
764
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
651
765
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
652
766
|
guidance: {
|
|
653
|
-
|
|
767
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
768
|
+
// actionable registration guidance on its failure — surface it
|
|
769
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
770
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
654
771
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
655
772
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
656
773
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -750,7 +867,7 @@ async function verify(config, request, options) {
|
|
|
750
867
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
751
868
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
752
869
|
};
|
|
753
|
-
} else if (result.recommendation === "step_up_required") {
|
|
870
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
754
871
|
result.requiresStepUp = true;
|
|
755
872
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
756
873
|
}
|
|
@@ -773,7 +890,7 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
773
890
|
}).catch(() => {
|
|
774
891
|
});
|
|
775
892
|
}
|
|
776
|
-
async function reportAttempt(config, report) {
|
|
893
|
+
async function reportAttempt(config, report, options = {}) {
|
|
777
894
|
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
778
895
|
const headers = { "Content-Type": "application/json" };
|
|
779
896
|
if (config.apiKey) {
|
|
@@ -784,13 +901,37 @@ async function reportAttempt(config, report) {
|
|
|
784
901
|
...report,
|
|
785
902
|
...!report.counterpartyId && config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
786
903
|
};
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
904
|
+
const url = `${apiBaseUrl}/agents/verify-access/attempt-report`;
|
|
905
|
+
const init = { method: "POST", headers, body: JSON.stringify(body) };
|
|
906
|
+
const fail = (failure) => {
|
|
907
|
+
try {
|
|
908
|
+
options.onFailure?.(failure);
|
|
909
|
+
} catch {
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
let response;
|
|
913
|
+
try {
|
|
914
|
+
response = await sdkFetch(url, init);
|
|
915
|
+
} catch (err) {
|
|
916
|
+
fail({ error: err instanceof Error ? err.message : String(err), retried: false });
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (response.ok) return;
|
|
920
|
+
if (response.status !== 429 && response.status < 500) {
|
|
921
|
+
fail({ status: response.status, retried: false });
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
const delayMs = Math.min(ATTEMPT_REPORT_MAX_RETRY_DELAY_MS, parseRetryAfterMs(response) ?? 500);
|
|
925
|
+
const timer = setTimeout(() => {
|
|
926
|
+
sdkFetch(url, init).then((retry) => {
|
|
927
|
+
if (!retry.ok) fail({ status: retry.status, retried: true });
|
|
928
|
+
}).catch((err) => {
|
|
929
|
+
fail({ error: err instanceof Error ? err.message : String(err), retried: true });
|
|
930
|
+
});
|
|
931
|
+
}, delayMs);
|
|
932
|
+
timer.unref?.();
|
|
793
933
|
}
|
|
934
|
+
var ATTEMPT_REPORT_MAX_RETRY_DELAY_MS = 2e3;
|
|
794
935
|
async function fetchRoutes(config, counterpartyId) {
|
|
795
936
|
if (!counterpartyId) return null;
|
|
796
937
|
const headers = { "Content-Type": "application/json" };
|
|
@@ -820,7 +961,10 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
820
961
|
};
|
|
821
962
|
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
822
963
|
method: "POST",
|
|
823
|
-
headers: {
|
|
964
|
+
headers: {
|
|
965
|
+
"Content-Type": "application/json",
|
|
966
|
+
...config.apiKey && { Authorization: `Bearer ${config.apiKey}` }
|
|
967
|
+
},
|
|
824
968
|
body: JSON.stringify(body)
|
|
825
969
|
}).catch(() => {
|
|
826
970
|
});
|
|
@@ -842,7 +986,11 @@ async function quickVerify(config, credentials) {
|
|
|
842
986
|
return {
|
|
843
987
|
identityVerified: result.identityVerified,
|
|
844
988
|
policyAllowed: result.policyAllowed,
|
|
845
|
-
reason: result.denialReasons?.[0]
|
|
989
|
+
reason: result.denialReasons?.[0],
|
|
990
|
+
...isVerificationUnavailable(result) && {
|
|
991
|
+
unavailable: true,
|
|
992
|
+
retryAfterMs: unavailableRetryAfterSeconds(result) * 1e3
|
|
993
|
+
}
|
|
846
994
|
};
|
|
847
995
|
}
|
|
848
996
|
|
|
@@ -1590,7 +1738,10 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
1590
1738
|
{
|
|
1591
1739
|
vendor: "chatgpt",
|
|
1592
1740
|
displayName: "OpenAI ChatGPT",
|
|
1593
|
-
|
|
1741
|
+
// GPTBot (training crawler) and OAI-SearchBot (ChatGPT search) are
|
|
1742
|
+
// OpenAI's declared crawler UAs — `gpt-?\d` never matched them, so they
|
|
1743
|
+
// classified as unattributed crawlers (round-27.4 W5).
|
|
1744
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i, /\bGPTBot\b/i, /\bOAI-SearchBot\b/i],
|
|
1594
1745
|
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1595
1746
|
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1596
1747
|
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
@@ -1725,6 +1876,215 @@ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
|
1725
1876
|
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1726
1877
|
|
|
1727
1878
|
// src/edge-core/events.ts
|
|
1879
|
+
async function withBudget(promise, ms) {
|
|
1880
|
+
let timer;
|
|
1881
|
+
try {
|
|
1882
|
+
return await Promise.race([
|
|
1883
|
+
promise,
|
|
1884
|
+
new Promise((resolve) => {
|
|
1885
|
+
timer = setTimeout(() => resolve(void 0), ms);
|
|
1886
|
+
})
|
|
1887
|
+
]);
|
|
1888
|
+
} catch {
|
|
1889
|
+
return void 0;
|
|
1890
|
+
} finally {
|
|
1891
|
+
if (timer) clearTimeout(timer);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
var destKeyOf = (d) => `${d.apiBaseUrl}\0${d.apiKey ?? ""}\0${d.counterparty}\0${d.adapter ?? ""}`;
|
|
1895
|
+
var BeaconBuffer = class {
|
|
1896
|
+
constructor(options = {}) {
|
|
1897
|
+
this.queue = [];
|
|
1898
|
+
this.droppedSinceLast = 0;
|
|
1899
|
+
this.droppedTotal = 0;
|
|
1900
|
+
this.deliveredTotal = 0;
|
|
1901
|
+
this.backoffUntil = 0;
|
|
1902
|
+
this.collapse = /* @__PURE__ */ new Map();
|
|
1903
|
+
/** Keys the backend answered 401 for — later batches go unauthenticated. */
|
|
1904
|
+
this.rejectedKeys = /* @__PURE__ */ new Set();
|
|
1905
|
+
this.opts = {
|
|
1906
|
+
maxItems: options.maxItems ?? 1e3,
|
|
1907
|
+
maxBatch: options.maxBatch ?? 100,
|
|
1908
|
+
maxAttempts: options.maxAttempts ?? 3,
|
|
1909
|
+
collapseWindowMs: options.collapseWindowMs ?? 6e4,
|
|
1910
|
+
collapseMaxKeys: options.collapseMaxKeys ?? 1e3,
|
|
1911
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
|
|
1912
|
+
retryBackoffMs: options.retryBackoffMs ?? 1e3,
|
|
1913
|
+
now: options.now ?? Date.now
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Queue one beacon. With a `collapseKey`, a repeat inside the window is
|
|
1918
|
+
* merged into a held aggregate (sent when the window closes) instead of
|
|
1919
|
+
* becoming its own item.
|
|
1920
|
+
*/
|
|
1921
|
+
enqueue(dest, item, collapseKey) {
|
|
1922
|
+
const now = this.opts.now();
|
|
1923
|
+
if (collapseKey) {
|
|
1924
|
+
const entry = this.collapse.get(collapseKey);
|
|
1925
|
+
if (entry && now - entry.firstSeenAt < this.opts.collapseWindowMs) {
|
|
1926
|
+
for (const target of [entry.first, entry.held]) {
|
|
1927
|
+
if (target && !target.inFlight && this.queue.includes(target)) {
|
|
1928
|
+
target.item.repeatCount = (target.item.repeatCount ?? 1) + 1;
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
const held = this.push(
|
|
1933
|
+
dest,
|
|
1934
|
+
{ ...item, repeatCount: 1 },
|
|
1935
|
+
entry.firstSeenAt + this.opts.collapseWindowMs
|
|
1936
|
+
);
|
|
1937
|
+
entry.held = held;
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
this.collapse.delete(collapseKey);
|
|
1941
|
+
if (this.collapse.size >= this.opts.collapseMaxKeys) {
|
|
1942
|
+
const oldest = this.collapse.keys().next().value;
|
|
1943
|
+
if (oldest !== void 0) this.collapse.delete(oldest);
|
|
1944
|
+
}
|
|
1945
|
+
const fresh = { firstSeenAt: now };
|
|
1946
|
+
this.collapse.set(collapseKey, fresh);
|
|
1947
|
+
fresh.first = this.push(dest, item, now);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
this.push(dest, item, now);
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Deliver every eligible queued item. Concurrent callers share ONE
|
|
1954
|
+
* in-flight drain (it survives the caller's budget and a Lambda freeze),
|
|
1955
|
+
* so nothing in flight is ever sent twice; items queued while it runs are
|
|
1956
|
+
* picked up by the same drain.
|
|
1957
|
+
*/
|
|
1958
|
+
flush() {
|
|
1959
|
+
if (!this.inFlight) {
|
|
1960
|
+
this.inFlight = this.drain().finally(() => {
|
|
1961
|
+
this.inFlight = void 0;
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
return this.inFlight;
|
|
1965
|
+
}
|
|
1966
|
+
/** Counters for tests and diagnostics. */
|
|
1967
|
+
stats() {
|
|
1968
|
+
return {
|
|
1969
|
+
queueDepth: this.queue.length,
|
|
1970
|
+
droppedSinceLast: this.droppedSinceLast,
|
|
1971
|
+
droppedTotal: this.droppedTotal,
|
|
1972
|
+
deliveredTotal: this.deliveredTotal,
|
|
1973
|
+
flushing: this.inFlight !== void 0
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
push(dest, item, eligibleAt) {
|
|
1977
|
+
while (this.queue.length >= this.opts.maxItems) this.dropOldest();
|
|
1978
|
+
const entry = {
|
|
1979
|
+
dest,
|
|
1980
|
+
destKey: destKeyOf(dest),
|
|
1981
|
+
item,
|
|
1982
|
+
attempts: 0,
|
|
1983
|
+
eligibleAt,
|
|
1984
|
+
inFlight: false
|
|
1985
|
+
};
|
|
1986
|
+
this.queue.push(entry);
|
|
1987
|
+
return entry;
|
|
1988
|
+
}
|
|
1989
|
+
dropOldest() {
|
|
1990
|
+
if (this.queue.shift()) this.countDropped(1);
|
|
1991
|
+
}
|
|
1992
|
+
countDropped(n) {
|
|
1993
|
+
this.droppedSinceLast += n;
|
|
1994
|
+
this.droppedTotal += n;
|
|
1995
|
+
}
|
|
1996
|
+
async drain() {
|
|
1997
|
+
for (; ; ) {
|
|
1998
|
+
const now = this.opts.now();
|
|
1999
|
+
if (now < this.backoffUntil) return;
|
|
2000
|
+
const batch = this.takeBatch(now);
|
|
2001
|
+
if (batch.length === 0) return;
|
|
2002
|
+
const ok = await this.send(batch);
|
|
2003
|
+
if (!ok) return;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/** Oldest eligible item + up to maxBatch-1 more for the same destination. */
|
|
2007
|
+
takeBatch(now) {
|
|
2008
|
+
const first = this.queue.find((q) => q.eligibleAt <= now);
|
|
2009
|
+
if (!first) return [];
|
|
2010
|
+
const batch = [];
|
|
2011
|
+
const rest = [];
|
|
2012
|
+
for (const q of this.queue) {
|
|
2013
|
+
if (batch.length < this.opts.maxBatch && q.destKey === first.destKey && q.eligibleAt <= now) {
|
|
2014
|
+
q.inFlight = true;
|
|
2015
|
+
batch.push(q);
|
|
2016
|
+
} else {
|
|
2017
|
+
rest.push(q);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
this.queue = rest;
|
|
2021
|
+
return batch;
|
|
2022
|
+
}
|
|
2023
|
+
/** POST one batch. Returns false when the drain should pause (backoff). */
|
|
2024
|
+
async send(batch) {
|
|
2025
|
+
const dest = batch[0].dest;
|
|
2026
|
+
const useKey = dest.apiKey && !this.rejectedKeys.has(dest.apiKey) ? dest.apiKey : void 0;
|
|
2027
|
+
const reportedDropped = this.droppedSinceLast;
|
|
2028
|
+
const meta = {
|
|
2029
|
+
droppedSinceLast: reportedDropped,
|
|
2030
|
+
queueDepth: this.queue.length,
|
|
2031
|
+
adapter: dest.adapter ?? "edge-core",
|
|
2032
|
+
version: SDK_VERSION
|
|
2033
|
+
};
|
|
2034
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
2035
|
+
const response = await withBudget(
|
|
2036
|
+
sdkFetch(`${dest.apiBaseUrl}/verification-activity/edge-events`, {
|
|
2037
|
+
method: "POST",
|
|
2038
|
+
headers: {
|
|
2039
|
+
"Content-Type": "application/json",
|
|
2040
|
+
...useKey && { Authorization: `Bearer ${useKey}` }
|
|
2041
|
+
},
|
|
2042
|
+
body: JSON.stringify({ events: batch.map((q) => q.item), meta }),
|
|
2043
|
+
...controller && { signal: controller.signal }
|
|
2044
|
+
}),
|
|
2045
|
+
this.opts.requestTimeoutMs
|
|
2046
|
+
);
|
|
2047
|
+
if (!response) controller?.abort();
|
|
2048
|
+
const status = response?.status;
|
|
2049
|
+
for (const q of batch) q.inFlight = false;
|
|
2050
|
+
if (status !== void 0 && status >= 200 && status < 300) {
|
|
2051
|
+
this.droppedSinceLast = Math.max(0, this.droppedSinceLast - reportedDropped);
|
|
2052
|
+
this.deliveredTotal += batch.length;
|
|
2053
|
+
return true;
|
|
2054
|
+
}
|
|
2055
|
+
if (status === 401 && useKey) {
|
|
2056
|
+
this.rejectedKeys.add(useKey);
|
|
2057
|
+
this.requeue(batch, false);
|
|
2058
|
+
return true;
|
|
2059
|
+
}
|
|
2060
|
+
if (status === 429) {
|
|
2061
|
+
const retryAfter = Number(response?.headers.get("retry-after"));
|
|
2062
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 120) * 1e3 : 3e4;
|
|
2063
|
+
this.backoffUntil = this.opts.now() + waitMs;
|
|
2064
|
+
this.requeue(batch, false);
|
|
2065
|
+
return false;
|
|
2066
|
+
}
|
|
2067
|
+
if (status !== void 0 && status >= 400 && status < 500) {
|
|
2068
|
+
this.countDropped(batch.length);
|
|
2069
|
+
return true;
|
|
2070
|
+
}
|
|
2071
|
+
this.backoffUntil = this.opts.now() + this.opts.retryBackoffMs;
|
|
2072
|
+
this.requeue(batch, true);
|
|
2073
|
+
return false;
|
|
2074
|
+
}
|
|
2075
|
+
/** Put a failed batch back at the head of the queue (order preserved). */
|
|
2076
|
+
requeue(batch, countAttempt) {
|
|
2077
|
+
const keep = [];
|
|
2078
|
+
for (const q of batch) {
|
|
2079
|
+
if (countAttempt) q.attempts += 1;
|
|
2080
|
+
if (q.attempts >= this.opts.maxAttempts) this.countDropped(1);
|
|
2081
|
+
else keep.push(q);
|
|
2082
|
+
}
|
|
2083
|
+
this.queue = [...keep, ...this.queue];
|
|
2084
|
+
while (this.queue.length > this.opts.maxItems) this.dropOldest();
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
var beaconBuffer = new BeaconBuffer();
|
|
1728
2088
|
function sampled(rate, random = Math.random) {
|
|
1729
2089
|
if (rate <= 0) return false;
|
|
1730
2090
|
if (rate >= 1) return true;
|
|
@@ -1901,12 +2261,19 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1901
2261
|
});
|
|
1902
2262
|
return;
|
|
1903
2263
|
}
|
|
1904
|
-
|
|
2264
|
+
if (isVerificationUnavailable(result)) {
|
|
2265
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2266
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2267
|
+
res.setHeader("Retry-After", String(unavailableRetryAfterSeconds(result)));
|
|
2268
|
+
res.status(503).json(buildVerificationUnavailableBody(result));
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const { status: statusCode, code } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
1905
2272
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1906
2273
|
res.status(statusCode).json({
|
|
1907
2274
|
success: false,
|
|
1908
2275
|
error: {
|
|
1909
|
-
code
|
|
2276
|
+
code,
|
|
1910
2277
|
message: result.denialReasons?.[0] || "Access denied",
|
|
1911
2278
|
guidance: result.guidance,
|
|
1912
2279
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
@@ -2154,6 +2521,21 @@ function createMiddleware(options) {
|
|
|
2154
2521
|
result.stepUpOutcome = outcome;
|
|
2155
2522
|
}
|
|
2156
2523
|
}
|
|
2524
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isAnonymousRead(req.method, credentials))) {
|
|
2525
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
2526
|
+
return next();
|
|
2527
|
+
}
|
|
2528
|
+
if (!shouldEnforce) {
|
|
2529
|
+
if (config.setPassThroughHeader) {
|
|
2530
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2531
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
2532
|
+
}
|
|
2533
|
+
if (shouldRecordDecisions && sessionId) {
|
|
2534
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
return next();
|
|
2538
|
+
}
|
|
2157
2539
|
if (!result.policyAllowed) {
|
|
2158
2540
|
if (shouldRecordDecisions && sessionId) {
|
|
2159
2541
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -2173,17 +2555,6 @@ function createMiddleware(options) {
|
|
|
2173
2555
|
onDenied(result, req, res);
|
|
2174
2556
|
return;
|
|
2175
2557
|
}
|
|
2176
|
-
if (!shouldEnforce) {
|
|
2177
|
-
if (config.setPassThroughHeader) {
|
|
2178
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
2179
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
2180
|
-
}
|
|
2181
|
-
if (shouldRecordDecisions && sessionId) {
|
|
2182
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
return next();
|
|
2186
|
-
}
|
|
2187
2558
|
if (routeConfig.minTrustScore && result.agent) {
|
|
2188
2559
|
if (result.agent.trustScore < routeConfig.minTrustScore) {
|
|
2189
2560
|
const trustFailure = {
|
|
@@ -2239,14 +2610,6 @@ function createMiddleware(options) {
|
|
|
2239
2610
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
2240
2611
|
correlationId
|
|
2241
2612
|
};
|
|
2242
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
2243
|
-
if (catchUrls) {
|
|
2244
|
-
result.guidance = {
|
|
2245
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
2246
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
2247
|
-
documentationUrl: catchUrls.documentationUrl
|
|
2248
|
-
};
|
|
2249
|
-
}
|
|
2250
2613
|
dedupeFailures(result);
|
|
2251
2614
|
return onDenied(result, req, res);
|
|
2252
2615
|
}
|
|
@@ -2666,6 +3029,20 @@ function createMiddleware2(options) {
|
|
|
2666
3029
|
}
|
|
2667
3030
|
const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
|
|
2668
3031
|
if (approvalRequired) annotateApprovalRequired(result);
|
|
3032
|
+
if (!result.policyAllowed && isVerificationUnavailable(result)) {
|
|
3033
|
+
if (isAnonymousRead(request.method, credentials)) {
|
|
3034
|
+
const passThrough = NextResponse.next();
|
|
3035
|
+
passThrough.headers.set(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
3036
|
+
return passThrough;
|
|
3037
|
+
}
|
|
3038
|
+
return NextResponse.json(buildVerificationUnavailableBody(result), {
|
|
3039
|
+
status: 503,
|
|
3040
|
+
headers: {
|
|
3041
|
+
"Retry-After": String(unavailableRetryAfterSeconds(result)),
|
|
3042
|
+
[VERIFICATION_STATUS_HEADER]: "unavailable"
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
}
|
|
2669
3046
|
if (!result.policyAllowed || approvalRequired) {
|
|
2670
3047
|
if (result.misconfigured && pathname.startsWith("/api/")) {
|
|
2671
3048
|
return NextResponse.json(
|
|
@@ -2683,13 +3060,12 @@ function createMiddleware2(options) {
|
|
|
2683
3060
|
);
|
|
2684
3061
|
}
|
|
2685
3062
|
if (pathname.startsWith("/api/")) {
|
|
3063
|
+
const denyStatus = denyStatusFor(result, "POLICY_DENIED");
|
|
2686
3064
|
return NextResponse.json(
|
|
2687
3065
|
{
|
|
2688
3066
|
success: false,
|
|
2689
3067
|
error: {
|
|
2690
|
-
|
|
2691
|
-
// OK, policy denied (update PDLSS / step up).
|
|
2692
|
-
code: !result.identityVerified ? "UNAUTHORIZED" : "POLICY_DENIED",
|
|
3068
|
+
code: denyStatus.code,
|
|
2693
3069
|
message: result.denialReasons?.[0] || "Access denied",
|
|
2694
3070
|
guidance: result.guidance,
|
|
2695
3071
|
failures: result.failures,
|
|
@@ -2699,7 +3075,7 @@ function createMiddleware2(options) {
|
|
|
2699
3075
|
}
|
|
2700
3076
|
},
|
|
2701
3077
|
{
|
|
2702
|
-
status:
|
|
3078
|
+
status: denyStatus.status,
|
|
2703
3079
|
...stepUpHoldOutcome && {
|
|
2704
3080
|
headers: { "X-Astra-Step-Up": stepUpHoldOutcome }
|
|
2705
3081
|
}
|
|
@@ -2826,6 +3202,12 @@ var VerificationGatewayClient = class {
|
|
|
2826
3202
|
* fulfil against it only once the order is settled / pending_merchant. An
|
|
2827
3203
|
* explicit `buyerEmail` (here or on the bridge handoff body) wins over the
|
|
2828
3204
|
* account default.
|
|
3205
|
+
*
|
|
3206
|
+
* 5.12.0 approval binding: pass the bridge handoff body's `attemptId` (and
|
|
3207
|
+
* the same `checkoutSessionId`) so a step-up approval minted for this cart
|
|
3208
|
+
* is matched to — and only to — this confirm. A held cart's approval is
|
|
3209
|
+
* bound to its checkout session and exact value; a confirm for any other
|
|
3210
|
+
* cart is held for its own approval.
|
|
2829
3211
|
*/
|
|
2830
3212
|
async confirmCheckout(options) {
|
|
2831
3213
|
const credentials = {
|
|
@@ -2845,6 +3227,7 @@ var VerificationGatewayClient = class {
|
|
|
2845
3227
|
commercePhase: "confirm",
|
|
2846
3228
|
checkoutSessionId: options.checkoutSessionId,
|
|
2847
3229
|
checkoutItems: options.checkoutItems,
|
|
3230
|
+
...options.attemptId && { attemptId: options.attemptId },
|
|
2848
3231
|
buyerEmail: options.buyerEmail,
|
|
2849
3232
|
counterpartyUrl: options.counterpartyUrl,
|
|
2850
3233
|
counterpartyType: options.counterpartyType
|
|
@@ -5645,8 +6028,37 @@ function dedupeFailures2(result) {
|
|
|
5645
6028
|
result.denialReasons = [...new Set(result.denialReasons)];
|
|
5646
6029
|
}
|
|
5647
6030
|
}
|
|
6031
|
+
function isReadOnlyMcpMethod(parsed) {
|
|
6032
|
+
return parsed.method !== "tools/call";
|
|
6033
|
+
}
|
|
5648
6034
|
function defaultMcpDenied(result, req, res) {
|
|
5649
6035
|
const id = req.body?.id ?? null;
|
|
6036
|
+
if (isVerificationUnavailable(result)) {
|
|
6037
|
+
const retryAfterSeconds = unavailableRetryAfterSeconds(result);
|
|
6038
|
+
const body = buildVerificationUnavailableBody(result).error;
|
|
6039
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
6040
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
6041
|
+
res.setHeader("Retry-After", String(retryAfterSeconds));
|
|
6042
|
+
res.status(503).json({
|
|
6043
|
+
jsonrpc: "2.0",
|
|
6044
|
+
id,
|
|
6045
|
+
error: {
|
|
6046
|
+
// -32003 → verification temporarily unavailable (retryable)
|
|
6047
|
+
code: -32003,
|
|
6048
|
+
message: body.message,
|
|
6049
|
+
data: {
|
|
6050
|
+
code: body.code,
|
|
6051
|
+
recommendation: "error",
|
|
6052
|
+
retryable: true,
|
|
6053
|
+
retryAfterSeconds,
|
|
6054
|
+
guidance: body.guidance,
|
|
6055
|
+
failures: result.failures,
|
|
6056
|
+
correlationId: result.correlationId
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
});
|
|
6060
|
+
return;
|
|
6061
|
+
}
|
|
5650
6062
|
if (result.misconfigured) {
|
|
5651
6063
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5652
6064
|
res.status(503).json({
|
|
@@ -5665,7 +6077,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
5665
6077
|
});
|
|
5666
6078
|
return;
|
|
5667
6079
|
}
|
|
5668
|
-
const status
|
|
6080
|
+
const { status, code: denyCode } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
5669
6081
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5670
6082
|
res.status(status).json({
|
|
5671
6083
|
jsonrpc: "2.0",
|
|
@@ -5675,9 +6087,11 @@ function defaultMcpDenied(result, req, res) {
|
|
|
5675
6087
|
// -32000 → unauthorized (no identity resolved)
|
|
5676
6088
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
5677
6089
|
// -32002 → merchant verification misconfigured (handled above)
|
|
6090
|
+
// -32003 → verification temporarily unavailable (handled above)
|
|
5678
6091
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
5679
6092
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
5680
6093
|
data: {
|
|
6094
|
+
code: denyCode,
|
|
5681
6095
|
guidance: result.guidance,
|
|
5682
6096
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
5683
6097
|
failures: result.failures,
|
|
@@ -5805,6 +6219,9 @@ function createMcpMiddleware(options) {
|
|
|
5805
6219
|
resolved_action: pdlss.action
|
|
5806
6220
|
});
|
|
5807
6221
|
}
|
|
6222
|
+
if (!pdlss.purpose && !shouldEnforce) {
|
|
6223
|
+
return next();
|
|
6224
|
+
}
|
|
5808
6225
|
if (!pdlss.purpose) {
|
|
5809
6226
|
const id = req.body?.id ?? null;
|
|
5810
6227
|
res.status(400).json({
|
|
@@ -5824,6 +6241,9 @@ function createMcpMiddleware(options) {
|
|
|
5824
6241
|
}
|
|
5825
6242
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
5826
6243
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
6244
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
6245
|
+
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
6246
|
+
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
5827
6247
|
const verificationRequest = {
|
|
5828
6248
|
credentials,
|
|
5829
6249
|
purpose: pdlss.purpose,
|
|
@@ -5838,9 +6258,10 @@ function createMcpMiddleware(options) {
|
|
|
5838
6258
|
counterpartyType: config.counterpartyType || "mcp_server",
|
|
5839
6259
|
enableRuntimeChallenge,
|
|
5840
6260
|
callerMetadata: {
|
|
5841
|
-
sourceIp
|
|
6261
|
+
sourceIp,
|
|
5842
6262
|
userAgent: req.headers["user-agent"],
|
|
5843
6263
|
host: req.headers.host,
|
|
6264
|
+
...forwardedForStr && { forwardedFor: forwardedForStr },
|
|
5844
6265
|
// Capture EVERY inbound header (parity with the express adapter —
|
|
5845
6266
|
// MCP-only merchants have no edge to do it upstream) plus the
|
|
5846
6267
|
// CDN-derived connection block. Local string work, secrets
|
|
@@ -5863,6 +6284,21 @@ function createMcpMiddleware(options) {
|
|
|
5863
6284
|
result.stepUpOutcome = outcome;
|
|
5864
6285
|
}
|
|
5865
6286
|
}
|
|
6287
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
6288
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
6289
|
+
return next();
|
|
6290
|
+
}
|
|
6291
|
+
if (!shouldEnforce) {
|
|
6292
|
+
if (config.setPassThroughHeader) {
|
|
6293
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
6294
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
6295
|
+
}
|
|
6296
|
+
if (shouldRecordDecisions && sessionId) {
|
|
6297
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
6298
|
+
});
|
|
6299
|
+
}
|
|
6300
|
+
return next();
|
|
6301
|
+
}
|
|
5866
6302
|
if (!result.policyAllowed) {
|
|
5867
6303
|
if (shouldRecordDecisions && sessionId) {
|
|
5868
6304
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -5882,17 +6318,6 @@ function createMcpMiddleware(options) {
|
|
|
5882
6318
|
onDenied(result, req, res);
|
|
5883
6319
|
return;
|
|
5884
6320
|
}
|
|
5885
|
-
if (!shouldEnforce) {
|
|
5886
|
-
if (config.setPassThroughHeader) {
|
|
5887
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
5888
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
5889
|
-
}
|
|
5890
|
-
if (shouldRecordDecisions && sessionId) {
|
|
5891
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
5892
|
-
});
|
|
5893
|
-
}
|
|
5894
|
-
return next();
|
|
5895
|
-
}
|
|
5896
6321
|
if (effectiveAstraId) {
|
|
5897
6322
|
res.setHeader(
|
|
5898
6323
|
MCP_VERIFIED_HOP_HEADER,
|
|
@@ -5933,14 +6358,6 @@ function createMcpMiddleware(options) {
|
|
|
5933
6358
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
5934
6359
|
correlationId
|
|
5935
6360
|
};
|
|
5936
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
5937
|
-
if (catchUrls) {
|
|
5938
|
-
result.guidance = {
|
|
5939
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
5940
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
5941
|
-
documentationUrl: catchUrls.documentationUrl
|
|
5942
|
-
};
|
|
5943
|
-
}
|
|
5944
6361
|
dedupeFailures2(result);
|
|
5945
6362
|
return onDenied(result, req, res);
|
|
5946
6363
|
}
|
|
@@ -9948,6 +10365,7 @@ function parseYaml(yamlContent) {
|
|
|
9948
10365
|
RegistrationTimeoutError,
|
|
9949
10366
|
SDK_USER_AGENT,
|
|
9950
10367
|
TRUST_LEVEL_RANGES,
|
|
10368
|
+
VERIFICATION_STATUS_HEADER,
|
|
9951
10369
|
VERSION,
|
|
9952
10370
|
_resetEdgeConfigCache,
|
|
9953
10371
|
agent,
|
|
@@ -9955,9 +10373,11 @@ function parseYaml(yamlContent) {
|
|
|
9955
10373
|
awaitStepUpApproval,
|
|
9956
10374
|
buildGuidance,
|
|
9957
10375
|
buildSdkObservedMetadata,
|
|
10376
|
+
buildVerificationUnavailableBody,
|
|
9958
10377
|
clearCache,
|
|
9959
10378
|
createMcpMiddleware,
|
|
9960
10379
|
degradeToObserve,
|
|
10380
|
+
denyStatusFor,
|
|
9961
10381
|
deriveConnectionFromHeaders,
|
|
9962
10382
|
detectPlatformFingerprint,
|
|
9963
10383
|
detectRuntime,
|
|
@@ -9974,6 +10394,8 @@ function parseYaml(yamlContent) {
|
|
|
9974
10394
|
getWellKnownUrls,
|
|
9975
10395
|
hasCredentials,
|
|
9976
10396
|
isEdgeConfig,
|
|
10397
|
+
isRejectedIdentityClaim,
|
|
10398
|
+
isVerificationUnavailable,
|
|
9977
10399
|
isVerifiedHopValidFor,
|
|
9978
10400
|
matchEdgePathRule,
|
|
9979
10401
|
matchPlatformSignature,
|
|
@@ -9993,6 +10415,7 @@ function parseYaml(yamlContent) {
|
|
|
9993
10415
|
serializeVerifiedHop,
|
|
9994
10416
|
setMcpMeta,
|
|
9995
10417
|
transport,
|
|
10418
|
+
unavailableRetryAfterSeconds,
|
|
9996
10419
|
validateLocalPolicy,
|
|
9997
10420
|
verify
|
|
9998
10421
|
});
|