@astrasyncai/verification-gateway 2.4.11 → 2.4.14
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/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +129 -36
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +129 -36
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +26 -4
- package/dist/adapters/mcp.d.ts +26 -4
- package/dist/adapters/mcp.js +94 -28
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +94 -28
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +75 -29
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +75 -29
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +45 -22
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +45 -22
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +29 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +29 -0
- package/dist/agent/index.mjs.map +1 -1
- package/dist/browser/background.js +86 -24
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +86 -24
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +86 -24
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +86 -24
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/{express-C1ePFB7n.d.ts → express-CrfwoNAR.d.ts} +1 -1
- package/dist/{express-4WStX3PV.d.mts → express-ienhAXps.d.mts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +86 -24
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +86 -24
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-ChPX4WHl.d.mts → index-B5e2IDWU.d.mts} +1 -1
- package/dist/{index-CzJMCgEy.d.ts → index-CCdZxvAr.d.ts} +71 -6
- package/dist/{index-D8IEntil.d.mts → index-CEg_WG6y.d.mts} +71 -6
- package/dist/{index-Cjm-zBeZ.d.ts → index-DC5f8eoQ.d.ts} +1 -1
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +344 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +344 -73
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/local-evaluator/evaluator.js +12 -2
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +12 -2
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{nextjs-BIORS__0.d.ts → nextjs-66R1KW8e.d.ts} +1 -1
- package/dist/{nextjs-CjzHdaXA.d.mts → nextjs-DSpisQst.d.mts} +1 -1
- package/dist/{sdk-Chhz-FcT.d.mts → sdk-5U_CBRpr.d.mts} +1 -1
- package/dist/{sdk-CqTEQAc6.d.ts → sdk-Bm8np66n.d.ts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +146 -28
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +146 -28
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-L15pYd2c.d.mts → types-B3USs-Kx.d.mts} +42 -1
- package/dist/{types-L15pYd2c.d.ts → types-B3USs-Kx.d.ts} +42 -1
- package/dist/{types-DNK2BgIf.d.mts → types-CgDCUfo8.d.mts} +1 -1
- package/dist/{types-DoWIuzfj.d.ts → types-R5N4ET6x.d.ts} +1 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ function hasMinimumAccess(actual, required) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// src/version.ts
|
|
21
|
-
var SDK_VERSION = "2.4.
|
|
21
|
+
var SDK_VERSION = "2.4.13";
|
|
22
22
|
|
|
23
23
|
// src/verify.ts
|
|
24
24
|
var DEFAULT_CONFIG = {
|
|
@@ -37,22 +37,27 @@ var DEFAULT_CONFIG = {
|
|
|
37
37
|
};
|
|
38
38
|
var initCheckPerformed = false;
|
|
39
39
|
var deprecationWarningShown = false;
|
|
40
|
-
async function performInitCheck(apiBaseUrl, debug) {
|
|
40
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
41
41
|
initCheckPerformed = true;
|
|
42
42
|
try {
|
|
43
43
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
44
44
|
const response = await fetch(probeUrl, { method: "HEAD" });
|
|
45
45
|
const contentType = response.headers.get("content-type") ?? "";
|
|
46
46
|
if (contentType.startsWith("text/html")) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
|
|
48
|
+
if (strictInit) {
|
|
49
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
50
|
+
}
|
|
51
|
+
console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
|
|
50
52
|
} else if (debug) {
|
|
51
53
|
console.log(
|
|
52
54
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
53
55
|
);
|
|
54
56
|
}
|
|
55
57
|
} catch (err) {
|
|
58
|
+
if (strictInit) {
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
56
61
|
if (debug) {
|
|
57
62
|
console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
|
|
58
63
|
}
|
|
@@ -76,7 +81,23 @@ function getCacheKey(request) {
|
|
|
76
81
|
request.counterpartyType || "",
|
|
77
82
|
request.isSubAgentRequest ? "1" : "0",
|
|
78
83
|
request.parentAgentId || "",
|
|
79
|
-
request.subAgentDepth ?? ""
|
|
84
|
+
request.subAgentDepth ?? "",
|
|
85
|
+
// Audit F-A1-07: previously-missing dimensions that DO affect the
|
|
86
|
+
// backend verdict. Without these, two requests with different
|
|
87
|
+
// durations (e.g. 60s vs 86400s) collided on the same cache key and
|
|
88
|
+
// the shorter-duration allow served the longer-duration request.
|
|
89
|
+
request.durationRequired ?? "",
|
|
90
|
+
request.invocationProtocol || "",
|
|
91
|
+
request.enableRuntimeChallenge ? "1" : "0",
|
|
92
|
+
// callerMetadata fields contribute to risk model; include the ones
|
|
93
|
+
// backend reads. sourceIp/userAgent/forwardedFor change per-request
|
|
94
|
+
// so their inclusion effectively forces a re-check for any varying
|
|
95
|
+
// client (the right behavior — IP-driven anomaly scoring shouldn't
|
|
96
|
+
// be cached across IPs).
|
|
97
|
+
request.callerMetadata?.sourceIp || "",
|
|
98
|
+
request.callerMetadata?.userAgent || "",
|
|
99
|
+
request.callerMetadata?.forwardedFor || "",
|
|
100
|
+
request.callerMetadata?.agentCardUrl || ""
|
|
80
101
|
].join("|");
|
|
81
102
|
}
|
|
82
103
|
function getCachedResult(request) {
|
|
@@ -102,9 +123,13 @@ function cacheResult(request, result, configuredTtl) {
|
|
|
102
123
|
}
|
|
103
124
|
function extractCredentials(headers, query) {
|
|
104
125
|
const credentials = {};
|
|
126
|
+
const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
|
|
105
127
|
const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
|
|
106
128
|
if (astraIdHeader) {
|
|
107
|
-
|
|
129
|
+
const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
|
|
130
|
+
if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
|
|
131
|
+
credentials.astraId = raw;
|
|
132
|
+
}
|
|
108
133
|
}
|
|
109
134
|
const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
|
|
110
135
|
if (apiKeyHeader) {
|
|
@@ -113,9 +138,11 @@ function extractCredentials(headers, query) {
|
|
|
113
138
|
const authHeader = headers["authorization"] || headers["Authorization"];
|
|
114
139
|
if (authHeader) {
|
|
115
140
|
const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
141
|
+
if (typeof authValue === "string") {
|
|
142
|
+
credentials.authorizationHeader = authValue;
|
|
143
|
+
if (authValue.startsWith("Bearer ")) {
|
|
144
|
+
credentials.jwt = authValue.slice(7);
|
|
145
|
+
}
|
|
119
146
|
}
|
|
120
147
|
}
|
|
121
148
|
if (query) {
|
|
@@ -133,7 +160,7 @@ function createGuidanceResponse(config, reason, options = {}) {
|
|
|
133
160
|
const isApiError = source === "api_error";
|
|
134
161
|
const guidance = isApiError ? {
|
|
135
162
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
136
|
-
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
|
|
163
|
+
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
|
|
137
164
|
documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
|
|
138
165
|
steps: [
|
|
139
166
|
"Retry the request with exponential backoff",
|
|
@@ -141,7 +168,7 @@ function createGuidanceResponse(config, reason, options = {}) {
|
|
|
141
168
|
]
|
|
142
169
|
} : {
|
|
143
170
|
message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
|
|
144
|
-
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
|
|
171
|
+
registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
|
|
145
172
|
documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
|
|
146
173
|
steps: [
|
|
147
174
|
"Register for an AstraSync account",
|
|
@@ -218,12 +245,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
218
245
|
"Content-Type": "application/json",
|
|
219
246
|
...config.customHeaders
|
|
220
247
|
};
|
|
221
|
-
if (credentials.authorizationHeader) {
|
|
222
|
-
headers["Authorization"] = credentials.authorizationHeader;
|
|
223
|
-
} else if (config.apiKey) {
|
|
224
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
225
|
-
}
|
|
226
248
|
if (config.apiKey) {
|
|
249
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
227
250
|
headers["X-API-Key"] = config.apiKey;
|
|
228
251
|
}
|
|
229
252
|
try {
|
|
@@ -269,7 +292,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
269
292
|
async function verify(config, request) {
|
|
270
293
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
271
294
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
272
|
-
|
|
295
|
+
if (mergedConfig.strictInit) {
|
|
296
|
+
await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
|
|
297
|
+
} else {
|
|
298
|
+
void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
|
|
299
|
+
}
|
|
273
300
|
}
|
|
274
301
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
275
302
|
deprecationWarningShown = true;
|
|
@@ -323,7 +350,7 @@ async function verify(config, request) {
|
|
|
323
350
|
requiresApproval: apiResponse.access?.requiresApproval,
|
|
324
351
|
guidance: {
|
|
325
352
|
message: apiResponse.access?.reason || "Access denied by PDLSS policy",
|
|
326
|
-
registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
|
|
353
|
+
registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
327
354
|
documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
328
355
|
},
|
|
329
356
|
verifiedAt: /* @__PURE__ */ new Date(),
|
|
@@ -393,13 +420,15 @@ async function verify(config, request) {
|
|
|
393
420
|
result.denialReasons = result.recommendationReasons || [
|
|
394
421
|
"Access denied by AstraSync recommendation"
|
|
395
422
|
];
|
|
396
|
-
|
|
397
|
-
result.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
423
|
+
result.guidance = result.runtimeChallenge ? {
|
|
424
|
+
message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
|
|
425
|
+
registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
426
|
+
documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
|
|
427
|
+
} : {
|
|
428
|
+
message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
|
|
429
|
+
registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
430
|
+
documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
431
|
+
};
|
|
403
432
|
} else if (result.recommendation === "step_up_required") {
|
|
404
433
|
result.requiresStepUp = true;
|
|
405
434
|
if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
|
|
@@ -588,18 +617,40 @@ function defaultExtractPurpose(req) {
|
|
|
588
617
|
return "general";
|
|
589
618
|
}
|
|
590
619
|
}
|
|
591
|
-
function matchRoute(pattern, path) {
|
|
620
|
+
function matchRoute(pattern, path, opts) {
|
|
592
621
|
const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
593
|
-
const
|
|
594
|
-
|
|
622
|
+
const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
|
|
623
|
+
const caseSensitiveResult = caseSensitiveRegex.test(path);
|
|
624
|
+
if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
|
|
625
|
+
return caseSensitiveResult;
|
|
626
|
+
}
|
|
627
|
+
const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
|
|
628
|
+
const caseInsensitiveResult = caseInsensitiveRegex.test(path);
|
|
629
|
+
if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
|
|
630
|
+
console.warn(
|
|
631
|
+
`[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
|
|
595
635
|
}
|
|
596
|
-
function findRouteConfig(routes, path, method) {
|
|
636
|
+
function findRouteConfig(routes, path, method, opts) {
|
|
597
637
|
return routes.find((route) => {
|
|
598
638
|
const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
|
|
599
|
-
const pathMatches = matchRoute(route.pattern, path);
|
|
639
|
+
const pathMatches = matchRoute(route.pattern, path, opts);
|
|
600
640
|
return methodMatches && pathMatches;
|
|
601
641
|
});
|
|
602
642
|
}
|
|
643
|
+
function dedupeFailures(result) {
|
|
644
|
+
if (result.failures && result.failures.length > 1) {
|
|
645
|
+
const seen = /* @__PURE__ */ new Set();
|
|
646
|
+
result.failures = result.failures.filter((f) => {
|
|
647
|
+
const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
|
|
648
|
+
if (seen.has(key)) return false;
|
|
649
|
+
seen.add(key);
|
|
650
|
+
return true;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
}
|
|
603
654
|
function defaultOnDenied(result, _req, res) {
|
|
604
655
|
const statusCode = !result.identityVerified ? 401 : 403;
|
|
605
656
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
@@ -626,6 +677,8 @@ function createMiddleware(options) {
|
|
|
626
677
|
recordDecisions,
|
|
627
678
|
enableRuntimeChallenge = true,
|
|
628
679
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
680
|
+
failOnError = "open",
|
|
681
|
+
caseInsensitiveRouteMatch = false,
|
|
629
682
|
...config
|
|
630
683
|
} = options;
|
|
631
684
|
let cachedRoutes = [];
|
|
@@ -648,7 +701,7 @@ function createMiddleware(options) {
|
|
|
648
701
|
cachedRoutes = fetched;
|
|
649
702
|
lastFetchAt = Date.now();
|
|
650
703
|
if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
|
|
651
|
-
const dashboard = config.dashboardUrl ?? "https://
|
|
704
|
+
const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
|
|
652
705
|
console.warn(
|
|
653
706
|
`[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
|
|
654
707
|
);
|
|
@@ -674,7 +727,12 @@ function createMiddleware(options) {
|
|
|
674
727
|
refreshing = null;
|
|
675
728
|
});
|
|
676
729
|
}
|
|
677
|
-
const
|
|
730
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
|
|
731
|
+
const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
|
|
732
|
+
caseInsensitive: caseInsensitiveRouteMatch,
|
|
733
|
+
logShadowDivergence: true,
|
|
734
|
+
correlationId
|
|
735
|
+
});
|
|
678
736
|
if (!routeConfig) {
|
|
679
737
|
if (config.setPassThroughHeader) {
|
|
680
738
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
@@ -706,7 +764,7 @@ function createMiddleware(options) {
|
|
|
706
764
|
denialReasons: preCheckFailures.map((f) => f.message),
|
|
707
765
|
guidance: {
|
|
708
766
|
message: "Request exceeds counterparty-defined PDLSS limits.",
|
|
709
|
-
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
|
|
767
|
+
registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
|
|
710
768
|
documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
|
|
711
769
|
},
|
|
712
770
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -721,18 +779,27 @@ function createMiddleware(options) {
|
|
|
721
779
|
requestMethod: req.method
|
|
722
780
|
}).catch(() => {
|
|
723
781
|
});
|
|
782
|
+
dedupeFailures(result2);
|
|
724
783
|
onDenied(result2, req, res);
|
|
725
784
|
return;
|
|
726
785
|
}
|
|
727
786
|
const shouldRecordDecisions = recordDecisions !== false;
|
|
728
787
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
729
788
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
730
|
-
const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() :
|
|
789
|
+
const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
790
|
+
if (!req.ip && forwardedForStr) {
|
|
791
|
+
console.warn(
|
|
792
|
+
"[VerificationGateway] req.ip unset \u2014 falling back to leftmost X-Forwarded-For. Configure Express trust proxy correctly to avoid spoofable client IPs in audit logs."
|
|
793
|
+
);
|
|
794
|
+
}
|
|
731
795
|
const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
|
|
732
796
|
const result = await verify(config, {
|
|
733
797
|
credentials,
|
|
734
798
|
purpose,
|
|
735
|
-
|
|
799
|
+
// RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
|
|
800
|
+
// Backend evaluator tolerates either case as defense-in-depth
|
|
801
|
+
// (round-18.6 batch 2); SDK emits canonical form.
|
|
802
|
+
action: req.method.toUpperCase(),
|
|
736
803
|
resource: req.path,
|
|
737
804
|
createSession: shouldRecordDecisions,
|
|
738
805
|
counterpartyUrl,
|
|
@@ -755,6 +822,7 @@ function createMiddleware(options) {
|
|
|
755
822
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
756
823
|
});
|
|
757
824
|
}
|
|
825
|
+
dedupeFailures(result);
|
|
758
826
|
onDenied(result, req, res);
|
|
759
827
|
return;
|
|
760
828
|
}
|
|
@@ -781,6 +849,7 @@ function createMiddleware(options) {
|
|
|
781
849
|
recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
|
|
782
850
|
});
|
|
783
851
|
}
|
|
852
|
+
dedupeFailures(result);
|
|
784
853
|
onDenied(result, req, res);
|
|
785
854
|
return;
|
|
786
855
|
}
|
|
@@ -797,6 +866,7 @@ function createMiddleware(options) {
|
|
|
797
866
|
recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
|
|
798
867
|
});
|
|
799
868
|
}
|
|
869
|
+
dedupeFailures(result);
|
|
800
870
|
onDenied(result, req, res);
|
|
801
871
|
return;
|
|
802
872
|
}
|
|
@@ -811,7 +881,30 @@ function createMiddleware(options) {
|
|
|
811
881
|
}
|
|
812
882
|
next();
|
|
813
883
|
} catch (error) {
|
|
884
|
+
const errorClass = error instanceof Error ? error.constructor.name : typeof error;
|
|
885
|
+
const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
814
886
|
console.error("[VerificationGateway] Middleware error:", error);
|
|
887
|
+
console.warn(
|
|
888
|
+
`[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
|
|
889
|
+
);
|
|
890
|
+
if (failOnError === "closed") {
|
|
891
|
+
const result = {
|
|
892
|
+
identityVerified: false,
|
|
893
|
+
policyAllowed: false,
|
|
894
|
+
accessLevel: "none",
|
|
895
|
+
denialReasons: [`Verification middleware internal error: ${errorClass}`],
|
|
896
|
+
failures: [
|
|
897
|
+
{
|
|
898
|
+
dimension: "middleware.internal_error",
|
|
899
|
+
message: `Middleware threw ${errorClass} \u2014 failing closed`
|
|
900
|
+
}
|
|
901
|
+
],
|
|
902
|
+
verifiedAt: /* @__PURE__ */ new Date(),
|
|
903
|
+
correlationId
|
|
904
|
+
};
|
|
905
|
+
dedupeFailures(result);
|
|
906
|
+
return onDenied(result, req, res);
|
|
907
|
+
}
|
|
815
908
|
next();
|
|
816
909
|
}
|
|
817
910
|
};
|