@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/adapters/mcp.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler } from 'express';
|
|
2
|
-
import { d as VerificationResult, G as GatewayConfig } from '../types-
|
|
2
|
+
import { d as VerificationResult, G as GatewayConfig } from '../types-BCkwBA84.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* X-Astra-Verified-Hop — cross-hop verify-access dedupe marker.
|
|
@@ -384,6 +384,10 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
384
384
|
* record what WOULD be denied with `failOnError: 'closed'` so merchants
|
|
385
385
|
* can grep correlationId for impact analysis during the observation
|
|
386
386
|
* window. Default flips to `'closed'` in a follow-up release.
|
|
387
|
+
*
|
|
388
|
+
* 5.12.0: `'closed'` answers the retryable verification-unavailable
|
|
389
|
+
* JSON-RPC error (HTTP 503 + Retry-After, code -32003) — never an
|
|
390
|
+
* identity-shaped 401.
|
|
387
391
|
*/
|
|
388
392
|
failOnError?: 'open' | 'closed';
|
|
389
393
|
}
|
package/dist/adapters/mcp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Request, Response, RequestHandler } from 'express';
|
|
2
|
-
import { d as VerificationResult, G as GatewayConfig } from '../types-
|
|
2
|
+
import { d as VerificationResult, G as GatewayConfig } from '../types-BCkwBA84.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* X-Astra-Verified-Hop — cross-hop verify-access dedupe marker.
|
|
@@ -384,6 +384,10 @@ interface McpMiddlewareOptions extends GatewayConfig {
|
|
|
384
384
|
* record what WOULD be denied with `failOnError: 'closed'` so merchants
|
|
385
385
|
* can grep correlationId for impact analysis during the observation
|
|
386
386
|
* window. Default flips to `'closed'` in a follow-up release.
|
|
387
|
+
*
|
|
388
|
+
* 5.12.0: `'closed'` answers the retryable verification-unavailable
|
|
389
|
+
* JSON-RPC error (HTTP 503 + Retry-After, code -32003) — never an
|
|
390
|
+
* identity-shaped 401.
|
|
387
391
|
*/
|
|
388
392
|
failOnError?: 'open' | 'closed';
|
|
389
393
|
}
|
package/dist/adapters/mcp.js
CHANGED
|
@@ -32,7 +32,7 @@ __export(mcp_exports, {
|
|
|
32
32
|
module.exports = __toCommonJS(mcp_exports);
|
|
33
33
|
|
|
34
34
|
// src/version.ts
|
|
35
|
-
var SDK_VERSION = "5.
|
|
35
|
+
var SDK_VERSION = "5.12.0";
|
|
36
36
|
|
|
37
37
|
// src/http.ts
|
|
38
38
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -102,23 +102,58 @@ function prefetchWellKnown(apiBaseUrl) {
|
|
|
102
102
|
inflight.set(apiBaseUrl, promise);
|
|
103
103
|
return promise;
|
|
104
104
|
}
|
|
105
|
-
async function getWellKnownUrls(apiBaseUrl) {
|
|
106
|
-
const entry = cache.get(apiBaseUrl);
|
|
107
|
-
if (entry) {
|
|
108
|
-
if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
|
|
109
|
-
prefetchWellKnown(apiBaseUrl).catch(() => {
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return entry.data;
|
|
113
|
-
}
|
|
114
|
-
const pending = inflight.get(apiBaseUrl);
|
|
115
|
-
if (pending) return pending;
|
|
116
|
-
return prefetchWellKnown(apiBaseUrl);
|
|
117
|
-
}
|
|
118
105
|
function getCachedWellKnownUrls(apiBaseUrl) {
|
|
119
106
|
return cache.get(apiBaseUrl)?.data;
|
|
120
107
|
}
|
|
121
108
|
|
|
109
|
+
// src/failure-posture.ts
|
|
110
|
+
var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
|
|
111
|
+
var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
|
|
112
|
+
var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
113
|
+
"identity.invalid_agent_id",
|
|
114
|
+
"identity.unknown_agent",
|
|
115
|
+
"agent.lookup"
|
|
116
|
+
]);
|
|
117
|
+
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.";
|
|
118
|
+
var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
119
|
+
"verify_access.api_error",
|
|
120
|
+
"middleware.internal_error"
|
|
121
|
+
]);
|
|
122
|
+
function isVerificationUnavailable(result) {
|
|
123
|
+
if (result.misconfigured) return false;
|
|
124
|
+
return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
|
|
125
|
+
}
|
|
126
|
+
function unavailableRetryAfterSeconds(result) {
|
|
127
|
+
if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
|
|
128
|
+
return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
|
|
129
|
+
}
|
|
130
|
+
return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
|
|
131
|
+
}
|
|
132
|
+
function buildVerificationUnavailableBody(result) {
|
|
133
|
+
return {
|
|
134
|
+
success: false,
|
|
135
|
+
error: {
|
|
136
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
137
|
+
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.",
|
|
138
|
+
retryable: true,
|
|
139
|
+
retryAfterSeconds: unavailableRetryAfterSeconds(result),
|
|
140
|
+
...result.failures && { failures: result.failures },
|
|
141
|
+
...result.correlationId && { correlationId: result.correlationId },
|
|
142
|
+
guidance: {
|
|
143
|
+
message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function isRejectedIdentityClaim(result) {
|
|
149
|
+
return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
150
|
+
}
|
|
151
|
+
function denyStatusFor(result, policyDeniedCode) {
|
|
152
|
+
if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
|
|
153
|
+
if (result.identityVerified) return { status: 403, code: policyDeniedCode };
|
|
154
|
+
return { status: 401, code: "UNAUTHORIZED" };
|
|
155
|
+
}
|
|
156
|
+
|
|
122
157
|
// src/verify.ts
|
|
123
158
|
var DEFAULT_CONFIG = {
|
|
124
159
|
apiBaseUrl: "https://astrasync.ai/api",
|
|
@@ -364,11 +399,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
364
399
|
// coercing the absent recommendation to a policy 'deny'. Misconfig is
|
|
365
400
|
// deterministic and intentionally NOT marked retryable.
|
|
366
401
|
...isApiError && { retryable: true },
|
|
402
|
+
...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
|
|
403
|
+
...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
|
|
367
404
|
correlationId: options.correlationId,
|
|
368
405
|
attemptId: options.attemptId,
|
|
369
406
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
370
407
|
};
|
|
371
408
|
}
|
|
409
|
+
async function readJsonSafe(response) {
|
|
410
|
+
try {
|
|
411
|
+
const parsed = await response.json();
|
|
412
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
413
|
+
} catch {
|
|
414
|
+
return {};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function rejectedIdentityGuidance(failures) {
|
|
418
|
+
const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
|
|
419
|
+
if (!f) return void 0;
|
|
420
|
+
if (f.dimension === "agent.lookup") {
|
|
421
|
+
return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
|
|
422
|
+
}
|
|
423
|
+
return f.guidance ? `${f.message} ${f.guidance}` : f.message;
|
|
424
|
+
}
|
|
425
|
+
var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
|
|
426
|
+
function parseRetryAfterMs(response) {
|
|
427
|
+
const raw = response.headers?.get?.("retry-after");
|
|
428
|
+
if (!raw) return void 0;
|
|
429
|
+
const trimmed = raw.trim();
|
|
430
|
+
let ms;
|
|
431
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
432
|
+
ms = Number(trimmed) * 1e3;
|
|
433
|
+
} else {
|
|
434
|
+
const at = Date.parse(trimmed);
|
|
435
|
+
if (Number.isNaN(at)) return void 0;
|
|
436
|
+
ms = at - Date.now();
|
|
437
|
+
}
|
|
438
|
+
return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
|
|
439
|
+
}
|
|
372
440
|
async function callVerifyAccessAPI(config, request) {
|
|
373
441
|
const { credentials, ...requestData } = request;
|
|
374
442
|
const body = {
|
|
@@ -425,7 +493,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
425
493
|
body: JSON.stringify(body),
|
|
426
494
|
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
427
495
|
});
|
|
428
|
-
const data = await response
|
|
496
|
+
const data = await readJsonSafe(response);
|
|
497
|
+
const retryAfterMs = parseRetryAfterMs(response);
|
|
429
498
|
if (response.status === 410) {
|
|
430
499
|
return {
|
|
431
500
|
success: true,
|
|
@@ -463,6 +532,23 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
463
532
|
}
|
|
464
533
|
};
|
|
465
534
|
}
|
|
535
|
+
if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
|
|
536
|
+
return {
|
|
537
|
+
success: true,
|
|
538
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
539
|
+
access: {
|
|
540
|
+
allowed: false,
|
|
541
|
+
reason: "unknown_agent",
|
|
542
|
+
failures: [
|
|
543
|
+
{
|
|
544
|
+
dimension: "identity.unknown_agent",
|
|
545
|
+
message: "The presented agent identifier is not registered with AstraSync.",
|
|
546
|
+
guidance: UNKNOWN_AGENT_GUIDANCE
|
|
547
|
+
}
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
466
552
|
if (!response.ok) {
|
|
467
553
|
if (response.status === 401) {
|
|
468
554
|
return {
|
|
@@ -477,9 +563,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
477
563
|
}
|
|
478
564
|
return {
|
|
479
565
|
success: false,
|
|
480
|
-
error: data.message || data.error || `API returned ${response.status}`,
|
|
566
|
+
error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
|
|
481
567
|
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
482
|
-
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
568
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
|
|
569
|
+
httpStatus: response.status,
|
|
570
|
+
...retryAfterMs !== void 0 && { retryAfterMs }
|
|
483
571
|
};
|
|
484
572
|
}
|
|
485
573
|
return data;
|
|
@@ -551,12 +639,14 @@ async function verify(config, request, options) {
|
|
|
551
639
|
correlationId: apiResponse.correlationId,
|
|
552
640
|
attemptId: apiResponse.attemptId,
|
|
553
641
|
remediation: apiResponse.remediation,
|
|
642
|
+
httpStatus: apiResponse.httpStatus,
|
|
643
|
+
retryAfterMs: apiResponse.retryAfterMs,
|
|
554
644
|
urls
|
|
555
645
|
});
|
|
556
646
|
}
|
|
557
647
|
if (!apiResponse.access?.allowed) {
|
|
558
648
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
559
|
-
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
|
|
649
|
+
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
560
650
|
const result2 = {
|
|
561
651
|
identityVerified: idVerifiedFromBackend,
|
|
562
652
|
policyAllowed: false,
|
|
@@ -568,7 +658,10 @@ async function verify(config, request, options) {
|
|
|
568
658
|
requiresStepUp: apiResponse.access?.requiresStepUp,
|
|
569
659
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
570
660
|
guidance: {
|
|
571
|
-
|
|
661
|
+
// 5.12.0: a presented-but-rejected id (malformed / unknown) carries
|
|
662
|
+
// actionable registration guidance on its failure — surface it
|
|
663
|
+
// instead of the bare machine reason ('unknown_agent').
|
|
664
|
+
message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
|
|
572
665
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
573
666
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
574
667
|
// 5.1.0: anonymous callers land on this branch — steer MCP-hosted
|
|
@@ -668,7 +761,7 @@ async function verify(config, request, options) {
|
|
|
668
761
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
669
762
|
documentationUrl: urls?.documentationUrl ?? ""
|
|
670
763
|
};
|
|
671
|
-
} else if (result.recommendation === "step_up_required") {
|
|
764
|
+
} else if (result.recommendation === "step_up_required" && result.identityVerified) {
|
|
672
765
|
result.requiresStepUp = true;
|
|
673
766
|
result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
|
|
674
767
|
}
|
|
@@ -1142,8 +1235,37 @@ function dedupeFailures(result) {
|
|
|
1142
1235
|
result.denialReasons = [...new Set(result.denialReasons)];
|
|
1143
1236
|
}
|
|
1144
1237
|
}
|
|
1238
|
+
function isReadOnlyMcpMethod(parsed) {
|
|
1239
|
+
return parsed.method !== "tools/call";
|
|
1240
|
+
}
|
|
1145
1241
|
function defaultMcpDenied(result, req, res) {
|
|
1146
1242
|
const id = req.body?.id ?? null;
|
|
1243
|
+
if (isVerificationUnavailable(result)) {
|
|
1244
|
+
const retryAfterSeconds = unavailableRetryAfterSeconds(result);
|
|
1245
|
+
const body = buildVerificationUnavailableBody(result).error;
|
|
1246
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1247
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1248
|
+
res.setHeader("Retry-After", String(retryAfterSeconds));
|
|
1249
|
+
res.status(503).json({
|
|
1250
|
+
jsonrpc: "2.0",
|
|
1251
|
+
id,
|
|
1252
|
+
error: {
|
|
1253
|
+
// -32003 → verification temporarily unavailable (retryable)
|
|
1254
|
+
code: -32003,
|
|
1255
|
+
message: body.message,
|
|
1256
|
+
data: {
|
|
1257
|
+
code: body.code,
|
|
1258
|
+
recommendation: "error",
|
|
1259
|
+
retryable: true,
|
|
1260
|
+
retryAfterSeconds,
|
|
1261
|
+
guidance: body.guidance,
|
|
1262
|
+
failures: result.failures,
|
|
1263
|
+
correlationId: result.correlationId
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1147
1269
|
if (result.misconfigured) {
|
|
1148
1270
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1149
1271
|
res.status(503).json({
|
|
@@ -1162,7 +1284,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1162
1284
|
});
|
|
1163
1285
|
return;
|
|
1164
1286
|
}
|
|
1165
|
-
const status
|
|
1287
|
+
const { status, code: denyCode } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
|
|
1166
1288
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1167
1289
|
res.status(status).json({
|
|
1168
1290
|
jsonrpc: "2.0",
|
|
@@ -1172,9 +1294,11 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1172
1294
|
// -32000 → unauthorized (no identity resolved)
|
|
1173
1295
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
1174
1296
|
// -32002 → merchant verification misconfigured (handled above)
|
|
1297
|
+
// -32003 → verification temporarily unavailable (handled above)
|
|
1175
1298
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
1176
1299
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
1177
1300
|
data: {
|
|
1301
|
+
code: denyCode,
|
|
1178
1302
|
guidance: result.guidance,
|
|
1179
1303
|
// Round-10: aggregated per-dimension detail + correlation handle.
|
|
1180
1304
|
failures: result.failures,
|
|
@@ -1302,6 +1426,9 @@ function createMcpMiddleware(options) {
|
|
|
1302
1426
|
resolved_action: pdlss.action
|
|
1303
1427
|
});
|
|
1304
1428
|
}
|
|
1429
|
+
if (!pdlss.purpose && !shouldEnforce) {
|
|
1430
|
+
return next();
|
|
1431
|
+
}
|
|
1305
1432
|
if (!pdlss.purpose) {
|
|
1306
1433
|
const id = req.body?.id ?? null;
|
|
1307
1434
|
res.status(400).json({
|
|
@@ -1321,6 +1448,9 @@ function createMcpMiddleware(options) {
|
|
|
1321
1448
|
}
|
|
1322
1449
|
const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
|
|
1323
1450
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
1451
|
+
const forwardedFor = req.headers["x-forwarded-for"];
|
|
1452
|
+
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
1453
|
+
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1324
1454
|
const verificationRequest = {
|
|
1325
1455
|
credentials,
|
|
1326
1456
|
purpose: pdlss.purpose,
|
|
@@ -1335,9 +1465,10 @@ function createMcpMiddleware(options) {
|
|
|
1335
1465
|
counterpartyType: config.counterpartyType || "mcp_server",
|
|
1336
1466
|
enableRuntimeChallenge,
|
|
1337
1467
|
callerMetadata: {
|
|
1338
|
-
sourceIp
|
|
1468
|
+
sourceIp,
|
|
1339
1469
|
userAgent: req.headers["user-agent"],
|
|
1340
1470
|
host: req.headers.host,
|
|
1471
|
+
...forwardedForStr && { forwardedFor: forwardedForStr },
|
|
1341
1472
|
// Capture EVERY inbound header (parity with the express adapter —
|
|
1342
1473
|
// MCP-only merchants have no edge to do it upstream) plus the
|
|
1343
1474
|
// CDN-derived connection block. Local string work, secrets
|
|
@@ -1360,6 +1491,21 @@ function createMcpMiddleware(options) {
|
|
|
1360
1491
|
result.stepUpOutcome = outcome;
|
|
1361
1492
|
}
|
|
1362
1493
|
}
|
|
1494
|
+
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
1495
|
+
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1496
|
+
return next();
|
|
1497
|
+
}
|
|
1498
|
+
if (!shouldEnforce) {
|
|
1499
|
+
if (config.setPassThroughHeader) {
|
|
1500
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1501
|
+
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1502
|
+
}
|
|
1503
|
+
if (shouldRecordDecisions && sessionId) {
|
|
1504
|
+
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
return next();
|
|
1508
|
+
}
|
|
1363
1509
|
if (!result.policyAllowed) {
|
|
1364
1510
|
if (shouldRecordDecisions && sessionId) {
|
|
1365
1511
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
@@ -1379,17 +1525,6 @@ function createMcpMiddleware(options) {
|
|
|
1379
1525
|
onDenied(result, req, res);
|
|
1380
1526
|
return;
|
|
1381
1527
|
}
|
|
1382
|
-
if (!shouldEnforce) {
|
|
1383
|
-
if (config.setPassThroughHeader) {
|
|
1384
|
-
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1385
|
-
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1386
|
-
}
|
|
1387
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1388
|
-
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
return next();
|
|
1392
|
-
}
|
|
1393
1528
|
if (effectiveAstraId) {
|
|
1394
1529
|
res.setHeader(
|
|
1395
1530
|
MCP_VERIFIED_HOP_HEADER,
|
|
@@ -1430,14 +1565,6 @@ function createMcpMiddleware(options) {
|
|
|
1430
1565
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
1431
1566
|
correlationId
|
|
1432
1567
|
};
|
|
1433
|
-
const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
1434
|
-
if (catchUrls) {
|
|
1435
|
-
result.guidance = {
|
|
1436
|
-
message: `Middleware threw ${errorClass} \u2014 failing closed`,
|
|
1437
|
-
registrationUrl: catchUrls.registrationUrl,
|
|
1438
|
-
documentationUrl: catchUrls.documentationUrl
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
1441
1568
|
dedupeFailures(result);
|
|
1442
1569
|
return onDenied(result, req, res);
|
|
1443
1570
|
}
|