@astrasyncai/verification-gateway 5.18.1 → 5.20.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/CHANGELOG.md +232 -0
- package/README.md +217 -4
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +9 -1
- package/dist/adapters/express.d.ts +9 -1
- package/dist/adapters/express.js +160 -52
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +160 -52
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +7 -1
- package/dist/adapters/http-pdlss.d.ts +7 -1
- package/dist/adapters/http-pdlss.js +18 -2
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs +16 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/kit-express.d.mts +93 -1
- package/dist/adapters/kit-express.d.ts +93 -1
- package/dist/adapters/kit-express.js +181 -2
- package/dist/adapters/kit-express.js.map +1 -1
- package/dist/adapters/kit-express.mjs +180 -2
- package/dist/adapters/kit-express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +102 -9
- package/dist/adapters/mcp.d.ts +102 -9
- package/dist/adapters/mcp.js +242 -19
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +240 -19
- 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 +97 -14
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +97 -14
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +101 -2
- package/dist/adapters/sdk.d.ts +101 -2
- package/dist/adapters/sdk.js +199 -12
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +199 -12
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +1 -1
- package/dist/agent/index.d.ts +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 +94 -16
- package/dist/bin/astrasync-codex-hook.js +94 -16
- package/dist/bin/astrasync-guard.js +94 -16
- package/dist/bin/astrasync.js +127 -16
- package/dist/browser/background.js +94 -16
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +94 -16
- 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 +94 -16
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +94 -16
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +94 -16
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +94 -16
- 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 +1 -1
- package/dist/edge-core/index.d.ts +1 -1
- package/dist/edge-core/index.js +69 -12
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +69 -12
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +94 -16
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +94 -16
- 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 +706 -34
- package/dist/index.d.ts +706 -34
- package/dist/index.js +1391 -205
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1374 -195
- 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/registration/index.d.mts +11 -2
- package/dist/registration/index.d.ts +11 -2
- 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.d.mts +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +3 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +3 -2
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
- package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
- package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
- package/dist/{types-Cns4l8gX.d.mts → types-CxjqmPl-.d.mts} +115 -13
- package/dist/{types-Cns4l8gX.d.ts → types-CxjqmPl-.d.ts} +115 -13
- package/dist/{types-3ObBpsnf.d.mts → types-D37aSQ9c.d.mts} +106 -3
- package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -1
- package/dist/{types-BoxZEYPS.d.ts → types-Dn8tXnei.d.ts} +106 -3
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +50 -4
- package/dist/verify.d.ts +50 -4
- package/dist/verify.js +93 -14
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +91 -14
- package/dist/verify.mjs.map +1 -1
- package/dist/webhooks.d.mts +16 -3
- package/dist/webhooks.d.ts +16 -3
- package/dist/webhooks.js +24 -13
- package/dist/webhooks.js.map +1 -1
- package/dist/webhooks.mjs +24 -13
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/adapters/mcp.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var SDK_VERSION = "5.
|
|
2
|
+
var SDK_VERSION = "5.20.0";
|
|
3
3
|
|
|
4
4
|
// src/http.ts
|
|
5
5
|
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
@@ -241,7 +241,12 @@ function getCacheKey(request, counterpartyId) {
|
|
|
241
241
|
// its start_checkout quote was served the cached quote-leg grant (no
|
|
242
242
|
// settlementOutcome) and never reached the backend. Same F-A1-07 bug class.
|
|
243
243
|
request.commercePhase || "",
|
|
244
|
-
request.checkoutSessionId || ""
|
|
244
|
+
request.checkoutSessionId || "",
|
|
245
|
+
// 5.19.0: a route_config purpose claim is checked server-side against
|
|
246
|
+
// the stored rule for this method + path, so it changes the verdict.
|
|
247
|
+
request.purposeSource || "",
|
|
248
|
+
request.requestMethod || "",
|
|
249
|
+
request.requestPath || ""
|
|
245
250
|
].join("|");
|
|
246
251
|
}
|
|
247
252
|
function getCachedResult(request, counterpartyId) {
|
|
@@ -433,6 +438,9 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
433
438
|
if (requestData.enableRuntimeChallenge)
|
|
434
439
|
body.enableRuntimeChallenge = requestData.enableRuntimeChallenge;
|
|
435
440
|
if (requestData.routePolicy) body.routePolicy = requestData.routePolicy;
|
|
441
|
+
if (config.actingProof?.connectorToken) {
|
|
442
|
+
body.actingProof = { connectorToken: config.actingProof.connectorToken };
|
|
443
|
+
}
|
|
436
444
|
if (requestData.approvalHeldByRoute) body.approvalHeldByRoute = true;
|
|
437
445
|
if (requestData.createSession) body.createSession = requestData.createSession;
|
|
438
446
|
if (requestData.durationRequired) body.durationRequired = requestData.durationRequired;
|
|
@@ -449,6 +457,11 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
449
457
|
if (requestData.buyerEmail) body.buyerEmail = requestData.buyerEmail;
|
|
450
458
|
if (requestData.commerceArtifacts) body.commerceArtifacts = requestData.commerceArtifacts;
|
|
451
459
|
if (requestData.attemptId) body.attemptId = requestData.attemptId;
|
|
460
|
+
if (requestData.purposeSource) body.purposeSource = requestData.purposeSource;
|
|
461
|
+
if (requestData.agentArtifacts) body.agentArtifacts = requestData.agentArtifacts;
|
|
462
|
+
if (requestData.metadata) body.metadata = requestData.metadata;
|
|
463
|
+
if (requestData.requestMethod) body.requestMethod = requestData.requestMethod;
|
|
464
|
+
if (requestData.requestPath) body.requestPath = requestData.requestPath;
|
|
452
465
|
if (requestData.considerationSet) body.considerationSet = requestData.considerationSet;
|
|
453
466
|
body.sdkVersion = SDK_VERSION;
|
|
454
467
|
if (requestData.callerMetadata || requestData.clientIp || requestData.userAgent) {
|
|
@@ -595,22 +608,46 @@ async function verify(config, request, options) {
|
|
|
595
608
|
"[VerificationGateway] minTrustScore / minTrustScoreForFull are deprecated in v2.3.0 and have no effect. Server is now the single source of truth for access decisions (verified ID + runtime challenge + PDLSS + trust score). To gate access to an endpoint, configure the endpoint's trust_score_requirement server-side."
|
|
596
609
|
);
|
|
597
610
|
}
|
|
598
|
-
const uncacheable = Boolean(request.commerceArtifacts);
|
|
611
|
+
const uncacheable = Boolean(request.commerceArtifacts) || Boolean(mergedConfig.actingProof);
|
|
599
612
|
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
|
|
600
613
|
const cached = getCachedResult(request, mergedConfig.counterpartyId);
|
|
601
614
|
if (cached) {
|
|
602
615
|
if (mergedConfig.debug) {
|
|
603
616
|
console.log("[VerificationGateway] Returning cached result");
|
|
604
617
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
618
|
+
return sharedVerdict(cached, request);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (mergedConfig.cacheTtl !== 0 && !options?.bypassCache && !uncacheable) {
|
|
622
|
+
const key = getCacheKey(request, mergedConfig.counterpartyId);
|
|
623
|
+
const pending = inflightVerifies.get(key);
|
|
624
|
+
if (pending) return sharedVerdict(await pending, request);
|
|
625
|
+
const flight = verifyViaApi(mergedConfig, request, urls, uncacheable);
|
|
626
|
+
inflightVerifies.set(key, flight);
|
|
627
|
+
try {
|
|
628
|
+
return await flight;
|
|
629
|
+
} finally {
|
|
630
|
+
inflightVerifies.delete(key);
|
|
612
631
|
}
|
|
613
632
|
}
|
|
633
|
+
return verifyViaApi(mergedConfig, request, urls, uncacheable);
|
|
634
|
+
}
|
|
635
|
+
var inflightVerifies = /* @__PURE__ */ new Map();
|
|
636
|
+
var sharedVerdicts = /* @__PURE__ */ new WeakSet();
|
|
637
|
+
function isSharedVerdict(result) {
|
|
638
|
+
return sharedVerdicts.has(result);
|
|
639
|
+
}
|
|
640
|
+
function sharedVerdict(source, request) {
|
|
641
|
+
const hit = { ...source };
|
|
642
|
+
if (request.attemptId) {
|
|
643
|
+
hit.attemptId = request.attemptId;
|
|
644
|
+
} else {
|
|
645
|
+
delete hit.attemptId;
|
|
646
|
+
}
|
|
647
|
+
sharedVerdicts.add(hit);
|
|
648
|
+
return hit;
|
|
649
|
+
}
|
|
650
|
+
async function verifyViaApi(mergedConfig, request, urls, uncacheable) {
|
|
614
651
|
const enrichedRequest = { ...request };
|
|
615
652
|
if (!enrichedRequest.counterpartyUrl && mergedConfig.counterpartyUrl) {
|
|
616
653
|
enrichedRequest.counterpartyUrl = mergedConfig.counterpartyUrl;
|
|
@@ -638,9 +675,18 @@ async function verify(config, request, options) {
|
|
|
638
675
|
if (!apiResponse.access?.allowed) {
|
|
639
676
|
const aggregatedFailures = apiResponse.access?.failures;
|
|
640
677
|
const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
|
|
678
|
+
const denyAgent = idVerifiedFromBackend && apiResponse.agent ? {
|
|
679
|
+
astraId: apiResponse.agent.astraId,
|
|
680
|
+
name: apiResponse.agent.name,
|
|
681
|
+
trustScore: apiResponse.agent.trustScore,
|
|
682
|
+
trustLevel: getTrustLevel(apiResponse.agent.trustScore),
|
|
683
|
+
blockchainAnchored: apiResponse.agent.blockchainStatus === "anchored",
|
|
684
|
+
status: apiResponse.agent.agentStatus
|
|
685
|
+
} : void 0;
|
|
641
686
|
const result2 = {
|
|
642
687
|
identityVerified: idVerifiedFromBackend,
|
|
643
688
|
policyAllowed: false,
|
|
689
|
+
...denyAgent && { agent: denyAgent },
|
|
644
690
|
// 3.6.0: the server now emits top-level denialReasons on every deny
|
|
645
691
|
// path — prefer it verbatim; the failure-message reconstruction remains
|
|
646
692
|
// as the fallback for older backends.
|
|
@@ -680,7 +726,9 @@ async function verify(config, request, options) {
|
|
|
680
726
|
fulfilment: apiResponse.fulfilment,
|
|
681
727
|
mandates: apiResponse.mandates,
|
|
682
728
|
kyaToken: apiResponse.kyaToken,
|
|
683
|
-
aep: apiResponse.aep
|
|
729
|
+
aep: apiResponse.aep,
|
|
730
|
+
// 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
|
|
731
|
+
warnings: apiResponse.warnings
|
|
684
732
|
};
|
|
685
733
|
return result2;
|
|
686
734
|
}
|
|
@@ -748,7 +796,9 @@ async function verify(config, request, options) {
|
|
|
748
796
|
// 5.15.0: merchant-lane AP2 mandates + KYAPay identity token.
|
|
749
797
|
mandates: apiResponse.mandates,
|
|
750
798
|
kyaToken: apiResponse.kyaToken,
|
|
751
|
-
aep: apiResponse.aep
|
|
799
|
+
aep: apiResponse.aep,
|
|
800
|
+
// 5.20.0 (round 29.1): non-fatal integration warnings (e.g. events_url_missing).
|
|
801
|
+
warnings: apiResponse.warnings
|
|
752
802
|
};
|
|
753
803
|
if (result.recommendation === "deny") {
|
|
754
804
|
result.policyAllowed = false;
|
|
@@ -774,8 +824,18 @@ async function verify(config, request, options) {
|
|
|
774
824
|
cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
|
|
775
825
|
}
|
|
776
826
|
}
|
|
827
|
+
notifyVerificationListeners(request, result);
|
|
777
828
|
return result;
|
|
778
829
|
}
|
|
830
|
+
var verificationListeners = /* @__PURE__ */ new Set();
|
|
831
|
+
function notifyVerificationListeners(request, result) {
|
|
832
|
+
for (const listener of verificationListeners) {
|
|
833
|
+
try {
|
|
834
|
+
listener(result, request);
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
779
839
|
async function recordDecision(config, sessionId, decision, reason) {
|
|
780
840
|
const headers = { "Content-Type": "application/json" };
|
|
781
841
|
if (config.apiKey) {
|
|
@@ -789,6 +849,42 @@ async function recordDecision(config, sessionId, decision, reason) {
|
|
|
789
849
|
}).catch(() => {
|
|
790
850
|
});
|
|
791
851
|
}
|
|
852
|
+
var refusedPolicyReads = /* @__PURE__ */ new Set();
|
|
853
|
+
async function fetchRoutes(config, counterpartyId, options = {}) {
|
|
854
|
+
if (!counterpartyId) return null;
|
|
855
|
+
const headers = { "Content-Type": "application/json" };
|
|
856
|
+
if (config.apiKey) {
|
|
857
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
858
|
+
headers["X-API-Key"] = config.apiKey;
|
|
859
|
+
}
|
|
860
|
+
try {
|
|
861
|
+
headers["X-AstraSync-SDK-Version"] = SDK_VERSION;
|
|
862
|
+
const response = await sdkFetch(
|
|
863
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes` + (options.codeGates?.length ? `?codeGates=${options.codeGates.map(encodeURIComponent).join(",")}` : ""),
|
|
864
|
+
{ method: "GET", headers }
|
|
865
|
+
);
|
|
866
|
+
if (!response.ok) {
|
|
867
|
+
if ((response.status === 401 || response.status === 403 || response.status === 404) && !refusedPolicyReads.has(counterpartyId)) {
|
|
868
|
+
refusedPolicyReads.add(counterpartyId);
|
|
869
|
+
console.error(
|
|
870
|
+
`[VerificationGateway] the configured API key cannot read the route policy for ${counterpartyId} (HTTP ${response.status}). The key belongs to an account that does not own this endpoint, or the counterpartyId is wrong. The gateway is in PASS-THROUGH mode: no agent is being verified.`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
const body = await response.json();
|
|
876
|
+
const type = body.data?.counterpartyType;
|
|
877
|
+
if (typeof type === "string" && options.onEndpointType) {
|
|
878
|
+
try {
|
|
879
|
+
options.onEndpointType(type);
|
|
880
|
+
} catch {
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return body.data?.routes ?? [];
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
792
888
|
|
|
793
889
|
// src/metadata-capture.ts
|
|
794
890
|
var CAPTURE_SCHEMA_VERSION = 1;
|
|
@@ -1111,6 +1207,42 @@ async function awaitStepUpApproval(config, approval) {
|
|
|
1111
1207
|
return "timeout";
|
|
1112
1208
|
}
|
|
1113
1209
|
|
|
1210
|
+
// src/adapters/routes-cache.ts
|
|
1211
|
+
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1212
|
+
function createRoutesCache(config, opts = {}) {
|
|
1213
|
+
const refreshMs = opts.refreshMs ?? DEFAULT_ROUTES_REFRESH_MS;
|
|
1214
|
+
let cached = [];
|
|
1215
|
+
let lastFetchAt = 0;
|
|
1216
|
+
let refreshing = null;
|
|
1217
|
+
async function refresh() {
|
|
1218
|
+
if (!config.counterpartyId) {
|
|
1219
|
+
opts.onNoCounterparty?.();
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
const fetched = await fetchRoutes(config, config.counterpartyId, opts.fetchOptions);
|
|
1223
|
+
if (fetched) {
|
|
1224
|
+
cached = fetched;
|
|
1225
|
+
lastFetchAt = Date.now();
|
|
1226
|
+
opts.onFetched?.(fetched);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
function start() {
|
|
1230
|
+
refreshing = refresh().catch(() => void 0).finally(() => {
|
|
1231
|
+
refreshing = null;
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
start();
|
|
1235
|
+
return {
|
|
1236
|
+
async current() {
|
|
1237
|
+
if (refreshing) await refreshing.catch(() => {
|
|
1238
|
+
});
|
|
1239
|
+
if (config.counterpartyId && Date.now() - lastFetchAt > refreshMs) start();
|
|
1240
|
+
return cached;
|
|
1241
|
+
},
|
|
1242
|
+
peek: () => cached
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1114
1246
|
// src/verified-hop.ts
|
|
1115
1247
|
var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
|
|
1116
1248
|
var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
|
|
@@ -1331,6 +1463,21 @@ function defaultMcpDenied(result, req, res) {
|
|
|
1331
1463
|
}
|
|
1332
1464
|
});
|
|
1333
1465
|
}
|
|
1466
|
+
function dashboardToolRules(routes) {
|
|
1467
|
+
const out = /* @__PURE__ */ new Map();
|
|
1468
|
+
for (const r of routes) {
|
|
1469
|
+
if (typeof r.tool !== "string" || !r.tool || r.legacy === true) continue;
|
|
1470
|
+
if (out.has(r.tool)) continue;
|
|
1471
|
+
out.set(r.tool, {
|
|
1472
|
+
tool: r.tool,
|
|
1473
|
+
...r.purpose && { purpose: r.purpose },
|
|
1474
|
+
...r.action && { action: r.action },
|
|
1475
|
+
...r.resource && { resource: r.resource },
|
|
1476
|
+
...r.observe === true && { observe: true }
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
return out;
|
|
1480
|
+
}
|
|
1334
1481
|
function resolveGate(parsed, opts) {
|
|
1335
1482
|
if (!parsed.toolName) {
|
|
1336
1483
|
const methodGate = opts.methodGates?.[parsed.method];
|
|
@@ -1340,8 +1487,30 @@ function resolveGate(parsed, opts) {
|
|
|
1340
1487
|
return { enforce: false, source: "discovery_default" };
|
|
1341
1488
|
}
|
|
1342
1489
|
const toolGate = opts.toolGates?.[parsed.toolName];
|
|
1490
|
+
const rule = opts.dashboardRules?.get(parsed.toolName);
|
|
1491
|
+
const fromRule = (r) => r.observe ? { enforce: false, source: "dashboard" } : {
|
|
1492
|
+
enforce: true,
|
|
1493
|
+
config: {
|
|
1494
|
+
...r.purpose && { purpose: r.purpose },
|
|
1495
|
+
...r.action && { action: r.action },
|
|
1496
|
+
...r.resource && { resource: r.resource }
|
|
1497
|
+
},
|
|
1498
|
+
source: "dashboard"
|
|
1499
|
+
};
|
|
1500
|
+
const codeGated = toolGate !== void 0 && toolGate !== "observe";
|
|
1501
|
+
if (rule && (opts.toolGatePrecedence === "dashboard" || !codeGated)) return fromRule(rule);
|
|
1343
1502
|
if (toolGate !== void 0) {
|
|
1344
|
-
|
|
1503
|
+
if (toolGate === "observe") return { enforce: false, source: "toolGate" };
|
|
1504
|
+
if (rule && !rule.observe && rule.purpose && (rule.purpose !== toolGate.purpose || rule.action && rule.action !== toolGate.action)) {
|
|
1505
|
+
const extra = fromRule(rule).config;
|
|
1506
|
+
return {
|
|
1507
|
+
enforce: true,
|
|
1508
|
+
config: toolGate,
|
|
1509
|
+
additional: { ...extra, ...!extra.resource && toolGate.resource && { resource: toolGate.resource } },
|
|
1510
|
+
source: "toolGate"
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
return { enforce: true, config: toolGate, source: "toolGate" };
|
|
1345
1514
|
}
|
|
1346
1515
|
return { enforce: mcpDefaultEnforced(parsed), source: "tier" };
|
|
1347
1516
|
}
|
|
@@ -1356,13 +1525,34 @@ function createMcpMiddleware(options) {
|
|
|
1356
1525
|
verifiedHopMaxAgeMs,
|
|
1357
1526
|
recordDecisions,
|
|
1358
1527
|
enableRuntimeChallenge = true,
|
|
1359
|
-
failOnError = "
|
|
1528
|
+
failOnError = "closed",
|
|
1529
|
+
toolGatePrecedence = "code",
|
|
1530
|
+
routesRefreshMs,
|
|
1531
|
+
skipEvaluation,
|
|
1360
1532
|
...config
|
|
1361
1533
|
} = options;
|
|
1534
|
+
const skipTools = new Set(skipEvaluation?.tools ?? []);
|
|
1535
|
+
const skipMethods = new Set(skipEvaluation?.methods ?? []);
|
|
1362
1536
|
if (config.apiBaseUrl) {
|
|
1363
1537
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1364
1538
|
});
|
|
1365
1539
|
}
|
|
1540
|
+
const codeGatedTools = Object.entries(toolGates ?? {}).filter(([, g]) => g !== "observe").map(([name]) => name);
|
|
1541
|
+
let warnedEndpointType = false;
|
|
1542
|
+
const onEndpointType = (counterpartyType) => {
|
|
1543
|
+
if (counterpartyType === "mcp_server" || warnedEndpointType) return;
|
|
1544
|
+
warnedEndpointType = true;
|
|
1545
|
+
console.warn(
|
|
1546
|
+
`[VerificationGateway/MCP] counterpartyId ${config.counterpartyId} is a '${counterpartyType}' endpoint, not an 'mcp_server' endpoint. Dashboard tool rules are read from an mcp_server endpoint only: register a separate MCP endpoint for this server (same owner) and use its id here. See https://astrasync.ai/docs/mcp-integration#tool-rules.`
|
|
1547
|
+
);
|
|
1548
|
+
};
|
|
1549
|
+
const rulesCache = config.apiKey && config.counterpartyId ? createRoutesCache(config, {
|
|
1550
|
+
...routesRefreshMs !== void 0 && { refreshMs: routesRefreshMs },
|
|
1551
|
+
fetchOptions: {
|
|
1552
|
+
...codeGatedTools.length && { codeGates: codeGatedTools },
|
|
1553
|
+
onEndpointType
|
|
1554
|
+
}
|
|
1555
|
+
}) : void 0;
|
|
1366
1556
|
return async (req, res, next) => {
|
|
1367
1557
|
try {
|
|
1368
1558
|
if (skip) return next();
|
|
@@ -1411,10 +1601,19 @@ function createMcpMiddleware(options) {
|
|
|
1411
1601
|
return next();
|
|
1412
1602
|
}
|
|
1413
1603
|
}
|
|
1604
|
+
if (!parsed.toolName && skipMethods.has(parsed.method) && methodGates?.[parsed.method] === void 0) {
|
|
1605
|
+
return next();
|
|
1606
|
+
}
|
|
1607
|
+
const dashboardRules = rulesCache ? dashboardToolRules(await rulesCache.current()) : void 0;
|
|
1414
1608
|
const gateDecision = resolveGate(parsed, {
|
|
1415
1609
|
toolGates,
|
|
1416
|
-
methodGates
|
|
1610
|
+
methodGates,
|
|
1611
|
+
dashboardRules,
|
|
1612
|
+
toolGatePrecedence
|
|
1417
1613
|
});
|
|
1614
|
+
if (!gateDecision.enforce && (parsed.toolName && skipTools.has(parsed.toolName) || !parsed.toolName && skipMethods.has(parsed.method))) {
|
|
1615
|
+
return next();
|
|
1616
|
+
}
|
|
1418
1617
|
const credentials = extractCredentials(
|
|
1419
1618
|
req.headers,
|
|
1420
1619
|
req.query
|
|
@@ -1471,8 +1670,14 @@ function createMcpMiddleware(options) {
|
|
|
1471
1670
|
const forwardedFor = req.headers["x-forwarded-for"];
|
|
1472
1671
|
const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
|
|
1473
1672
|
const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
|
|
1673
|
+
const attemptRaw = readSingleHeader(req.headers["x-astra-attempt-id"]);
|
|
1674
|
+
const metaAttempt = req.body?.params?._meta?.astrasync?.attemptId;
|
|
1675
|
+
const attemptId = [attemptRaw, metaAttempt].find(
|
|
1676
|
+
(v) => typeof v === "string" && /^att_[0-9a-f]{32}$/.test(v)
|
|
1677
|
+
);
|
|
1474
1678
|
const verificationRequest = {
|
|
1475
1679
|
credentials,
|
|
1680
|
+
...attemptId && { attemptId },
|
|
1476
1681
|
purpose: pdlss.purpose,
|
|
1477
1682
|
action: pdlss.action,
|
|
1478
1683
|
resource: pdlss.resource,
|
|
@@ -1511,6 +1716,20 @@ function createMcpMiddleware(options) {
|
|
|
1511
1716
|
result.stepUpOutcome = outcome;
|
|
1512
1717
|
}
|
|
1513
1718
|
}
|
|
1719
|
+
if (shouldEnforce && gateDecision.additional?.purpose && result.policyAllowed && !requiresHumanApproval(result)) {
|
|
1720
|
+
const extra = mcpToPdlss(parsed, req.path, void 0, void 0, gateDecision.additional);
|
|
1721
|
+
const extraResult = await verify(config, {
|
|
1722
|
+
...verificationRequest,
|
|
1723
|
+
purpose: extra.purpose,
|
|
1724
|
+
action: extra.action,
|
|
1725
|
+
resource: extra.resource
|
|
1726
|
+
});
|
|
1727
|
+
if (!extraResult.policyAllowed || requiresHumanApproval(extraResult)) {
|
|
1728
|
+
result = extraResult;
|
|
1729
|
+
req.agentVerification = result;
|
|
1730
|
+
sessionId = result.sessionId;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1514
1733
|
if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
|
|
1515
1734
|
res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
|
|
1516
1735
|
return next();
|
|
@@ -1520,14 +1739,14 @@ function createMcpMiddleware(options) {
|
|
|
1520
1739
|
res.setHeader("X-Astra-Gateway-Mode", "enforced");
|
|
1521
1740
|
res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
|
|
1522
1741
|
}
|
|
1523
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1742
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1524
1743
|
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1525
1744
|
});
|
|
1526
1745
|
}
|
|
1527
1746
|
return next();
|
|
1528
1747
|
}
|
|
1529
1748
|
if (!result.policyAllowed) {
|
|
1530
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1749
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1531
1750
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
1532
1751
|
});
|
|
1533
1752
|
}
|
|
@@ -1537,7 +1756,7 @@ function createMcpMiddleware(options) {
|
|
|
1537
1756
|
}
|
|
1538
1757
|
if (requiresHumanApproval(result)) {
|
|
1539
1758
|
annotateApprovalRequired(result);
|
|
1540
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1759
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1541
1760
|
recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
|
|
1542
1761
|
});
|
|
1543
1762
|
}
|
|
@@ -1555,7 +1774,7 @@ function createMcpMiddleware(options) {
|
|
|
1555
1774
|
})
|
|
1556
1775
|
);
|
|
1557
1776
|
}
|
|
1558
|
-
if (shouldRecordDecisions && sessionId) {
|
|
1777
|
+
if (shouldRecordDecisions && sessionId && !isSharedVerdict(result)) {
|
|
1559
1778
|
recordDecision(config, sessionId, "granted").catch(() => {
|
|
1560
1779
|
});
|
|
1561
1780
|
}
|
|
@@ -1595,11 +1814,13 @@ function createMcpMiddleware(options) {
|
|
|
1595
1814
|
export {
|
|
1596
1815
|
MCP_VERIFIED_HOP_HEADER,
|
|
1597
1816
|
createMcpMiddleware,
|
|
1817
|
+
dashboardToolRules,
|
|
1598
1818
|
isVerifiedHopValidFor,
|
|
1599
1819
|
mcpDefaultEnforced,
|
|
1600
1820
|
mcpToPdlss,
|
|
1601
1821
|
parseMcpJsonRpc,
|
|
1602
1822
|
parseVerifiedHop,
|
|
1823
|
+
resolveGate,
|
|
1603
1824
|
serializeVerifiedHop
|
|
1604
1825
|
};
|
|
1605
1826
|
//# sourceMappingURL=mcp.mjs.map
|