@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/README.md +217 -4
  3. package/dist/adapter-interface/interface.d.mts +2 -2
  4. package/dist/adapter-interface/interface.d.ts +2 -2
  5. package/dist/adapters/express.d.mts +9 -1
  6. package/dist/adapters/express.d.ts +9 -1
  7. package/dist/adapters/express.js +160 -52
  8. package/dist/adapters/express.js.map +1 -1
  9. package/dist/adapters/express.mjs +160 -52
  10. package/dist/adapters/express.mjs.map +1 -1
  11. package/dist/adapters/http-pdlss.d.mts +7 -1
  12. package/dist/adapters/http-pdlss.d.ts +7 -1
  13. package/dist/adapters/http-pdlss.js +18 -2
  14. package/dist/adapters/http-pdlss.js.map +1 -1
  15. package/dist/adapters/http-pdlss.mjs +16 -1
  16. package/dist/adapters/http-pdlss.mjs.map +1 -1
  17. package/dist/adapters/kit-express.d.mts +93 -1
  18. package/dist/adapters/kit-express.d.ts +93 -1
  19. package/dist/adapters/kit-express.js +181 -2
  20. package/dist/adapters/kit-express.js.map +1 -1
  21. package/dist/adapters/kit-express.mjs +180 -2
  22. package/dist/adapters/kit-express.mjs.map +1 -1
  23. package/dist/adapters/mcp.d.mts +102 -9
  24. package/dist/adapters/mcp.d.ts +102 -9
  25. package/dist/adapters/mcp.js +242 -19
  26. package/dist/adapters/mcp.js.map +1 -1
  27. package/dist/adapters/mcp.mjs +240 -19
  28. package/dist/adapters/mcp.mjs.map +1 -1
  29. package/dist/adapters/nextjs.d.mts +1 -1
  30. package/dist/adapters/nextjs.d.ts +1 -1
  31. package/dist/adapters/nextjs.js +97 -14
  32. package/dist/adapters/nextjs.js.map +1 -1
  33. package/dist/adapters/nextjs.mjs +97 -14
  34. package/dist/adapters/nextjs.mjs.map +1 -1
  35. package/dist/adapters/sdk.d.mts +101 -2
  36. package/dist/adapters/sdk.d.ts +101 -2
  37. package/dist/adapters/sdk.js +199 -12
  38. package/dist/adapters/sdk.js.map +1 -1
  39. package/dist/adapters/sdk.mjs +199 -12
  40. package/dist/adapters/sdk.mjs.map +1 -1
  41. package/dist/agent/index.d.mts +1 -1
  42. package/dist/agent/index.d.ts +1 -1
  43. package/dist/agent/index.js +1 -1
  44. package/dist/agent/index.js.map +1 -1
  45. package/dist/agent/index.mjs +1 -1
  46. package/dist/agent/index.mjs.map +1 -1
  47. package/dist/bin/astrasync-claude-hook.js +94 -16
  48. package/dist/bin/astrasync-codex-hook.js +94 -16
  49. package/dist/bin/astrasync-guard.js +94 -16
  50. package/dist/bin/astrasync.js +127 -16
  51. package/dist/browser/background.js +94 -16
  52. package/dist/browser/background.js.map +1 -1
  53. package/dist/browser/background.mjs +94 -16
  54. package/dist/browser/background.mjs.map +1 -1
  55. package/dist/cli/index.js +1 -1
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/index.mjs +1 -1
  58. package/dist/cli/index.mjs.map +1 -1
  59. package/dist/codex/index.js +94 -16
  60. package/dist/codex/index.js.map +1 -1
  61. package/dist/codex/index.mjs +94 -16
  62. package/dist/codex/index.mjs.map +1 -1
  63. package/dist/cursor/extension.js +94 -16
  64. package/dist/cursor/extension.js.map +1 -1
  65. package/dist/cursor/extension.mjs +94 -16
  66. package/dist/cursor/extension.mjs.map +1 -1
  67. package/dist/edge-config.d.mts +1 -1
  68. package/dist/edge-config.d.ts +1 -1
  69. package/dist/edge-config.js +1 -1
  70. package/dist/edge-config.js.map +1 -1
  71. package/dist/edge-config.mjs +1 -1
  72. package/dist/edge-config.mjs.map +1 -1
  73. package/dist/edge-core/index.d.mts +1 -1
  74. package/dist/edge-core/index.d.ts +1 -1
  75. package/dist/edge-core/index.js +69 -12
  76. package/dist/edge-core/index.js.map +1 -1
  77. package/dist/edge-core/index.mjs +69 -12
  78. package/dist/edge-core/index.mjs.map +1 -1
  79. package/dist/gateway/gateway.d.mts +2 -2
  80. package/dist/gateway/gateway.d.ts +2 -2
  81. package/dist/gateway/gateway.js +94 -16
  82. package/dist/gateway/gateway.js.map +1 -1
  83. package/dist/gateway/gateway.mjs +94 -16
  84. package/dist/gateway/gateway.mjs.map +1 -1
  85. package/dist/git-trigger/git-hooks.d.mts +1 -1
  86. package/dist/git-trigger/git-hooks.d.ts +1 -1
  87. package/dist/index.d.mts +706 -34
  88. package/dist/index.d.ts +706 -34
  89. package/dist/index.js +1391 -205
  90. package/dist/index.js.map +1 -1
  91. package/dist/index.mjs +1374 -195
  92. package/dist/index.mjs.map +1 -1
  93. package/dist/local-evaluator/evaluator.d.mts +2 -2
  94. package/dist/local-evaluator/evaluator.d.ts +2 -2
  95. package/dist/registration/index.d.mts +11 -2
  96. package/dist/registration/index.d.ts +11 -2
  97. package/dist/registration/index.js +1 -1
  98. package/dist/registration/index.js.map +1 -1
  99. package/dist/registration/index.mjs +1 -1
  100. package/dist/registration/index.mjs.map +1 -1
  101. package/dist/transport/index.d.mts +1 -1
  102. package/dist/transport/index.d.ts +1 -1
  103. package/dist/transport/index.js +3 -2
  104. package/dist/transport/index.js.map +1 -1
  105. package/dist/transport/index.mjs +3 -2
  106. package/dist/transport/index.mjs.map +1 -1
  107. package/dist/{types-BRz2U0Pn.d.ts → types-B_HLhZ9n.d.ts} +1 -1
  108. package/dist/{types-BfILnheI.d.mts → types-CU17R4Nr.d.mts} +11 -0
  109. package/dist/{types-BfILnheI.d.ts → types-CU17R4Nr.d.ts} +11 -0
  110. package/dist/{types-Cns4l8gX.d.mts → types-CxjqmPl-.d.mts} +115 -13
  111. package/dist/{types-Cns4l8gX.d.ts → types-CxjqmPl-.d.ts} +115 -13
  112. package/dist/{types-3ObBpsnf.d.mts → types-D37aSQ9c.d.mts} +106 -3
  113. package/dist/{types-CfBpm3w5.d.mts → types-Djpz-L0w.d.mts} +1 -1
  114. package/dist/{types-BoxZEYPS.d.ts → types-Dn8tXnei.d.ts} +106 -3
  115. package/dist/ui/index.d.mts +1 -1
  116. package/dist/ui/index.d.ts +1 -1
  117. package/dist/verify.d.mts +50 -4
  118. package/dist/verify.d.ts +50 -4
  119. package/dist/verify.js +93 -14
  120. package/dist/verify.js.map +1 -1
  121. package/dist/verify.mjs +91 -14
  122. package/dist/verify.mjs.map +1 -1
  123. package/dist/webhooks.d.mts +16 -3
  124. package/dist/webhooks.d.ts +16 -3
  125. package/dist/webhooks.js +24 -13
  126. package/dist/webhooks.js.map +1 -1
  127. package/dist/webhooks.mjs +24 -13
  128. package/dist/webhooks.mjs.map +1 -1
  129. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var SDK_VERSION = "5.18.1";
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
- const hit = { ...cached };
606
- if (request.attemptId) {
607
- hit.attemptId = request.attemptId;
608
- } else {
609
- delete hit.attemptId;
610
- }
611
- return hit;
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
- return toolGate === "observe" ? { enforce: false, source: "toolGate" } : { enforce: true, config: toolGate, source: "toolGate" };
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 = "open",
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