@astrasyncai/verification-gateway 3.1.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 (79) 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 +23 -61
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +23 -61
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +12 -7
  10. package/dist/adapters/mcp.d.ts +12 -7
  11. package/dist/adapters/mcp.js +38 -100
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +38 -100
  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 +20 -29
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +20 -29
  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/browser/background.js +18 -21
  30. package/dist/browser/background.js.map +1 -1
  31. package/dist/browser/background.mjs +18 -21
  32. package/dist/browser/background.mjs.map +1 -1
  33. package/dist/browser/browser-adapter.d.mts +2 -2
  34. package/dist/browser/browser-adapter.d.ts +2 -2
  35. package/dist/cli/index.d.mts +2 -2
  36. package/dist/cli/index.d.ts +2 -2
  37. package/dist/cursor/cursor-adapter.d.mts +2 -2
  38. package/dist/cursor/cursor-adapter.d.ts +2 -2
  39. package/dist/cursor/extension.d.mts +2 -2
  40. package/dist/cursor/extension.d.ts +2 -2
  41. package/dist/cursor/extension.js +18 -21
  42. package/dist/cursor/extension.js.map +1 -1
  43. package/dist/cursor/extension.mjs +18 -21
  44. package/dist/cursor/extension.mjs.map +1 -1
  45. package/dist/{express-DavQ76oF.d.ts → express-BowlMHQF.d.ts} +1 -1
  46. package/dist/{express-DFVBlXr_.d.mts → express-CeoSdOAZ.d.mts} +1 -1
  47. package/dist/gateway/gateway.d.mts +2 -2
  48. package/dist/gateway/gateway.d.ts +2 -2
  49. package/dist/gateway/gateway.js +18 -21
  50. package/dist/gateway/gateway.js.map +1 -1
  51. package/dist/gateway/gateway.mjs +18 -21
  52. package/dist/gateway/gateway.mjs.map +1 -1
  53. package/dist/git-trigger/git-hooks.d.mts +2 -2
  54. package/dist/git-trigger/git-hooks.d.ts +2 -2
  55. package/dist/{index-BhL2R65s.d.mts → index-B51W8gn8.d.mts} +1 -1
  56. package/dist/{index-BhEgEiJL.d.ts → index-DBmlycVm.d.ts} +1 -1
  57. package/dist/{index-BVxantdv.d.mts → index-DtGziFEm.d.mts} +1 -1
  58. package/dist/{index-Dk2nIA4w.d.ts → index-DzXXBuLm.d.ts} +1 -1
  59. package/dist/index.d.mts +7 -7
  60. package/dist/index.d.ts +7 -7
  61. package/dist/index.js +50 -121
  62. package/dist/index.js.map +1 -1
  63. package/dist/index.mjs +50 -121
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/local-evaluator/evaluator.d.mts +2 -2
  66. package/dist/local-evaluator/evaluator.d.ts +2 -2
  67. package/dist/{nextjs-D-maqrNz.d.mts → nextjs-BW1rzr1I.d.mts} +1 -1
  68. package/dist/{nextjs-BXLH1hJj.d.ts → nextjs-V_K0qlAQ.d.ts} +1 -1
  69. package/dist/{sdk-767LaEP8.d.mts → sdk-ZYgI7G9f.d.ts} +14 -3
  70. package/dist/{sdk-K8IgssHI.d.ts → sdk-e5jg7sqW.d.mts} +14 -3
  71. package/dist/transport/index.d.mts +2 -2
  72. package/dist/transport/index.d.ts +2 -2
  73. package/dist/{types-CyFwZ_Yu.d.mts → types-BNiLZY0i.d.mts} +1 -1
  74. package/dist/{types-WIRp_BP_.d.ts → types-DJi-u3fz.d.ts} +1 -1
  75. package/dist/{types-Cuh7ELfr.d.mts → types-rFh4VMH4.d.mts} +5 -2
  76. package/dist/{types-Cuh7ELfr.d.ts → types-rFh4VMH4.d.ts} +5 -2
  77. package/dist/ui/index.d.mts +1 -1
  78. package/dist/ui/index.d.ts +1 -1
  79. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, RequestHandler } from 'express';
2
- import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-Cuh7ELfr.mjs';
2
+ import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-rFh4VMH4.mjs';
3
3
 
