@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.js
CHANGED
|
@@ -40,7 +40,7 @@ function getTrustLevel(score) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// src/version.ts
|
|
43
|
-
var SDK_VERSION = "
|
|
43
|
+
var SDK_VERSION = "4.1.0";
|
|
44
44
|
|
|
45
45
|
// src/well-known.ts
|
|
46
46
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -118,7 +118,7 @@ var DEFAULT_CONFIG = {
|
|
|
118
118
|
};
|
|
119
119
|
var initCheckPerformed = false;
|
|
120
120
|
var deprecationWarningShown = false;
|
|
121
|
-
async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
121
|
+
async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
122
122
|
initCheckPerformed = true;
|
|
123
123
|
try {
|
|
124
124
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
@@ -135,6 +135,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
|
|
|
135
135
|
`[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
|
|
136
136
|
);
|
|
137
137
|
}
|
|
138
|
+
if (apiKey) {
|
|
139
|
+
const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
140
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
141
|
+
});
|
|
142
|
+
if (selfCheck.status === 401) {
|
|
143
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
144
|
+
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}` : "") + `.`;
|
|
145
|
+
if (strictInit) {
|
|
146
|
+
throw new Error(`${message} (strictInit=true)`);
|
|
147
|
+
}
|
|
148
|
+
console.error(message);
|
|
149
|
+
} else if (selfCheck.ok) {
|
|
150
|
+
const body = await selfCheck.json().catch(() => ({}));
|
|
151
|
+
if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
|
|
152
|
+
console.warn(
|
|
153
|
+
`[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
|
|
154
|
+
);
|
|
155
|
+
} else if (debug) {
|
|
156
|
+
console.log("[VerificationGateway] API key self-check passed");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
138
160
|
} catch (err) {
|
|
139
161
|
if (strictInit) {
|
|
140
162
|
throw err;
|
|
@@ -246,8 +268,17 @@ function extractCredentials(headers, query) {
|
|
|
246
268
|
function createGuidanceResponse(_config, reason, options = {}) {
|
|
247
269
|
const source = options.source ?? "no_credentials";
|
|
248
270
|
const isApiError = source === "api_error";
|
|
271
|
+
const isMisconfig = source === "merchant_misconfig";
|
|
249
272
|
const urls = options.urls;
|
|
250
|
-
const guidance =
|
|
273
|
+
const guidance = isMisconfig ? {
|
|
274
|
+
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.",
|
|
275
|
+
registrationUrl: urls?.registrationUrl ?? "",
|
|
276
|
+
documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
|
|
277
|
+
steps: [
|
|
278
|
+
"Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
|
|
279
|
+
"Do not retry: the failure is deterministic until the operator updates their key"
|
|
280
|
+
]
|
|
281
|
+
} : isApiError ? {
|
|
251
282
|
message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
|
|
252
283
|
registrationUrl: urls?.registrationUrl ?? "",
|
|
253
284
|
documentationUrl: urls?.documentationUrl ?? "",
|
|
@@ -285,13 +316,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
|
|
|
285
316
|
// partners (and their custom onDenied handlers) can branch on
|
|
286
317
|
// dimension. Without this, the synthesised stub was indistinguishable
|
|
287
318
|
// from a real policy deny.
|
|
288
|
-
failures:
|
|
319
|
+
failures: isMisconfig ? [
|
|
320
|
+
{
|
|
321
|
+
dimension: "verify_access.merchant_misconfigured",
|
|
322
|
+
message: reason ?? "The integration API key was rejected by AstraSync",
|
|
323
|
+
guidance: guidance.message
|
|
324
|
+
}
|
|
325
|
+
] : isApiError ? [
|
|
289
326
|
{
|
|
290
327
|
dimension: "verify_access.api_error",
|
|
291
328
|
message: reason ?? "Verification temporarily unavailable",
|
|
292
329
|
guidance: guidance.message
|
|
293
330
|
}
|
|
294
331
|
] : void 0,
|
|
332
|
+
// 4.0.0: typed marker so adapters render the misconfig envelope (503 +
|
|
333
|
+
// MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
|
|
334
|
+
...isMisconfig && { misconfigured: true },
|
|
295
335
|
correlationId: options.correlationId,
|
|
296
336
|
attemptId: options.attemptId,
|
|
297
337
|
verifiedAt: /* @__PURE__ */ new Date()
|
|
@@ -346,7 +386,8 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
346
386
|
const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
347
387
|
method: "POST",
|
|
348
388
|
headers,
|
|
349
|
-
body: JSON.stringify(body)
|
|
389
|
+
body: JSON.stringify(body),
|
|
390
|
+
signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
|
|
350
391
|
});
|
|
351
392
|
const data = await response.json();
|
|
352
393
|
if (response.status === 410) {
|
|
@@ -370,6 +411,17 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
370
411
|
};
|
|
371
412
|
}
|
|
372
413
|
if (!response.ok) {
|
|
414
|
+
if (response.status === 401) {
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
error: data.message || data.error || "Integration API key rejected",
|
|
418
|
+
misconfig: true,
|
|
419
|
+
misconfigCode: typeof data?.code === "string" ? data.code : void 0,
|
|
420
|
+
remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
|
|
421
|
+
correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
|
|
422
|
+
attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
|
|
423
|
+
};
|
|
424
|
+
}
|
|
373
425
|
return {
|
|
374
426
|
success: false,
|
|
375
427
|
error: data.message || data.error || `API returned ${response.status}`,
|
|
@@ -391,9 +443,19 @@ async function verify(config, request, options) {
|
|
|
391
443
|
const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
|
|
392
444
|
if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
|
|
393
445
|
if (mergedConfig.strictInit) {
|
|
394
|
-
await performInitCheck(
|
|
446
|
+
await performInitCheck(
|
|
447
|
+
mergedConfig.apiBaseUrl,
|
|
448
|
+
mergedConfig.debug,
|
|
449
|
+
true,
|
|
450
|
+
mergedConfig.apiKey
|
|
451
|
+
);
|
|
395
452
|
} else {
|
|
396
|
-
void performInitCheck(
|
|
453
|
+
void performInitCheck(
|
|
454
|
+
mergedConfig.apiBaseUrl,
|
|
455
|
+
mergedConfig.debug,
|
|
456
|
+
false,
|
|
457
|
+
mergedConfig.apiKey
|
|
458
|
+
);
|
|
397
459
|
}
|
|
398
460
|
}
|
|
399
461
|
if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
|
|
@@ -432,9 +494,10 @@ async function verify(config, request, options) {
|
|
|
432
494
|
const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
|
|
433
495
|
if (!apiResponse.success) {
|
|
434
496
|
return createGuidanceResponse(mergedConfig, apiResponse.error, {
|
|
435
|
-
source: "api_error",
|
|
497
|
+
source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
|
|
436
498
|
correlationId: apiResponse.correlationId,
|
|
437
499
|
attemptId: apiResponse.attemptId,
|
|
500
|
+
remediation: apiResponse.remediation,
|
|
438
501
|
urls
|
|
439
502
|
});
|
|
440
503
|
}
|
|
@@ -571,6 +634,205 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
571
634
|
});
|
|
572
635
|
}
|
|
573
636
|
|
|
637
|
+
// src/metadata-capture.ts
|
|
638
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
639
|
+
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
640
|
+
"cookie",
|
|
641
|
+
"set-cookie",
|
|
642
|
+
"x-csrf-token",
|
|
643
|
+
"proxy-authorization",
|
|
644
|
+
"x-astrasync-signature",
|
|
645
|
+
"x-astrasync-secret",
|
|
646
|
+
"x-hub-signature",
|
|
647
|
+
"x-hub-signature-256"
|
|
648
|
+
]);
|
|
649
|
+
var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
|
|
650
|
+
var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
651
|
+
"authorization",
|
|
652
|
+
"x-api-key",
|
|
653
|
+
"x-goog-api-key",
|
|
654
|
+
"api-key"
|
|
655
|
+
]);
|
|
656
|
+
function isSignatureHeader(name) {
|
|
657
|
+
return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
|
|
658
|
+
}
|
|
659
|
+
var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
660
|
+
"openai-organization",
|
|
661
|
+
"openai-project",
|
|
662
|
+
"openai-version",
|
|
663
|
+
"openai-processing-ms",
|
|
664
|
+
"anthropic-version",
|
|
665
|
+
"anthropic-beta",
|
|
666
|
+
"x-goog-user-project",
|
|
667
|
+
"x-goog-api-client",
|
|
668
|
+
"user-agent",
|
|
669
|
+
"x-agent-card-url",
|
|
670
|
+
"x-agent-provider"
|
|
671
|
+
]);
|
|
672
|
+
var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
|
|
673
|
+
var KEY_FORMAT_PREFIXES = [
|
|
674
|
+
"sk-ant-api03",
|
|
675
|
+
"sk-ant-api",
|
|
676
|
+
"sk-ant",
|
|
677
|
+
"sk-proj",
|
|
678
|
+
"sk-svcacct",
|
|
679
|
+
"sk-or-v1",
|
|
680
|
+
"sk-or",
|
|
681
|
+
"sk-lf",
|
|
682
|
+
"sk-",
|
|
683
|
+
"gsk_",
|
|
684
|
+
"aiza",
|
|
685
|
+
"ya29",
|
|
686
|
+
"ghp_",
|
|
687
|
+
"gho_",
|
|
688
|
+
"github_pat_",
|
|
689
|
+
"xai-",
|
|
690
|
+
"pplx-",
|
|
691
|
+
"r8_",
|
|
692
|
+
"hf_",
|
|
693
|
+
"astra-",
|
|
694
|
+
"astrae-"
|
|
695
|
+
];
|
|
696
|
+
var MAX_HEADERS = 64;
|
|
697
|
+
var MAX_HEADER_VALUE_BYTES = 1024;
|
|
698
|
+
var MAX_TOTAL_BYTES = 16 * 1024;
|
|
699
|
+
function byteLength(s) {
|
|
700
|
+
let bytes = 0;
|
|
701
|
+
for (let i = 0; i < s.length; i++) {
|
|
702
|
+
const code = s.charCodeAt(i);
|
|
703
|
+
if (code < 128) bytes += 1;
|
|
704
|
+
else if (code < 2048) bytes += 2;
|
|
705
|
+
else if (code >= 55296 && code <= 56319) {
|
|
706
|
+
bytes += 4;
|
|
707
|
+
i++;
|
|
708
|
+
} else bytes += 3;
|
|
709
|
+
}
|
|
710
|
+
return bytes;
|
|
711
|
+
}
|
|
712
|
+
function extractApiKeyFormat(rawValue) {
|
|
713
|
+
if (!rawValue) return void 0;
|
|
714
|
+
let value = rawValue.trim();
|
|
715
|
+
const spaceIdx = value.indexOf(" ");
|
|
716
|
+
if (spaceIdx > 0) {
|
|
717
|
+
const scheme = value.slice(0, spaceIdx).toLowerCase();
|
|
718
|
+
if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
|
|
719
|
+
value = value.slice(spaceIdx + 1).trim();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const lower = value.toLowerCase();
|
|
723
|
+
for (const prefix of KEY_FORMAT_PREFIXES) {
|
|
724
|
+
if (lower.startsWith(prefix)) {
|
|
725
|
+
return prefix;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return void 0;
|
|
729
|
+
}
|
|
730
|
+
function isSecretHeader(name) {
|
|
731
|
+
if (SECRET_HEADER_NAMES.has(name)) return true;
|
|
732
|
+
if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
|
|
733
|
+
if (isSignatureHeader(name)) return true;
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
function isPlatformHeader(name) {
|
|
737
|
+
if (PLATFORM_HEADER_NAMES.has(name)) return true;
|
|
738
|
+
return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
|
|
739
|
+
}
|
|
740
|
+
function sanitizeHeaders(raw) {
|
|
741
|
+
const headers = {};
|
|
742
|
+
const platformHeaders = {};
|
|
743
|
+
let apiKeyFormat;
|
|
744
|
+
let totalBytes = 0;
|
|
745
|
+
let count = 0;
|
|
746
|
+
if (!raw) return { headers };
|
|
747
|
+
for (const rawName of Object.keys(raw)) {
|
|
748
|
+
if (count >= MAX_HEADERS) break;
|
|
749
|
+
const name = rawName.toLowerCase();
|
|
750
|
+
const rawValue = raw[rawName];
|
|
751
|
+
if (rawValue === void 0) continue;
|
|
752
|
+
const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
|
|
753
|
+
if (CREDENTIAL_HEADER_NAMES.has(name)) {
|
|
754
|
+
const fmt = extractApiKeyFormat(joined);
|
|
755
|
+
if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (isSecretHeader(name)) continue;
|
|
759
|
+
let value = joined;
|
|
760
|
+
if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
|
|
761
|
+
value = value.slice(0, MAX_HEADER_VALUE_BYTES);
|
|
762
|
+
}
|
|
763
|
+
const entryBytes = byteLength(name) + byteLength(value);
|
|
764
|
+
if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
|
|
765
|
+
totalBytes += entryBytes;
|
|
766
|
+
count++;
|
|
767
|
+
headers[name] = value;
|
|
768
|
+
if (isPlatformHeader(name)) platformHeaders[name] = value;
|
|
769
|
+
}
|
|
770
|
+
const result = { headers };
|
|
771
|
+
if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
|
|
772
|
+
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
773
|
+
return result;
|
|
774
|
+
}
|
|
775
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
776
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
777
|
+
[
|
|
778
|
+
"country",
|
|
779
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
780
|
+
],
|
|
781
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
782
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
783
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
784
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
785
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
786
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
787
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
788
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
789
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
790
|
+
["ja4", ["cf-ja4"]]
|
|
791
|
+
];
|
|
792
|
+
function deriveConnectionFromHeaders(headers) {
|
|
793
|
+
if (!headers) return void 0;
|
|
794
|
+
const lower = {};
|
|
795
|
+
for (const name of Object.keys(headers)) {
|
|
796
|
+
const value = headers[name];
|
|
797
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
798
|
+
}
|
|
799
|
+
const connection = {};
|
|
800
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
801
|
+
for (const source of sources) {
|
|
802
|
+
const value = lower[source]?.trim();
|
|
803
|
+
if (value) {
|
|
804
|
+
connection[key] = value;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (!connection.ip) {
|
|
810
|
+
const xff = lower["x-forwarded-for"];
|
|
811
|
+
const client = xff?.split(",")[0]?.trim();
|
|
812
|
+
if (client) connection.ip = client;
|
|
813
|
+
}
|
|
814
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
815
|
+
}
|
|
816
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
817
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
818
|
+
const flat = {};
|
|
819
|
+
if (rawHeaders) {
|
|
820
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
821
|
+
const value = rawHeaders[name];
|
|
822
|
+
if (value === void 0) continue;
|
|
823
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
827
|
+
return {
|
|
828
|
+
headers,
|
|
829
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
830
|
+
...platformHeaders && { platformHeaders },
|
|
831
|
+
...connection && { connection },
|
|
832
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
|
|
574
836
|
// src/adapters/approval-gate.ts
|
|
575
837
|
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.";
|
|
576
838
|
function requiresHumanApproval(result) {
|
|
@@ -749,8 +1011,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
|
|
|
749
1011
|
} else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
|
|
750
1012
|
action = parsed.actionFromBody;
|
|
751
1013
|
actionSource = parsed.actionSourceFromBody;
|
|
1014
|
+
} else if (parsed.method === "tools/call") {
|
|
1015
|
+
action = void 0;
|
|
1016
|
+
actionSource = void 0;
|
|
752
1017
|
} else {
|
|
753
|
-
action = parsed.toolName ?
|
|
1018
|
+
action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
|
|
754
1019
|
actionSource = "transport_layer";
|
|
755
1020
|
}
|
|
756
1021
|
return { purpose, action, resource, purposeSource, actionSource };
|
|
@@ -788,6 +1053,24 @@ function dedupeFailures(result) {
|
|
|
788
1053
|
}
|
|
789
1054
|
function defaultMcpDenied(result, req, res) {
|
|
790
1055
|
const id = req.body?.id ?? null;
|
|
1056
|
+
if (result.misconfigured) {
|
|
1057
|
+
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1058
|
+
res.status(503).json({
|
|
1059
|
+
jsonrpc: "2.0",
|
|
1060
|
+
id,
|
|
1061
|
+
error: {
|
|
1062
|
+
// -32002 → merchant verification misconfigured (see -32000/-32001 below)
|
|
1063
|
+
code: -32002,
|
|
1064
|
+
message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
|
|
1065
|
+
data: {
|
|
1066
|
+
guidance: result.guidance,
|
|
1067
|
+
failures: result.failures,
|
|
1068
|
+
correlationId: result.correlationId
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
791
1074
|
const status = !result.identityVerified ? 401 : 403;
|
|
792
1075
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
793
1076
|
res.status(status).json({
|
|
@@ -797,6 +1080,7 @@ function defaultMcpDenied(result, req, res) {
|
|
|
797
1080
|
// JSON-RPC error codes:
|
|
798
1081
|
// -32000 → unauthorized (no identity resolved)
|
|
799
1082
|
// -32001 → insufficient access (identity OK, policy denied)
|
|
1083
|
+
// -32002 → merchant verification misconfigured (handled above)
|
|
800
1084
|
code: !result.identityVerified ? -32e3 : -32001,
|
|
801
1085
|
message: result.denialReasons?.[0] ?? "Access denied",
|
|
802
1086
|
data: {
|
|
@@ -846,6 +1130,15 @@ function createMcpMiddleware(options) {
|
|
|
846
1130
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
847
1131
|
});
|
|
848
1132
|
}
|
|
1133
|
+
if (toolGates) {
|
|
1134
|
+
for (const [toolName, gate] of Object.entries(toolGates)) {
|
|
1135
|
+
if (typeof gate === "string") {
|
|
1136
|
+
console.warn(
|
|
1137
|
+
`[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: '...' }.`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
849
1142
|
return async (req, res, next) => {
|
|
850
1143
|
try {
|
|
851
1144
|
if (skip) return next();
|
|
@@ -965,7 +1258,13 @@ function createMcpMiddleware(options) {
|
|
|
965
1258
|
callerMetadata: {
|
|
966
1259
|
sourceIp: req.ip,
|
|
967
1260
|
userAgent: req.headers["user-agent"],
|
|
968
|
-
host: req.headers.host
|
|
1261
|
+
host: req.headers.host,
|
|
1262
|
+
// Capture EVERY inbound header (parity with the express adapter —
|
|
1263
|
+
// MCP-only merchants have no edge to do it upstream) plus the
|
|
1264
|
+
// CDN-derived connection block. Local string work, secrets
|
|
1265
|
+
// stripped; out of the verify cache key so it doesn't defeat
|
|
1266
|
+
// caching.
|
|
1267
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
969
1268
|
}
|
|
970
1269
|
};
|
|
971
1270
|
let result = await verify(config, verificationRequest);
|