@astrasyncai/verification-gateway 3.12.0 → 4.1.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 +106 -29
- 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 +482 -22
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +482 -22
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +3 -3
- package/dist/adapters/http-pdlss.d.ts +3 -3
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +56 -36
- package/dist/adapters/mcp.d.ts +56 -36
- package/dist/adapters/mcp.js +309 -10
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +309 -10
- 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 +518 -25
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +518 -25
- 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 +72 -9
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +72 -9
- 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.map +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +83 -20
- package/dist/bin/astrasync-codex-hook.js +83 -20
- package/dist/bin/astrasync-guard.js +83 -20
- package/dist/bin/astrasync.js +150 -33
- package/dist/browser/background.js +83 -20
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +83 -20
- 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/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/codex/index.d.mts +3 -3
- package/dist/codex/index.d.ts +3 -3
- package/dist/codex/index.js +83 -20
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +83 -20
- package/dist/codex/index.mjs.map +1 -1
- 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 +83 -20
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +83 -20
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +22 -7
- package/dist/edge-config.d.ts +22 -7
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +425 -0
- package/dist/edge-core/index.d.ts +425 -0
- package/dist/edge-core/index.js +1482 -0
- package/dist/edge-core/index.js.map +1 -0
- package/dist/edge-core/index.mjs +1437 -0
- package/dist/edge-core/index.mjs.map +1 -0
- package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
- package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +83 -20
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +83 -20
- 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-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
- package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
- package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
- package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
- package/dist/index.d.mts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +595 -272
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +592 -272
- 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/metadata-capture.d.mts +63 -4
- package/dist/metadata-capture.d.ts +63 -4
- package/dist/metadata-capture.js +67 -0
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +64 -0
- package/dist/metadata-capture.mjs.map +1 -1
- package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
- package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
- package/dist/platform-signatures.d.mts +5 -6
- package/dist/platform-signatures.d.ts +5 -6
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.d.mts +10 -11
- package/dist/registration/index.d.ts +10 -11
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
- package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
- package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
- package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
- package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
- package/dist/ui/index.d.mts +11 -7
- package/dist/ui/index.d.ts +11 -7
- package/dist/ui/index.js +13 -9
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +10 -8
- package/dist/ui/index.mjs.map +1 -1
- package/dist/verify.d.mts +11 -8
- package/dist/verify.d.ts +11 -8
- package/dist/verify.js +76 -8
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +75 -8
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -7,7 +7,7 @@ function getTrustLevel(score) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
// src/version.ts
|
|
10
|
-
var SDK_VERSION = "
|
|
10
|
+
var SDK_VERSION = "4.1.0";
|
|
11
11
|
|
|
12
12
|
// src/well-known.ts
|
|
13
13
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -85,7 +85,7 @@ var DEFAULT_CONFIG = {
|
|
|
85
85
|
};
|
|
86
86
|
var initCheckPerformed = false;
|
|
87
87
|
var deprecationWarningShown = false;
|
|
88
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
88
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
89
89
|
initCheckPerformed = true;
|
|
90
90
|
try {
|
|
91
91
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -102,6 +102,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
102
102
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
103
103
|
);
|
|
104
104
|
}
|
|
105
|
+
if (apiKey) {
|
|
106
|
+
const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
107
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
108
|
+
});
|
|
109
|
+
if (selfCheck.status === 401) {
|
|
110
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
111
|
+
const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
|
|
112
|
+
if (strictInit) {
|
|
113
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
114
|
+
}
|
|
115
|
+
console.error(message);
|
|
116
|
+
} else if (selfCheck.ok) {
|
|
117
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
118
|
+
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
119
|
+
console.warn(
|
|
120
|
+
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
121
|
+
);
|
|
122
|
+
} else if (debug) {
|
|
123
|
+
console.log("[VerificationGateway] API key self-check passed");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
105
127
|
} catch (err) {
|
|
106
128
|
if (strictInit) {
|
|
107
129
|
throw err;
|
|
@@ -213,8 +235,17 @@ function extractCredentials(headers, query) {
|
|
|
213
235
|
function createGuidanceResponse(_config, reason, options = {}) {
|
|
214
236
|
const source = options.source ?? "no_credentials";
|
|
215
237
|
const isApiError = source === "api_error";
|
|
238
|
+
const isMisconfig = source === "merchant_misconfig";
|
|
216
239
|
const urls = options.urls;
|
|
217
|
-
const guidance =
|
|
240
|
+
const guidance = isMisconfig ? {
|
|
241
|
+
message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
|
|
242
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
243
|
+
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
244
|
+
steps: [
|
|
245
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
|
|
246
|
+
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
247
|
+
]
|
|
248
|
+
} : isApiError ? {
|
|
218
249
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
219
250
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
220
251
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
@@ -252,13 +283,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
252
283
|
// partners (and their custom onDenied handlers) can branch on
|
|
253
284
|
// dimension. Without this, the synthesised stub was indistinguishable
|
|
254
285
|
// from a real policy deny.
|
|
255
|
-
failures:
|
|
286
|
+
failures: isMisconfig ? [
|
|
287
|
+
{
|
|
288
|
+
dimension: "verify_access.merchant_misconfigured",
|
|
289
|
+
message: reason ?? "The integration API key was rejected by AstraSync",
|
|
290
|
+
guidance: guidance.message
|
|
291
|
+
}
|
|
292
|
+
] : isApiError ? [
|
|
256
293
|
{
|
|
257
294
|
dimension: "verify_access.api_error",
|
|
258
295
|
message: reason ?? "Verification temporarily unavailable",
|
|
259
296
|
guidance: guidance.message
|
|
260
297
|
}
|
|
261
298
|
] : void 0,
|
|
299
|
+
// 4.0.0: typed marker so adapters render the misconfig envelope (503 +
|
|
300
|
+
// MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
|
|
301
|
+
...isMisconfig && { misconfigured: true },
|
|
262
302
|
correlationId: options.correlationId,
|
|
263
303
|
attemptId: options.attemptId,
|
|
264
304
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -313,7 +353,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
313
353
|
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
314
354
|
method: "POST",
|
|
315
355
|
headers,
|
|
316
|
-
body: JSON.stringify(body)
|
|
356
|
+
body: JSON.stringify(body),
|
|
357
|
+
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
317
358
|
});
|
|
318
359
|
const data = await response.json();
|
|
319
360
|
if (response.status === 410) {
|
|
@@ -337,6 +378,17 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
337
378
|
};
|
|
338
379
|
}
|
|
339
380
|
if (!response.ok) {
|
|
381
|
+
if (response.status === 401) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
error: data.message || data.error || "Integration API key rejected",
|
|
385
|
+
misconfig: true,
|
|
386
|
+
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
387
|
+
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
388
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
389
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
390
|
+
};
|
|
391
|
+
}
|
|
340
392
|
return {
|
|
341
393
|
success: false,
|
|
342
394
|
error: data.message || data.error || `API returned ${response.status}`,
|
|
@@ -358,9 +410,19 @@ async function verify(config, request, options) {
|
|
|
358
410
|
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
359
411
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
360
412
|
if (mergedConfig.strictInit) {
|
|
361
|
-
await performInitCheck(
|
|
413
|
+
await performInitCheck(
|
|
414
|
+
mergedConfig.apiBaseUrl,
|
|
415
|
+
mergedConfig.debug,
|
|
416
|
+
true,
|
|
417
|
+
mergedConfig.apiKey
|
|
418
|
+
);
|
|
362
419
|
} else {
|
|
363
|
-
void performInitCheck(
|
|
420
|
+
void performInitCheck(
|
|
421
|
+
mergedConfig.apiBaseUrl,
|
|
422
|
+
mergedConfig.debug,
|
|
423
|
+
false,
|
|
424
|
+
mergedConfig.apiKey
|
|
425
|
+
);
|
|
364
426
|
}
|
|
365
427
|
}
|
|
366
428
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
@@ -399,9 +461,10 @@ async function verify(config, request, options) {
|
|
|
399
461
|
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
400
462
|
if (!apiResponse.success) {
|
|
401
463
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
402
|
-
source: "api_error",
|
|
464
|
+
source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
|
|
403
465
|
correlationId: apiResponse.correlationId,
|
|
404
466
|
attemptId: apiResponse.attemptId,
|
|
467
|
+
remediation: apiResponse.remediation,
|
|
405
468
|
urls
|
|
406
469
|
});
|
|
407
470
|
}
|
|
@@ -538,6 +601,205 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
538
601
|
});
|
|
539
602
|
}
|
|
540
603
|
|
|
604
|
+
// src/metadata-capture.ts
|
|
605
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
606
|
+
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
607
|
+
"cookie",
|
|
608
|
+
"set-cookie",
|
|
609
|
+
"x-csrf-token",
|
|
610
|
+
"proxy-authorization",
|
|
611
|
+
"x-astrasync-signature",
|
|
612
|
+
"x-astrasync-secret",
|
|
613
|
+
"x-hub-signature",
|
|
614
|
+
"x-hub-signature-256"
|
|
615
|
+
]);
|
|
616
|
+
var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
|
|
617
|
+
var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
618
|
+
"authorization",
|
|
619
|
+
"x-api-key",
|
|
620
|
+
"x-goog-api-key",
|
|
621
|
+
"api-key"
|
|
622
|
+
]);
|
|
623
|
+
function isSignatureHeader(name) {
|
|
624
|
+
return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
|
|
625
|
+
}
|
|
626
|
+
var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
627
|
+
"openai-organization",
|
|
628
|
+
"openai-project",
|
|
629
|
+
"openai-version",
|
|
630
|
+
"openai-processing-ms",
|
|
631
|
+
"anthropic-version",
|
|
632
|
+
"anthropic-beta",
|
|
633
|
+
"x-goog-user-project",
|
|
634
|
+
"x-goog-api-client",
|
|
635
|
+
"user-agent",
|
|
636
|
+
"x-agent-card-url",
|
|
637
|
+
"x-agent-provider"
|
|
638
|
+
]);
|
|
639
|
+
var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
|
|
640
|
+
var KEY_FORMAT_PREFIXES = [
|
|
641
|
+
"sk-ant-api03",
|
|
642
|
+
"sk-ant-api",
|
|
643
|
+
"sk-ant",
|
|
644
|
+
"sk-proj",
|
|
645
|
+
"sk-svcacct",
|
|
646
|
+
"sk-or-v1",
|
|
647
|
+
"sk-or",
|
|
648
|
+
"sk-lf",
|
|
649
|
+
"sk-",
|
|
650
|
+
"gsk_",
|
|
651
|
+
"aiza",
|
|
652
|
+
"ya29",
|
|
653
|
+
"ghp_",
|
|
654
|
+
"gho_",
|
|
655
|
+
"github_pat_",
|
|
656
|
+
"xai-",
|
|
657
|
+
"pplx-",
|
|
658
|
+
"r8_",
|
|
659
|
+
"hf_",
|
|
660
|
+
"astra-",
|
|
661
|
+
"astrae-"
|
|
662
|
+
];
|
|
663
|
+
var MAX_HEADERS = 64;
|
|
664
|
+
var MAX_HEADER_VALUE_BYTES = 1024;
|
|
665
|
+
var MAX_TOTAL_BYTES = 16 * 1024;
|
|
666
|
+
function byteLength(s) {
|
|
667
|
+
let bytes = 0;
|
|
668
|
+
for (let i = 0; i < s.length; i++) {
|
|
669
|
+
const code = s.charCodeAt(i);
|
|
670
|
+
if (code < 128) bytes += 1;
|
|
671
|
+
else if (code < 2048) bytes += 2;
|
|
672
|
+
else if (code >= 55296 && code <= 56319) {
|
|
673
|
+
bytes += 4;
|
|
674
|
+
i++;
|
|
675
|
+
} else bytes += 3;
|
|
676
|
+
}
|
|
677
|
+
return bytes;
|
|
678
|
+
}
|
|
679
|
+
function extractApiKeyFormat(rawValue) {
|
|
680
|
+
if (!rawValue) return void 0;
|
|
681
|
+
let value = rawValue.trim();
|
|
682
|
+
const spaceIdx = value.indexOf(" ");
|
|
683
|
+
if (spaceIdx > 0) {
|
|
684
|
+
const scheme = value.slice(0, spaceIdx).toLowerCase();
|
|
685
|
+
if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
|
|
686
|
+
value = value.slice(spaceIdx + 1).trim();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const lower = value.toLowerCase();
|
|
690
|
+
for (const prefix of KEY_FORMAT_PREFIXES) {
|
|
691
|
+
if (lower.startsWith(prefix)) {
|
|
692
|
+
return prefix;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
function isSecretHeader(name) {
|
|
698
|
+
if (SECRET_HEADER_NAMES.has(name)) return true;
|
|
699
|
+
if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
|
|
700
|
+
if (isSignatureHeader(name)) return true;
|
|
701
|
+
return false;
|
|
702
|
+
}
|
|
703
|
+
function isPlatformHeader(name) {
|
|
704
|
+
if (PLATFORM_HEADER_NAMES.has(name)) return true;
|
|
705
|
+
return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
|
|
706
|
+
}
|
|
707
|
+
function sanitizeHeaders(raw) {
|
|
708
|
+
const headers = {};
|
|
709
|
+
const platformHeaders = {};
|
|
710
|
+
let apiKeyFormat;
|
|
711
|
+
let totalBytes = 0;
|
|
712
|
+
let count = 0;
|
|
713
|
+
if (!raw) return { headers };
|
|
714
|
+
for (const rawName of Object.keys(raw)) {
|
|
715
|
+
if (count >= MAX_HEADERS) break;
|
|
716
|
+
const name = rawName.toLowerCase();
|
|
717
|
+
const rawValue = raw[rawName];
|
|
718
|
+
if (rawValue === void 0) continue;
|
|
719
|
+
const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
|
|
720
|
+
if (CREDENTIAL_HEADER_NAMES.has(name)) {
|
|
721
|
+
const fmt = extractApiKeyFormat(joined);
|
|
722
|
+
if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (isSecretHeader(name)) continue;
|
|
726
|
+
let value = joined;
|
|
727
|
+
if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
|
|
728
|
+
value = value.slice(0, MAX_HEADER_VALUE_BYTES);
|
|
729
|
+
}
|
|
730
|
+
const entryBytes = byteLength(name) + byteLength(value);
|
|
731
|
+
if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
|
|
732
|
+
totalBytes += entryBytes;
|
|
733
|
+
count++;
|
|
734
|
+
headers[name] = value;
|
|
735
|
+
if (isPlatformHeader(name)) platformHeaders[name] = value;
|
|
736
|
+
}
|
|
737
|
+
const result = { headers };
|
|
738
|
+
if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
|
|
739
|
+
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
740
|
+
return result;
|
|
741
|
+
}
|
|
742
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
743
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
744
|
+
[
|
|
745
|
+
"country",
|
|
746
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
747
|
+
],
|
|
748
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
749
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
750
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
751
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
752
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
753
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
754
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
755
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
756
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
757
|
+
["ja4", ["cf-ja4"]]
|
|
758
|
+
];
|
|
759
|
+
function deriveConnectionFromHeaders(headers) {
|
|
760
|
+
if (!headers) return void 0;
|
|
761
|
+
const lower = {};
|
|
762
|
+
for (const name of Object.keys(headers)) {
|
|
763
|
+
const value = headers[name];
|
|
764
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
765
|
+
}
|
|
766
|
+
const connection = {};
|
|
767
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
768
|
+
for (const source of sources) {
|
|
769
|
+
const value = lower[source]?.trim();
|
|
770
|
+
if (value) {
|
|
771
|
+
connection[key] = value;
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!connection.ip) {
|
|
777
|
+
const xff = lower["x-forwarded-for"];
|
|
778
|
+
const client = xff?.split(",")[0]?.trim();
|
|
779
|
+
if (client) connection.ip = client;
|
|
780
|
+
}
|
|
781
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
782
|
+
}
|
|
783
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
784
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
785
|
+
const flat = {};
|
|
786
|
+
if (rawHeaders) {
|
|
787
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
788
|
+
const value = rawHeaders[name];
|
|
789
|
+
if (value === void 0) continue;
|
|
790
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
794
|
+
return {
|
|
795
|
+
headers,
|
|
796
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
797
|
+
...platformHeaders && { platformHeaders },
|
|
798
|
+
...connection && { connection },
|
|
799
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
541
803
|
// src/adapters/approval-gate.ts
|
|
542
804
|
var APPROVAL_REASON = "Transaction is above the autonomous limit and requires human approval. The agent owner can approve or deny this transaction in the AstraSync dashboard.";
|
|
543
805
|
function requiresHumanApproval(result) {
|
|
@@ -716,8 +978,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
716
978
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
717
979
|
action = parsed.actionFromBody;
|
|
718
980
|
actionSource = parsed.actionSourceFromBody;
|
|
981
|
+
} else if (parsed.method === "tools/call") {
|
|
982
|
+
action = void 0;
|
|
983
|
+
actionSource = void 0;
|
|
719
984
|
} else {
|
|
720
|
-
action = parsed.toolName ?
|
|
985
|
+
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
721
986
|
actionSource = "transport_layer";
|
|
722
987
|
}
|
|
723
988
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -755,6 +1020,24 @@ function dedupeFailures(result) {
|
|
|
755
1020
|
}
|
|
756
1021
|
function defaultMcpDenied(result, req, res) {
|
|
757
1022
|
const id = req.body?.id ?? null;
|
|
1023
|
+
if (result.misconfigured) {
|
|
1024
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1025
|
+
res.status(503).json({
|
|
1026
|
+
jsonrpc: "2.0",
|
|
1027
|
+
id,
|
|
1028
|
+
error: {
|
|
1029
|
+
// -32002 → merchant verification misconfigured (see -32000/-32001 below)
|
|
1030
|
+
code: -32002,
|
|
1031
|
+
message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
|
|
1032
|
+
data: {
|
|
1033
|
+
guidance: result.guidance,
|
|
1034
|
+
failures: result.failures,
|
|
1035
|
+
correlationId: result.correlationId
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
758
1041
|
const status = !result.identityVerified ? 401 : 403;
|
|
759
1042
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
760
1043
|
res.status(status).json({
|
|
@@ -764,6 +1047,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
764
1047
|
// JSON-RPC error codes:
|
|
765
1048
|
// -32000 → unauthorized (no identity resolved)
|
|
766
1049
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
1050
|
+
// -32002 → merchant verification misconfigured (handled above)
|
|
767
1051
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
768
1052
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
769
1053
|
data: {
|
|
@@ -813,6 +1097,15 @@ function createMcpMiddleware(options) {
|
|
|
813
1097
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
814
1098
|
});
|
|
815
1099
|
}
|
|
1100
|
+
if (toolGates) {
|
|
1101
|
+
for (const [toolName, gate] of Object.entries(toolGates)) {
|
|
1102
|
+
if (typeof gate === "string") {
|
|
1103
|
+
console.warn(
|
|
1104
|
+
`[VerificationGateway/MCP] toolGates['${toolName}'] uses the bare-string shorthand ('${gate}'). Gated tools need a PDLSS purpose (else PDLSS_PURPOSE_REQUIRED 400) and evaluate purpose-only without a declared action. Use the object form instead: { minAccessLevel: '${gate}', purpose: '...', action: '...' }.`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
816
1109
|
return async (req, res, next) => {
|
|
817
1110
|
try {
|
|
818
1111
|
if (skip) return next();
|
|
@@ -932,7 +1225,13 @@ function createMcpMiddleware(options) {
|
|
|
932
1225
|
callerMetadata: {
|
|
933
1226
|
sourceIp: req.ip,
|
|
934
1227
|
userAgent: req.headers["user-agent"],
|
|
935
|
-
host: req.headers.host
|
|
1228
|
+
host: req.headers.host,
|
|
1229
|
+
// Capture EVERY inbound header (parity with the express adapter —
|
|
1230
|
+
// MCP-only merchants have no edge to do it upstream) plus the
|
|
1231
|
+
// CDN-derived connection block. Local string work, secrets
|
|
1232
|
+
// stripped; out of the verify cache key so it doesn't defeat
|
|
1233
|
+
// caching.
|
|
1234
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
936
1235
|
}
|
|
937
1236
|
};
|
|
938
1237
|
let result = await verify(config, verificationRequest);
|