4
4
  /**
5
5
  * MCP server-side helpers — companion to `transport/mcp.ts` (which handles the
@@ -264,16 +264,21 @@ interface McpMiddlewareOptions extends GatewayConfig {
264
264
  * Per-tool gating for `tools/call` invocations. Tools not listed inherit
265
265
  * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
266
266
  *
267
- * Accepts both the shorthand access-level string and the full object shape:
267
+ * Accepts both the shorthand access-level string and the full object shape.
268
+ * NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
269
+ * must carry a resolvable PDLSS **purpose** (the backend requires it). So the
270
+ * bare string shorthand — which carries no purpose — only works if the caller
271
+ * supplies the purpose another way (an `X-Astra-Purpose` header, or
272
+ * `params._meta.astrasync.purpose`); otherwise the call fails fast with a
273
+ * `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
268
274
  * ```typescript
269
275
  * toolGates: {
270
- * browse_catalog: 'read-only', // shorthand
271
- * list_products: { minAccessLevel: 'none', // full shape
272
- * purpose: 'shopping',
276
+ * browse_catalog: 'read-only', // shorthand — purpose must come
277
+ * // from a header / _meta, else 400
278
+ * list_products: { purpose: 'shopping', // object form (recommended)
273
279
  * action: 'shopping.search',
274
280
  * resource: '/api/catalog' },
275
- * start_checkout: { minAccessLevel: 'standard',
276
- * purpose: 'shopping',
281
+ * start_checkout: { purpose: 'shopping',
277
282
  * action: 'shopping.purchase',
278
283
  * resource: '/api/checkout/*' },
279
284
  * }
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, RequestHandler } from 'express';
2
- import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-Cuh7ELfr.js';
2
+ import { A as AccessLevel, G as GatewayConfig, i as VerificationResult } from '../types-rFh4VMH4.js';
3
3
 
4
4
  /**
5
5
  * MCP server-side helpers — companion to `transport/mcp.ts` (which handles the
@@ -264,16 +264,21 @@ interface McpMiddlewareOptions extends GatewayConfig {
264
264
  * Per-tool gating for `tools/call` invocations. Tools not listed inherit
265
265
  * the default tier from `mcpRiskTier` (`tools/call` → `'standard'`).
266
266
  *
267
- * Accepts both the shorthand access-level string and the full object shape:
267
+ * Accepts both the shorthand access-level string and the full object shape.
268
+ * NOTE (3.2.0): the access-level band no longer gates, and a gated `tools/call`
269
+ * must carry a resolvable PDLSS **purpose** (the backend requires it). So the
270
+ * bare string shorthand — which carries no purpose — only works if the caller
271
+ * supplies the purpose another way (an `X-Astra-Purpose` header, or
272
+ * `params._meta.astrasync.purpose`); otherwise the call fails fast with a
273
+ * `PDLSS_PURPOSE_REQUIRED` 400. Prefer the **object form with `purpose`**:
268
274
  * ```typescript
269
275
  * toolGates: {
270
- * browse_catalog: 'read-only', // shorthand
271
- * list_products: { minAccessLevel: 'none', // full shape
272
- * purpose: 'shopping',
276
+ * browse_catalog: 'read-only', // shorthand — purpose must come
277
+ * // from a header / _meta, else 400
278
+ * list_products: { purpose: 'shopping', // object form (recommended)
273
279
  * action: 'shopping.search',
274
280
  * resource: '/api/catalog' },
275
- * start_checkout: { minAccessLevel: 'standard',
276
- * purpose: 'shopping',
281
+ * start_checkout: { purpose: 'shopping',
277
282
  * action: 'shopping.purchase',
278
283
  * resource: '/api/checkout/*' },
279
284
  * }
@@ -32,26 +32,15 @@ __export(mcp_exports, {
32
32
  module.exports = __toCommonJS(mcp_exports);
33
33
 
34
34
  // src/access-levels.ts
35
- var ACCESS_LEVEL_HIERARCHY = {
36
- none: 0,
37
- restricted: 1,
38
- "read-only": 2,
39
- standard: 3,
40
- full: 4,
41
- internal: 5
42
- };
43
35
  function getTrustLevel(score) {
44
36
  if (score >= 80) return "PLATINUM";
45
37
  if (score >= 60) return "GOLD";
46
38
  if (score >= 40) return "SILVER";
47
39
  return "BRONZE";
48
40
  }
49
- function hasMinimumAccess(actual, required) {
50
- return ACCESS_LEVEL_HIERARCHY[actual] >= ACCESS_LEVEL_HIERARCHY[required];
51
- }
52
41
 
53
42
  // src/version.ts
54
- var SDK_VERSION = "3.1.0";
43
+ var SDK_VERSION = "3.2.0";
55
44
 
56
45
  // src/well-known.ts
57
46
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -156,7 +145,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
156
145
  }
157
146
  }
158
147
  var verificationCache = /* @__PURE__ */ new Map();
