@astrasyncai/verification-gateway 3.0.0 → 3.2.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 (87) hide show
  1. package/dist/adapter-interface/interface.d.mts +2 -2
  2. package/dist/adapter-interface/interface.d.ts +2 -2
  3. package/dist/adapters/express.d.mts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +145 -93
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +145 -93
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +29 -11
  10. package/dist/adapters/mcp.d.ts +29 -11
  11. package/dist/adapters/mcp.js +43 -102
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +43 -102
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.js +126 -56
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +126 -56
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +2 -2
  22. package/dist/adapters/sdk.d.ts +2 -2
  23. package/dist/adapters/sdk.js +25 -14
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +25 -14
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +2 -2
  28. package/dist/agent/index.d.ts +2 -2
  29. package/dist/agent/index.js +3 -0
  30. package/dist/agent/index.js.map +1 -1
  31. package/dist/agent/index.mjs +3 -0
  32. package/dist/agent/index.mjs.map +1 -1
  33. package/dist/browser/background.js +18 -21
  34. package/dist/browser/background.js.map +1 -1
  35. package/dist/browser/background.mjs +18 -21
  36. package/dist/browser/background.mjs.map +1 -1
  37. package/dist/browser/browser-adapter.d.mts +2 -2
  38. package/dist/browser/browser-adapter.d.ts +2 -2
  39. package/dist/cli/index.d.mts +2 -2
  40. package/dist/cli/index.d.ts +2 -2
  41. package/dist/cursor/cursor-adapter.d.mts +2 -2
  42. package/dist/cursor/cursor-adapter.d.ts +2 -2
  43. package/dist/cursor/extension.d.mts +2 -2
  44. package/dist/cursor/extension.d.ts +2 -2
  45. package/dist/cursor/extension.js +18 -21
  46. package/dist/cursor/extension.js.map +1 -1
  47. package/dist/cursor/extension.mjs +18 -21
  48. package/dist/cursor/extension.mjs.map +1 -1
  49. package/dist/{express-CrfwoNAR.d.ts → express-BowlMHQF.d.ts} +1 -1
  50. package/dist/{express-ienhAXps.d.mts → express-CeoSdOAZ.d.mts} +1 -1
  51. package/dist/gateway/gateway.d.mts +2 -2
  52. package/dist/gateway/gateway.d.ts +2 -2
  53. package/dist/gateway/gateway.js +18 -21
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +18 -21
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +2 -2
  58. package/dist/git-trigger/git-hooks.d.ts +2 -2
  59. package/dist/{index-CEg_WG6y.d.mts → index-B51W8gn8.d.mts} +1 -1
  60. package/dist/{index-DC5f8eoQ.d.ts → index-DBmlycVm.d.ts} +1 -1
  61. package/dist/{index-B5e2IDWU.d.mts → index-DtGziFEm.d.mts} +1 -1
  62. package/dist/{index-CCdZxvAr.d.ts → index-DzXXBuLm.d.ts} +1 -1
  63. package/dist/index.d.mts +7 -7
  64. package/dist/index.d.ts +7 -7
  65. package/dist/index.js +209 -191
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +209 -191
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/local-evaluator/evaluator.d.mts +2 -2
  70. package/dist/local-evaluator/evaluator.d.ts +2 -2
  71. package/dist/{nextjs-DSpisQst.d.mts → nextjs-BW1rzr1I.d.mts} +1 -1
  72. package/dist/{nextjs-66R1KW8e.d.ts → nextjs-V_K0qlAQ.d.ts} +1 -1
  73. package/dist/{sdk-5U_CBRpr.d.mts → sdk-ZYgI7G9f.d.ts} +14 -3
  74. package/dist/{sdk-Bm8np66n.d.ts → sdk-e5jg7sqW.d.mts} +14 -3
  75. package/dist/transport/index.d.mts +2 -2
  76. package/dist/transport/index.d.ts +2 -2
  77. package/dist/transport/index.js +10 -0
  78. package/dist/transport/index.js.map +1 -1
  79. package/dist/transport/index.mjs +10 -0
  80. package/dist/transport/index.mjs.map +1 -1
  81. package/dist/{types-CgDCUfo8.d.mts → types-BNiLZY0i.d.mts} +1 -1
  82. package/dist/{types-R5N4ET6x.d.ts → types-DJi-u3fz.d.ts} +1 -1
  83. package/dist/{types-B3USs-Kx.d.mts → types-rFh4VMH4.d.mts} +30 -2
  84. package/dist/{types-B3USs-Kx.d.ts → types-rFh4VMH4.d.ts} +30 -2
  85. package/dist/ui/index.d.mts +1 -1
  86. package/dist/ui/index.d.ts +1 -1
  87. package/package.json +1 -1
