@astrasyncai/verification-gateway 5.11.0 → 5.13.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 +394 -39
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +394 -39
- 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 +193 -49
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +193 -49
- 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 +387 -22
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +387 -22
- 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 +125 -16
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +125 -16
- 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 +309 -15
- package/dist/bin/astrasync-codex-hook.js +309 -15
- package/dist/bin/astrasync-guard.js +309 -15
- package/dist/bin/astrasync.js +322 -17
- package/dist/browser/background.js +309 -15
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +309 -15
- 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 +309 -15
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +309 -15
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +309 -15
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +309 -15
- 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 +362 -63
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +359 -63
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +309 -15
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +309 -15
- 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 -15
- package/dist/index.d.ts +118 -15
- package/dist/index.js +521 -72
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +515 -72
- 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 -14
- package/dist/verify.d.ts +35 -14
- package/dist/verify.js +165 -25
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +164 -25
- package/dist/verify.mjs.map +1 -1
- package/package.json +3 -2
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var SDK_VERSION = "5.
|
|
2
|
+
var SDK_VERSION = "5.13.0";
|
|
3
3
|
|
|
4
4
|
// src/http.ts
|
|
5
5
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -69,23 +69,58 @@ function prefetchWellKnown(apiBaseUrl) {
|
|
|
69
69
|
inflight.set(apiBaseUrl, promise);
|
|
70
70
|
return promise;
|
|
71
71
|
}
|
|
72
|
-
async function getWellKnownUrls(apiBaseUrl) {
|
|
73
|
-
const entry = cache.get(apiBaseUrl);
|
|
74
|
-
if (entry) {
|
|
75
|
-
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
76
|
-
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return entry.data;
|
|
80
|
-
}
|
|
81
|
-
const pending = inflight.get(apiBaseUrl);
|
|
82
|
-
if (pending) return pending;
|
|
83
|
-
return prefetchWellKnown(apiBaseUrl);
|
|
84
|
-
}
|
|
85
72
|
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
86
73
|
return cache.get(apiBaseUrl)?.data;
|
|
87
74
|
}
|
|
88
75
|
|
|
76
|
+
// src/failure-posture.ts
|
|
77
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
78
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
79
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
80
|
+
"identity.invalid_agent_id",
|
|
81
|
+
"identity.unknown_agent",
|
|
82
|
+
"agent.lookup"
|
|
83
|
+
]);
|
|
84
|
+
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.";
|
|
85
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
86
|
+
"verify_access.api_error",
|
|
87
|
+
"middleware.internal_error"
|
|
88
|
+
]);
|
|
89
|
+
function isVerificationUnavailable(result) {
|
|
90
|
+
if (result.misconfigured) return false;
|
|
91
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
92
|
+
}
|
|
93
|
+
function unavailableRetryAfterSeconds(result) {
|
|
94
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
95
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
96
|
+
}
|
|
97
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
98
|
+
}
|
|
99
|
+
function buildVerificationUnavailableBody(result) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: {
|
|
103
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
104
|
+
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.",
|
|
105
|
+
retryable: true,
|
|
106
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
107
|
+
...result.failures && { failures: result.failures },
|
|
108
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
109
|
+
guidance: {
|
|
110
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function isRejectedIdentityClaim(result) {
|
|
116
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
117
|
+
}
|
|
118
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
119
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
120
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
121
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
122
|
+
}
|
|
123
|
+
|
|
89
124
|
// src/verify.ts
|
|
90
125
|
var DEFAULT_CONFIG = {
|
|
91
126
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -98,7 +133,7 @@ var DEFAULT_CONFIG = {
|
|
|
98
133
|
};
|
|
99
134
|
var initCheckPerformed = false;
|
|
100
135
|
var deprecationWarningShown = false;
|
|
101
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
136
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey, counterparty) {
|
|
102
137
|
initCheckPerformed = true;
|
|
103
138
|
try {
|
|
104
139
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -116,7 +151,8 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
116
151
|
);
|
|
117
152
|
}
|
|
118
153
|
if (apiKey) {
|
|
119
|
-
const
|
|
154
|
+
const q = counterparty?.id ? `?counterpartyId=${encodeURIComponent(counterparty.id)}` : counterparty?.url ? `?counterpartyUrl=${encodeURIComponent(counterparty.url)}` : "";
|
|
155
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check${q}`, {
|
|
120
156
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
121
157
|
});
|
|
122
158
|
if (selfCheck.status === 401) {
|
|
@@ -128,6 +164,13 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
128
164
|
console.error(message);
|
|
129
165
|
} else if (selfCheck.ok) {
|
|
130
166
|
const body = await selfCheck.json().catch(() => ({}));
|
|
167
|
+
if (body.counterparty && body.counterparty.canVerify === false) {
|
|
168
|
+
const message = `[VerificationGateway] the configured AstraSync API key cannot verify for this endpoint (${body.counterparty.message ?? "endpoint not found or owned by another account"}). Every inbound agent will be turned away until the key or counterparty is corrected.`;
|
|
169
|
+
if (strictInit) {
|
|
170
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
171
|
+
}
|
|
172
|
+
console.error(message);
|
|
173
|
+
}
|
|
131
174
|
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
132
175
|
console.warn(
|
|
133
176
|
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
@@ -266,11 +309,11 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
266
309
|
const isMisconfig = source === "merchant_misconfig";
|
|
267
310
|
const urls = options.urls;
|
|
268
311
|
const guidance = isMisconfig ? {
|
|
269
|
-
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or
|
|
312
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, rotated, or belongs to another account). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
270
313
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
271
314
|
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
272
315
|
steps: [
|
|
273
|
-
"Report the failure to the site operator \u2014 their AstraSync API key needs
|
|
316
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs replacing",
|
|
274
317
|
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
275
318
|
]
|
|
276
319
|
} : isApiError ? {
|
|
@@ -331,11 +374,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
331
374
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
332
375
|
// deterministic and intentionally NOT marked retryable.
|
|
333
376
|
...isApiError && { retryable: true },
|
|
377
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
378
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
334
379
|
correlationId: options.correlationId,
|
|
335
380
|
attemptId: options.attemptId,
|
|
336
381
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
337
382
|
};
|
|
338
383
|
}
|
|
384
|
+
async function readJsonSafe(response) {
|
|
385
|
+
try {
|
|
386
|
+
const parsed = await response.json();
|
|
387
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
388
|
+
} catch {
|
|
389
|
+
return {};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function rejectedIdentityGuidance(failures) {
|
|
393
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
394
|
+
if (!f) return void 0;
|
|
395
|
+
if (f.dimension === "agent.lookup") {
|
|
396
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
397
|
+
}
|
|
398
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
399
|
+
}
|
|
400
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
401
|
+
function parseRetryAfterMs(response) {
|
|
402
|
+
const raw = response.headers?.get?.("retry-after");
|
|
403
|
+
if (!raw) return void 0;
|
|
404
|
+
const trimmed = raw.trim();
|
|
405
|
+
let ms;
|
|
406
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
407
|
+
ms = Number(trimmed) * 1e3;
|
|
408
|
+
} else {
|
|
409
|
+
const at = Date.parse(trimmed);
|
|
410
|
+
if (Number.isNaN(at)) return void 0;
|
|
411
|
+
ms = at - Date.now();
|
|
412
|
+
}
|
|
413
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
414
|
+
}
|
|
339
415
|
async function callVerifyAccessAPI(config, request) {
|
|
340
416
|
const { credentials, ...requestData } = request;
|
|
341
417
|
const body = {
|
|
@@ -392,7 +468,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
392
468
|
body: JSON.stringify(body),
|
|
393
469
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
394
470
|
});
|
|
395
|
-
const data = await response
|
|
471
|
+
const data = await readJsonSafe(response);
|
|
472
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
396
473
|
if (response.status === 410) {
|
|
397
474
|
return {
|
|
398
475
|
success: true,
|
|
@@ -430,13 +507,31 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
430
507
|
}
|
|
431
508
|
};
|
|
432
509
|
}
|
|
510
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
511
|
+
return {
|
|
512
|
+
success: true,
|
|
513
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
514
|
+
access: {
|
|
515
|
+
allowed: false,
|
|
516
|
+
reason: "unknown_agent",
|
|
517
|
+
failures: [
|
|
518
|
+
{
|
|
519
|
+
dimension: "identity.unknown_agent",
|
|
520
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
521
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
522
|
+
}
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
433
527
|
if (!response.ok) {
|
|
434
|
-
|
|
528
|
+
const keyMismatch = response.status === 403 && data?.error === "COUNTERPARTY_KEY_MISMATCH";
|
|
529
|
+
if (response.status === 401 || keyMismatch) {
|
|
435
530
|
return {
|
|
436
531
|
success: false,
|
|
437
532
|
error: data.message || data.error || "Integration API key rejected",
|
|
438
533
|
misconfig: true,
|
|
439
|
-
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
534
|
+
misconfigCode: keyMismatch ? "counterparty_key_mismatch" : typeof data?.code === "string" ? data.code : void 0,
|
|
440
535
|
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
441
536
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
442
537
|
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
@@ -444,9 +539,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
444
539
|
}
|
|
445
540
|
return {
|
|
446
541
|
success: false,
|
|
447
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
542
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
448
543
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
449
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
544
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
545
|
+
httpStatus: response.status,
|
|
546
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
450
547
|
};
|
|
451
548
|
}
|
|
452
549
|
return data;
|
|
@@ -467,14 +564,22 @@ async function verify(config, request, options) {
|
|
|
467
564
|
mergedConfig.apiBaseUrl,
|
|
468
565
|
mergedConfig.debug,
|
|
469
566
|
true,
|
|
470
|
-
mergedConfig.apiKey
|
|
567
|
+
mergedConfig.apiKey,
|
|
568
|
+
{
|
|
569
|
+
id: mergedConfig.counterpartyId,
|
|
570
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
571
|
+
}
|
|
471
572
|
);
|
|
472
573
|
} else {
|
|
473
574
|
void performInitCheck(
|
|
474
575
|
mergedConfig.apiBaseUrl,
|
|
475
576
|
mergedConfig.debug,
|
|
476
577
|
false,
|
|
477
|
-
mergedConfig.apiKey
|
|
578
|
+
mergedConfig.apiKey,
|
|
579
|
+
{
|
|
580
|
+
id: mergedConfig.counterpartyId,
|
|
581
|
+
url: mergedConfig.counterpartyUrl ?? request.counterpartyUrl
|
|
582
|
+
}
|
|
478
583
|
);
|
|
479
584
|
}
|
|
480
585
|
}
|
|
@@ -518,12 +623,14 @@ async function verify(config, request, options) {
|
|
|
518
623
|
correlationId: apiResponse.correlationId,
|
|
519
624
|
attemptId: apiResponse.attemptId,
|
|
520
625
|
remediation: apiResponse.remediation,
|
|
626
|
+
httpStatus: apiResponse.httpStatus,
|
|
627
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
521
628
|
urls
|
|
522
629
|
});
|
|
523
630
|
}
|
|
524
631
|
if (!apiResponse.access?.allowed) {
|
|
525
632
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
526
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
633
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
527
634
|
const result2 = {
|
|
528
635
|
identityVerified: idVerifiedFromBackend,
|
|
529
636
|
policyAllowed: false,
|
|
@@ -535,7 +642,10 @@ async function verify(config, request, options) {
|
|
|
535
642
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
536
643
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
537
644
|
guidance: {
|
|
538
|
-
|
|
645
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
646
|
+
// actionable registration guidance on its failure — surface it
|
|
647
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
648
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
539
649
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
540
650
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
541
651
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -635,7 +745,7 @@ async function verify(config, request, options) {
|
|
|
635
745
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
636
746
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
637
747
|
};
|
|
638
|
-
} else if (result.recommendation === "step_up_required") {
|
|
748
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
639
749
|
result.requiresStepUp = true;
|
|
640
750
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
641
751
|
}
|
|
@@ -1109,8 +1219,37 @@ function dedupeFailures(result) {
|
|
|
1109
1219
|
result.denialReasons = [...new Set(result.denialReasons)];
|
|
1110
1220
|
}
|
|
1111
1221
|
}
|
|
1222
|
+
function isReadOnlyMcpMethod(parsed) {
|
|
1223
|
+
return parsed.method !== "tools/call";
|
|
1224
|
+
}
|
|
1112
1225
|
function defaultMcpDenied(result, req, res) {
|
|
1113
1226
|
const id = req.body?.id ?? null;
|
|
1227
|
+
if (isVerificationUnavailable(result)) {
|
|
1228
|
+
const retryAfterSeconds = unavailableRetryAfterSeconds(result);
|
|
1229
|
+
const body = buildVerificationUnavailableBody(result).error;
|
|
1230
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1231
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1232
|
+
res.setHeader("Retry-After", String(retryAfterSeconds));
|
|
1233
|
+
res.status(503).json({
|
|
1234
|
+
jsonrpc: "2.0",
|
|
1235
|
+
id,
|
|
1236
|
+
error: {
|
|
1237
|
+
// -32003 → verification temporarily unavailable (retryable)
|
|
1238
|
+
code: -32003,
|
|
1239
|
+
message: body.message,
|
|
1240
|
+
data: {
|
|
1241
|
+
code: body.code,
|
|
1242
|
+
recommendation: "error",
|
|
1243
|
+
retryable: true,
|
|
1244
|
+
retryAfterSeconds,
|
|
1245
|
+
guidance: body.guidance,
|
|
1246
|
+
failures: result.failures,
|
|
1247
|
+
correlationId: result.correlationId
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1114
1253
|
if (result.misconfigured) {
|
|
1115
1254
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1116
1255
|
res.status(503).json({
|
|
@@ -1129,7 +1268,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1129
1268
|
});
|
|
1130
1269
|
return;
|
|
1131
1270
|
}
|
|
1132
|
-
const status
|
|
1271
|
+
const { status, code: denyCode } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
1133
1272
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1134
1273
|
res.status(status).json({
|
|
1135
1274
|
jsonrpc: "2.0",
|
|
@@ -1139,9 +1278,11 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1139
1278
|
// -32000 → unauthorized (no identity resolved)
|
|
1140
1279
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
1141
1280
|
// -32002 → merchant verification misconfigured (handled above)
|
|
1281
|
+
// -32003 → verification temporarily unavailable (handled above)
|
|
1142
1282
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
1143
1283
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
1144
1284
|
data: {
|
|
1285
|
+
code: denyCode,
|
|
1145
1286
|
guidance: result.guidance,
|
|
1146
1287
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
1147
1288
|
failures: result.failures,
|
|
@@ -1269,6 +1410,9 @@ function createMcpMiddleware(options) {
|
|
|
1269
1410
|
resolved_action: pdlss.action
|
|
1270
1411
|
});
|
|
1271
1412
|
}
|
|
1413
|
+
if (!pdlss.purpose && !shouldEnforce) {
|
|
1414
|
+
return next();
|
|
1415
|
+
}
|
|
1272
1416
|
if (!pdlss.purpose) {
|
|
1273
1417
|
const id = req.body?.id ?? null;
|
|
1274
1418
|
res.status(400).json({
|
|
@@ -1288,6 +1432,9 @@ function createMcpMiddleware(options) {
|
|
|
1288
1432
|
}
|
|
1289
1433
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
1290
1434
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
1435
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
1436
|
+
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
1437
|
+
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1291
1438
|
const verificationRequest = {
|
|
1292
1439
|
credentials,
|
|
1293
1440
|
purpose: pdlss.purpose,
|
|
@@ -1302,9 +1449,10 @@ function createMcpMiddleware(options) {
|
|
|
1302
1449
|
counterpartyType: config.counterpartyType || "mcp_server",
|
|
1303
1450
|
enableRuntimeChallenge,
|
|
1304
1451
|
callerMetadata: {
|
|
1305
|
-
sourceIp
|
|
1452
|
+
sourceIp,
|
|
1306
1453
|
userAgent: req.headers["user-agent"],
|
|
1307
1454
|
host: req.headers.host,
|
|
1455
|
+
...forwardedForStr && { forwardedFor: forwardedForStr },
|
|
1308
1456
|
// Capture EVERY inbound header (parity with the express adapter —
|
|
1309
1457
|
// MCP-only merchants have no edge to do it upstream) plus the
|
|
1310
1458
|
// CDN-derived connection block. Local string work, secrets
|
|
@@ -1327,6 +1475,21 @@ function createMcpMiddleware(options) {
|
|
|
1327
1475
|
result.stepUpOutcome = outcome;
|
|
1328
1476
|
}
|
|
1329
1477
|
}
|
|
1478
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
1479
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1480
|
+
return next();
|
|
1481
|
+
}
|
|
1482
|
+
if (!shouldEnforce) {
|
|
1483
|
+
if (config.setPassThroughHeader) {
|
|
1484
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1485
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1486
|
+
}
|
|
1487
|
+
if (shouldRecordDecisions && sessionId) {
|
|
1488
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
return next();
|
|
1492
|
+
}
|
|
1330
1493
|
if (!result.policyAllowed) {
|
|
1331
1494
|
if (shouldRecordDecisions && sessionId) {
|
|
1332
1495
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -1346,17 +1509,6 @@ function createMcpMiddleware(options) {
|
|
|
1346
1509
|
onDenied(result, req, res);
|
|
1347
1510
|
return;
|
|
1348
1511
|
}
|
|
1349
|
-
if (!shouldEnforce) {
|
|
1350
|
-
if (config.setPassThroughHeader) {
|
|
1351
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1352
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1353
|
-
}
|
|
1354
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1355
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
return next();
|
|
1359
|
-
}
|
|
1360
1512
|
if (effectiveAstraId) {
|
|
1361
1513
|
res.setHeader(
|
|
1362
1514
|
MCP_VERIFIED_HOP_HEADER,
|
|
@@ -1397,14 +1549,6 @@ function createMcpMiddleware(options) {
|
|
|
1397
1549
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
1398
1550
|
correlationId
|
|
1399
1551
|
};
|
|
1400
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
1401
|
-
if (catchUrls) {
|
|
1402
|
-
result.guidance = {
|
|
1403
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
1404
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
1405
|
-
documentationUrl: catchUrls.documentationUrl
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
1552
|
dedupeFailures(result);
|
|
1409
1553
|
return onDenied(result, req, res);
|
|
1410
1554
|
}
|