159
- function getCacheKey(request) {
148
+ function getCacheKey(request, counterpartyId) {
160
149
  const c = request.credentials;
161
150
  return [
162
151
  c.astraId || "",
@@ -169,6 +158,14 @@ function getCacheKey(request) {
169
158
  request.jurisdiction || "",
170
159
  request.transactionValue ?? "",
171
160
  request.currency || "",
161
+ // SECURITY (cross-merchant cache leak): the merchant identity is sent via
162
+ // `config.counterpartyId`, NOT on the request, so it was previously absent
163
+ // from the key — two verifies for the SAME agent/purpose/action/value but
164
+ // DIFFERENT merchants collided, and a grant at a permissive merchant (low
165
+ // trust floor) was served for a stricter one. Same bug class as the
166
+ // duration omission (F-A1-07). counterpartyId affects the backend verdict
167
+ // (trust floor / per-route policy), so it MUST key the cache.
168
+ counterpartyId || "",
172
169
  request.counterpartyUrl || "",
173
170
  request.counterpartyType || "",
174
171
  request.isSubAgentRequest ? "1" : "0",
@@ -192,8 +189,8 @@ function getCacheKey(request) {
192
189
  request.callerMetadata?.agentCardUrl || ""
193
190
  ].join("|");
194
191
  }
195
- function getCachedResult(request) {
196
- const key = getCacheKey(request);
192
+ function getCachedResult(request, counterpartyId) {
193
+ const key = getCacheKey(request, counterpartyId);
197
194
  const cached = verificationCache.get(key);
198
195
  if (cached && cached.expiresAt > Date.now()) {
199
196
  return cached.result;
@@ -205,9 +202,9 @@ function getCachedResult(request) {
205
202
  }
206
203
  var DEFAULT_AUTONOMOUS_TTL_SECONDS = 60;
207
204
  var DEFAULT_STEP_UP_TTL_SECONDS = 300;
208
- function cacheResult(request, result, configuredTtl) {
205
+ function cacheResult(request, result, configuredTtl, counterpartyId) {
209
206
  const ttlSeconds = configuredTtl && configuredTtl > 0 ? configuredTtl : result.requiresStepUp ? DEFAULT_STEP_UP_TTL_SECONDS : DEFAULT_AUTONOMOUS_TTL_SECONDS;
210
- const key = getCacheKey(request);
207
+ const key = getCacheKey(request, counterpartyId);
211
208
  verificationCache.set(key, {
212
209
  result,
213
210
  expiresAt: Date.now() + ttlSeconds * 1e3
@@ -399,7 +396,7 @@ async function verify(config, request) {
399
396
  );
400
397
  }
401
398
  if (mergedConfig.cacheTtl !== 0) {
402
- const cached = getCachedResult(request);
399
+ const cached = getCachedResult(request, mergedConfig.counterpartyId);
403
400
  if (cached) {
404
401
  if (mergedConfig.debug) {
405
402
  console.log("[VerificationGateway] Returning cached result");
@@ -451,8 +448,8 @@ async function verify(config, request) {
451
448
  verifiedAt: /* @__PURE__ */ new Date(),
452
449
  // Extract sessionId so decisions can be recorded for denials too
453
450
  sessionId: apiResponse.sessionId,
454
- // v2.3.10 (defect #34, round-4): anonymous traffic has no session →
455
- // correlationId is the linking key for paired local_override events.
451
+ // Anonymous traffic has no session → correlationId is the per-attempt
452
+ // linking key (the sessionId-equivalent for anonymous callers).
456
453
  correlationId: apiResponse.correlationId,
457
454
  recommendation: apiResponse.recommendation,
458
455
  recommendationReasons: apiResponse.recommendationReasons
@@ -526,17 +523,14 @@ async function verify(config, request) {
526
523
  };
527
524
  } else if (result.recommendation === "step_up_required") {
528
525
  result.requiresStepUp = true;
529
- if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
530
- result.accessLevel = "read-only";
531
- }
532
526
  result.denialReasons = result.recommendationReasons || ["Step-up verification required"];
533
527
  }
534
528
  if (mergedConfig.cacheTtl !== 0 && result.recommendation !== "deny") {
535
- cacheResult(request, result, mergedConfig.cacheTtl);
529
+ cacheResult(request, result, mergedConfig.cacheTtl, mergedConfig.counterpartyId);
536
530
  }
537
531
  return result;
538
532
  }
539
- async function recordDecision(config, sessionId, decision, reason, override) {
533
+ async function recordDecision(config, sessionId, decision, reason) {
540
534
  const headers = { "Content-Type": "application/json" };
541
535
  if (config.apiKey) {
542
536
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -545,36 +539,7 @@ async function recordDecision(config, sessionId, decision, reason, override) {
545
539
  await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
546
540
  method: "POST",
547
541
  headers,
548
- body: JSON.stringify({
549
- decision,
550
- reason,
551
- ...override && {
552
- overriddenBy: override.overriddenBy,
553
- toolName: override.toolName,
554
- requestedLevel: override.requestedLevel,
555
- grantedLevel: override.grantedLevel
556
- }
557
- })
558
- }).catch(() => {
559
- });
560
- }
561
- async function recordAnonymousLocalOverride(config, correlationId, override, reason) {
562
- const headers = { "Content-Type": "application/json" };
563
- if (config.apiKey) {
564
- headers["Authorization"] = `Bearer ${config.apiKey}`;
565
- headers["X-API-Key"] = config.apiKey;
566
- }
567
- await fetch(`${config.apiBaseUrl}/agents/verify-access/local-override`, {
568
- method: "POST",
569
- headers,
570
- body: JSON.stringify({
571
- correlationId,
572
- reason,
573
- overriddenBy: override.overriddenBy,
574
- toolName: override.toolName,
575
- requestedLevel: override.requestedLevel,
576
- grantedLevel: override.grantedLevel
577
- })
542
+ body: JSON.stringify({ decision, reason })
578
543
  }).catch(() => {
579
544
  });
580
545
  }
@@ -791,7 +756,6 @@ function createMcpMiddleware(options) {
791
756
  return next();
792
757
  }
793
758
  req.mcpRequest = parsed;
794
- const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
795
759
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
796
760
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
797
761
  const bodyAstraId = parsed.agentIdFromBody;
@@ -828,7 +792,7 @@ function createMcpMiddleware(options) {
828
792
  return next();
829
793
  }
830
794
  }
831
- const { level: minAccessLevel, source: gateSource } = resolveMinAccessLevel(parsed, {
795
+ const { level: minAccessLevel } = resolveMinAccessLevel(parsed, {
832
796
  toolGates,
833
797
  methodGates
834
798
  });
@@ -864,6 +828,23 @@ function createMcpMiddleware(options) {
864
828
  resolved_action: pdlss.action
865
829
  });
866
830
  }
831
+ if (!pdlss.purpose) {
832
+ const id = req.body?.id ?? null;
833
+ res.status(400).json({
834
+ jsonrpc: "2.0",
835
+ id,
836
+ error: {
837
+ code: -32602,
838
+ message: "PDLSS_PURPOSE_REQUIRED",
839
+ data: {
840
+ dimension: "pdlss.purpose",
841
+ 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.",
842
+ resolvedAction: pdlss.action
843
+ }
844
+ }
845
+ });
846
+ return;
847
+ }
867
848
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
868
849
  const shouldRecordDecisions = recordDecisions !== false;
869
850
  const result = await verify(config, {
@@ -887,7 +868,6 @@ function createMcpMiddleware(options) {
887
868
  });
888
869
  req.agentVerification = result;
889
870
  const sessionId = result.sessionId;
890
- const correlationId = result.correlationId;
891
871
  if (!result.identityVerified || !result.policyAllowed) {
892
872
  if (shouldRecordDecisions && sessionId) {
893
873
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -908,48 +888,6 @@ function createMcpMiddleware(options) {
908
888
  }
909
889
  return next();
910
890
  }
911
- if (!hasMinimumAccess(result.accessLevel, minAccessLevel)) {
912
- const insufficientFailure = {
913
- dimension: "access_level.insufficient",
914
- message: `Tool requires accessLevel '${minAccessLevel}'; agent has '${result.accessLevel}'.`,
915
- 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."
916
- };
917
- result.failures = [...result.failures ?? [], insufficientFailure];
918
- result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
919
- if (!result.guidance && wellKnownUrls) {
920
- result.guidance = {
921
- message: insufficientFailure.message,
922
- registrationUrl: wellKnownUrls.registrationUrl,
923
- documentationUrl: wellKnownUrls.documentationUrl
924
- };
925
- }
926
- if (shouldRecordDecisions) {
927
- const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
928
- const override = {
929
- overriddenBy: overrideKind,
930
- ...parsed.toolName && { toolName: parsed.toolName },
931
- requestedLevel: minAccessLevel,
932
- grantedLevel: result.accessLevel
933
- };
934
- if (sessionId) {
935
- recordDecision(config, sessionId, "denied", result.denialReasons?.[0], override).catch(
936
- () => {
937
- }
938
- );
939
- } else if (correlationId) {
940
- recordAnonymousLocalOverride(
941
- config,
942
- correlationId,
943
- override,
944
- result.denialReasons?.[0]
945
- ).catch(() => {
946
- });
947
- }
948
- }
949
- dedupeFailures(result);
950
- onDenied(result, req, res);
951
- return;
952
- }
953
891
  if (effectiveAstraId) {
954
892
  res.setHeader(
955
893
  MCP_VERIFIED_HOP_HEADER,