@@ -1,24 +1,13 @@
1
1
  // src/access-levels.ts
2
- var ACCESS_LEVEL_HIERARCHY = {
3
- none: 0,
4
- restricted: 1,
5
- "read-only": 2,
6
- standard: 3,
7
- full: 4,
8
- internal: 5
9
- };
10
2
  function getTrustLevel(score) {
11
3
  if (score >= 80) return "PLATINUM";
12
4
  if (score >= 60) return "GOLD";
13
5
  if (score >= 40) return "SILVER";
14
6
  return "BRONZE";
15
7
  }
16
- function hasMinimumAccess(actual, required) {
17
- return ACCESS_LEVEL_HIERARCHY[actual] >= ACCESS_LEVEL_HIERARCHY[required];
18
- }
19
8
 
20
9
  // src/version.ts
21
- var SDK_VERSION = "3.0.0";
10
+ var SDK_VERSION = "3.2.0";
22
11
 
23
12
  // src/well-known.ts
24
13
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -123,7 +112,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
123
112
  }
124
113
  }
125
114
  var verificationCache = /* @__PURE__ */ new Map();
126
- function getCacheKey(request) {
115
+ function getCacheKey(request, counterpartyId) {
127
116
  const c = request.credentials;
128
117
  return [
129
118
  c.astraId || "",
@@ -136,6 +125,14 @@ function getCacheKey(request) {
136
125
  request.jurisdiction || "",
137
126
  request.transactionValue ?? "",
138
127
  request.currency || "",
128
+ // SECURITY (cross-merchant cache leak): the merchant identity is sent via
129
+ // `config.counterpartyId`, NOT on the request, so it was previously absent
130
+ // from the key — two verifies for the SAME agent/purpose/action/value but
131
+ // DIFFERENT merchants collided, and a grant at a permissive merchant (low
132
+ // trust floor) was served for a stricter one. Same bug class as the
133
+ // duration omission (F-A1-07). counterpartyId affects the backend verdict
134
+ // (trust floor / per-route policy), so it MUST key the cache.
135
+ counterpartyId || "",
139
136
  request.counterpartyUrl || "",
140
137
  request.counterpartyType || "",
141
138
  request.isSubAgentRequest ? "1" : "0",
@@ -159,8 +156,8 @@ function getCacheKey(request) {
159
156
  request.callerMetadata?.agentCardUrl || ""
160
157
  ].join("|");
161
158
  }
162
- function getCachedResult(request) {
163
- const key = getCacheKey(request);
159
+ function getCachedResult(request, counterpartyId) {
160
+ const key = getCacheKey(request, counterpartyId);
164
161
  const cached = verificationCache.get(key);
165
162
  if (cached && cached.expiresAt > Date.now()) {
166
163
  return cached.result;
@@ -172,9 +169,9 @@ function getCachedResult(request) {
172
169
  }
173
170
  var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
174
171
  var DEFAULT_STEP_UP_TTL_SECONDS = 300;
175
- function cacheResult(request, result, configuredTtl) {
172
+ function cacheResult(request, result, configuredTtl, counterpartyId) {
176
173
  const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
177
- const key = getCacheKey(request);
174
+ const key = getCacheKey(request, counterpartyId);
178
175
  verificationCache.set(key, {
179
176
  result,
180
177
  expiresAt: Date.now() + ttlSeconds * 1e3
@@ -366,7 +363,7 @@ async function verify(config, request) {
366
363
  );
367
364
  }
368
365
  if (mergedConfig.cacheTtl !== 0) {
369
- const cached = getCachedResult(request);
366
+ const cached = getCachedResult(request, mergedConfig.counterpartyId);
370
367
  if (cached) {
371
368
  if (mergedConfig.debug) {
372
369
  console.log("[VerificationGateway] Returning cached result");
@@ -418,8 +415,8 @@ async function verify(config, request) {
418
415
  verifiedAt: /* @__PURE__ */ new Date(),
419
416
  // Extract sessionId so decisions can be recorded for denials too
420
417
  sessionId: apiResponse.sessionId,
421
- // v2.3.10 (defect #34, round-4): anonymous traffic has no session →
422
- // correlationId is the linking key for paired local_override events.
418
+ // Anonymous traffic has no session → correlationId is the per-attempt
419
+ // linking key (the sessionId-equivalent for anonymous callers).
423
420
  correlationId: apiResponse.correlationId,
424
421
  recommendation: apiResponse.recommendation,
425
422
  recommendationReasons: apiResponse.recommendationReasons
@@ -493,17 +490,14 @@ async function verify(config, request) {
493
490
  };
494
491
  } else if (result.recommendation === "step_up_required") {
495
492
  result.requiresStepUp = true;
496
- if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
497
- result.accessLevel = "read-only";
498
- }
499
493
  result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
500
494
  }
501
495
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
502
- cacheResult(request, result, mergedConfig.cacheTtl);
496
+ cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
503
497
  }
504
498
  return result;
505
499
  }
506
- async function recordDecision(config, sessionId, decision, reason, override) {
500
+ async function recordDecision(config, sessionId, decision, reason) {
507
501
  const headers = { "Content-Type": "application/json" };
508
502
  if (config.apiKey) {
509
503
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -512,36 +506,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
512
506
  await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
513
507
  method: "POST",
514
508
  headers,
515
- body: JSON.stringify({
516
- decision,
517
- reason,
518
- ...override && {
519
- overriddenBy: override.overriddenBy,
520
- toolName: override.toolName,
521
- requestedLevel: override.requestedLevel,
522
- grantedLevel: override.grantedLevel
523
- }
524
- })
525
- }).catch(() => {
526
- });
527
- }
528
- async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
529
- const headers = { "Content-Type": "application/json" };
530
- if (config.apiKey) {
531
- headers["Authorization"] = `Bearer ${config.apiKey}`;
532
- headers["X-API-Key"] = config.apiKey;
533
- }
534
- await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
535
- method: "POST",
536
- headers,
537
- body: JSON.stringify({
538
- correlationId,
539
- reason,
540
- overriddenBy: override.overriddenBy,
541
- toolName: override.toolName,
542
- requestedLevel: override.requestedLevel,
543
- grantedLevel: override.grantedLevel
544
- })
509
+ body: JSON.stringify({ decision, reason })
545
510
  }).catch(() => {
546
511
  });
547
512
  }
@@ -644,7 +609,10 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
644
609
  }
645
610
  let action;
646
611
  let actionSource;
647
- if (headerAction) {
612
+ if (toolGate?.action !== void 0) {
613
+ action = toolGate.action;
614
+ actionSource = "tool_gate";
615
+ } else if (headerAction) {
648
616
  action = headerAction;
649
617
  actionSource = "header";
650
618
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
@@ -755,7 +723,6 @@ function createMcpMiddleware(options) {
755
723
  return next();
756
724
  }
757
725
  req.mcpRequest = parsed;
758
- const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
759
726
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
760
727
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
761
728
  const bodyAstraId = parsed.agentIdFromBody;
@@ -792,7 +759,7 @@ function createMcpMiddleware(options) {
792
759
  return next();
793
760
  }
794
761
  }
795
- const { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(parsed, {
762
+ const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
796
763
  toolGates,
797
764
  methodGates
798
765
  });
@@ -818,7 +785,7 @@ function createMcpMiddleware(options) {
818
785
  req.path,
819
786
  headerPurpose,
820
787
  headerAction,
821
- gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
788
+ gate ? { purpose: gate.purpose, action: gate.action, resource: gate.resource } : void 0
822
789
  );
823
790
  if (config.debug) {
824
791
  console.debug("[mcp-middleware] pdlss resolved", {
@@ -828,6 +795,23 @@ function createMcpMiddleware(options) {
828
795
  resolved_action: pdlss.action
829
796
  });
830
797
  }
798
+ if (!pdlss.purpose) {
799
+ const id = req.body?.id ?? null;
800
+ res.status(400).json({
801
+ jsonrpc: "2.0",
802
+ id,
803
+ error: {
804
+ code: -32602,
805
+ message: "PDLSS_PURPOSE_REQUIRED",
806
+ data: {
807
+ dimension: "pdlss.purpose",
808
+ detail: "This tool is access-gated but the call declared no PDLSS purpose. Supply a bare-category purpose via the X-Astra-Purpose header or params._meta.astrasync.purpose, or have the merchant set the tool\u2019s purpose in its toolGate config.",
809
+ resolvedAction: pdlss.action
810
+ }
811
+ }
812
+ });
813
+ return;
814
+ }
831
815
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
832
816
  const shouldRecordDecisions = recordDecisions !== false;
833
817
  const result = await verify(config, {
@@ -851,7 +835,6 @@ function createMcpMiddleware(options) {
851
835
  });
852
836
  req.agentVerification = result;
853
837
  const sessionId = result.sessionId;
854
- const correlationId = result.correlationId;
855
838
  if (!result.identityVerified || !result.policyAllowed) {
856
839
  if (shouldRecordDecisions && sessionId) {
857
840
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -872,48 +855,6 @@ function createMcpMiddleware(options) {
872
855
  }
873
856
  return next();
874
857
  }
875
- if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
876
- const insufficientFailure = {
877
- dimension: "access_level.insufficient",
878
- message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
879
- guidance: "Request elevated access via step-up verification (coming soon \u2014 ships this month). Step-up lets the agent owner approve a one-time elevation for this specific counterparty + purpose without changing the agent's baseline trust score."
880
- };
881
- result.failures = [...result.failures ?? [], insufficientFailure];
882
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
883
- if (!result.guidance && wellKnownUrls) {
884
- result.guidance = {
885
- message: insufficientFailure.message,
886
- registrationUrl: wellKnownUrls.registrationUrl,
887
- documentationUrl: wellKnownUrls.documentationUrl
888
- };
889
- }
890
- if (shouldRecordDecisions) {
891
- const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
892
- const override = {
893
- overriddenBy: overrideKind,
894
- ...parsed.toolName && { toolName: parsed.toolName },
895
- requestedLevel: minAccessLevel,
896
- grantedLevel: result.accessLevel
897
- };
898
- if (sessionId) {
899
- recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
900
- () => {
901
- }
902
- );
903
- } else if (correlationId) {
904
- recordAnonymousLocalOverride(
905
- config,
906
- correlationId,
907
- override,
908
- result.denialReasons?.[0]
909
- ).catch(() => {
910
- });
911
- }
912
- }
913
- dedupeFailures(result);
914
- onDenied(result, req, res);
915
- return;
916
- }
917
858
  if (effectiveAstraId) {
918
859
  res.setHeader(
919
860
  MCP_VERIFIED_HOP_HEADER,