@astrasyncai/verification-gateway 2.4.14 → 2.5.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/dist/index.mjs CHANGED
@@ -128,6 +128,65 @@ function getCapabilities(accessLevel) {
128
128
  // src/version.ts
129
129
  var SDK_VERSION = "2.4.13";
130
130
 
131
+ // src/well-known.ts
132
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
133
+ var cache = /* @__PURE__ */ new Map();
134
+ var inflight = /* @__PURE__ */ new Map();
135
+ function wellKnownUrl(apiBaseUrl) {
136
+ const base = apiBaseUrl.replace(/\/api\/?$/, "");
137
+ return `${base}/.well-known/agentic-commerce`;
138
+ }
139
+ async function fetchWellKnown(apiBaseUrl) {
140
+ const url = wellKnownUrl(apiBaseUrl);
141
+ const response = await fetch(url, {
142
+ method: "GET",
143
+ headers: { Accept: "application/json" },
144
+ signal: AbortSignal.timeout(5e3)
145
+ });
146
+ if (!response.ok) {
147
+ throw new Error(
148
+ `AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
149
+ );
150
+ }
151
+ const data = await response.json();
152
+ if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
153
+ throw new Error(
154
+ `/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
155
+ );
156
+ }
157
+ return data;
158
+ }
159
+ function prefetchWellKnown(apiBaseUrl) {
160
+ const existing = inflight.get(apiBaseUrl);
161
+ if (existing) return existing;
162
+ const promise = fetchWellKnown(apiBaseUrl).then((data) => {
163
+ cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
164
+ inflight.delete(apiBaseUrl);
165
+ return data;
166
+ }).catch((err) => {
167
+ inflight.delete(apiBaseUrl);
168
+ throw err;
169
+ });
170
+ inflight.set(apiBaseUrl, promise);
171
+ return promise;
172
+ }
173
+ async function getWellKnownUrls(apiBaseUrl) {
174
+ const entry = cache.get(apiBaseUrl);
175
+ if (entry) {
176
+ if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
177
+ prefetchWellKnown(apiBaseUrl).catch(() => {
178
+ });
179
+ }
180
+ return entry.data;
181
+ }
182
+ const pending = inflight.get(apiBaseUrl);
183
+ if (pending) return pending;
184
+ return prefetchWellKnown(apiBaseUrl);
185
+ }
186
+ function getCachedWellKnownUrls(apiBaseUrl) {
187
+ return cache.get(apiBaseUrl)?.data;
188
+ }
189
+
131
190
  // src/verify.ts
132
191
  var DEFAULT_CONFIG = {
133
192
  apiBaseUrl: "https://astrasync.ai/api",
@@ -269,21 +328,22 @@ function extractCredentials(headers, query) {
269
328
  function hasCredentials(credentials) {
270
329
  return !!(credentials.astraId || credentials.apiKey || credentials.jwt);
271
330
  }
272
- function createGuidanceResponse(config, reason, options = {}) {
331
+ function createGuidanceResponse(_config, reason, options = {}) {
273
332
  const source = options.source ?? "no_credentials";
274
333
  const isApiError = source === "api_error";
334
+ const urls = options.urls;
275
335
  const guidance = isApiError ? {
276
336
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
277
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
278
- documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
337
+ registrationUrl: urls?.registrationUrl ?? "",
338
+ documentationUrl: urls?.documentationUrl ?? "",
279
339
  steps: [
280
340
  "Retry the request with exponential backoff",
281
341
  "If failures persist, share the correlationId with support"
282
342
  ]
283
343
  } : {
284
344
  message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
285
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
286
- documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
345
+ registrationUrl: urls?.registrationUrl ?? "",
346
+ documentationUrl: urls?.documentationUrl ?? "",
287
347
  steps: [
288
348
  "Register for an AstraSync account",
289
349
  "Create and register your agent",
@@ -325,7 +385,7 @@ async function callVerifyAccessAPI(config, request) {
325
385
  const { credentials, ...requestData } = request;
326
386
  const body = {
327
387
  ...credentials.astraId && { agentId: credentials.astraId },
328
- purpose: requestData.purpose || "general"
388
+ ...requestData.purpose && { purpose: requestData.purpose }
329
389
  };
330
390
  if (requestData.action) body.action = requestData.action;
331
391
  if (requestData.resourceType) body.resourceType = requestData.resourceType;
@@ -405,6 +465,7 @@ async function callVerifyAccessAPI(config, request) {
405
465
  }
406
466
  async function verify(config, request) {
407
467
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
468
+ const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
408
469
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
409
470
  if (mergedConfig.strictInit) {
410
471
  await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
@@ -441,7 +502,8 @@ async function verify(config, request) {
441
502
  if (!apiResponse.success) {
442
503
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
443
504
  source: "api_error",
444
- correlationId: apiResponse.correlationId
505
+ correlationId: apiResponse.correlationId,
506
+ urls
445
507
  });
446
508
  }
447
509
  if (!apiResponse.access?.allowed) {
@@ -464,8 +526,8 @@ async function verify(config, request) {
464
526
  requiresApproval: apiResponse.access?.requiresApproval,
465
527
  guidance: {
466
528
  message: apiResponse.access?.reason || "Access denied by PDLSS policy",
467
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
468
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
529
+ registrationUrl: urls?.registrationUrl ?? "",
530
+ documentationUrl: urls?.documentationUrl ?? ""
469
531
  },
470
532
  verifiedAt: /* @__PURE__ */ new Date(),
471
533
  // Extract sessionId so decisions can be recorded for denials too
@@ -536,12 +598,12 @@ async function verify(config, request) {
536
598
  ];
537
599
  result.guidance = result.runtimeChallenge ? {
538
600
  message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
539
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
540
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
601
+ registrationUrl: urls?.registrationUrl ?? "",
602
+ documentationUrl: urls?.documentationUrl ?? ""
541
603
  } : {
542
604
  message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
543
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
544
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
605
+ registrationUrl: urls?.registrationUrl ?? "",
606
+ documentationUrl: urls?.documentationUrl ?? ""
545
607
  };
546
608
  } else if (result.recommendation === "step_up_required") {
547
609
  result.requiresStepUp = true;
@@ -824,6 +886,9 @@ function dedupeFailures(result) {
824
886
  return true;
825
887
  });
826
888
  }
889
+ if (result.denialReasons && result.denialReasons.length > 1) {
890
+ result.denialReasons = [...new Set(result.denialReasons)];
891
+ }
827
892
  }
828
893
  function defaultOnDenied(result, _req, res) {
829
894
  const statusCode = !result.identityVerified ? 401 : 403;
@@ -855,6 +920,10 @@ function createMiddleware(options) {
855
920
  caseInsensitiveRouteMatch = false,
856
921
  ...config
857
922
  } = options;
923
+ if (config.apiBaseUrl) {
924
+ prefetchWellKnown(config.apiBaseUrl).catch(() => {
925
+ });
926
+ }
858
927
  let cachedRoutes = [];
859
928
  let lastFetchAt = 0;
860
929
  let refreshing = null;
@@ -917,6 +986,7 @@ function createMiddleware(options) {
917
986
  }
918
987
  return next();
919
988
  }
989
+ const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
920
990
  const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
921
991
  const shouldEnforce = routeConfig.minAccessLevel !== "none";
922
992
  if (routeConfig.minAccessLevel === "none" && (!config.evaluateAlwaysIfCredentialed || !credentials.astraId)) {
@@ -938,8 +1008,8 @@ function createMiddleware(options) {
938
1008
  denialReasons: preCheckFailures.map((f) => f.message),
939
1009
  guidance: {
940
1010
  message: "Request exceeds counterparty-defined PDLSS limits.",
941
- registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
942
- documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
1011
+ registrationUrl: wellKnownUrls?.registrationUrl ?? "",
1012
+ documentationUrl: wellKnownUrls?.documentationUrl ?? ""
943
1013
  },
944
1014
  verifiedAt: /* @__PURE__ */ new Date()
945
1015
  };
@@ -1019,6 +1089,13 @@ function createMiddleware(options) {
1019
1089
  };
1020
1090
  result.failures = [...result.failures ?? [], insufficientFailure];
1021
1091
  result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
1092
+ if (!result.guidance && wellKnownUrls) {
1093
+ result.guidance = {
1094
+ message: insufficientFailure.message,
1095
+ registrationUrl: wellKnownUrls.registrationUrl,
1096
+ documentationUrl: wellKnownUrls.documentationUrl
1097
+ };
1098
+ }
1022
1099
  if (shouldRecordDecisions && sessionId) {
1023
1100
  recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
1024
1101
  });
@@ -1036,6 +1113,13 @@ function createMiddleware(options) {
1036
1113
  };
1037
1114
  result.failures = [...result.failures ?? [], trustFailure];
1038
1115
  result.denialReasons = [trustFailure.message];
1116
+ if (!result.guidance && wellKnownUrls) {
1117
+ result.guidance = {
1118
+ message: trustFailure.message,
1119
+ registrationUrl: wellKnownUrls.registrationUrl,
1120
+ documentationUrl: wellKnownUrls.documentationUrl
1121
+ };
1122
+ }
1039
1123
  if (shouldRecordDecisions && sessionId) {
1040
1124
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
1041
1125
  });
@@ -1076,6 +1160,14 @@ function createMiddleware(options) {
1076
1160
  verifiedAt: /* @__PURE__ */ new Date(),
1077
1161
  correlationId
1078
1162
  };
1163
+ const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
1164
+ if (catchUrls) {
1165
+ result.guidance = {
1166
+ message: `Middleware threw ${errorClass} \u2014 failing closed`,
1167
+ registrationUrl: catchUrls.registrationUrl,
1168
+ documentationUrl: catchUrls.documentationUrl
1169
+ };
1170
+ }
1079
1171
  dedupeFailures(result);
1080
1172
  return onDenied(result, req, res);
1081
1173
  }
@@ -4083,7 +4175,7 @@ async function exportJwkFromKeyLike(keyLike) {
4083
4175
 
4084
4176
  // src/transport/registry/mastercard.ts
4085
4177
  function createMastercardRegistry(options = {}) {
4086
- const cache = /* @__PURE__ */ new Map();
4178
+ const cache2 = /* @__PURE__ */ new Map();
4087
4179
  const ttlSec = options.cacheTtlSec ?? 3600;
4088
4180
  const fetchFn = options.fetch ?? globalThis.fetch;
4089
4181
  let warned = false;
@@ -4100,7 +4192,7 @@ function createMastercardRegistry(options = {}) {
4100
4192
  }
4101
4193
  return null;
4102
4194
  }
4103
- const cached = cache.get(kid);
4195
+ const cached = cache2.get(kid);
4104
4196
  if (cached && cached.expiresAt > Date.now()) return cached.jwk;
4105
4197
  try {
4106
4198
  const res = await fetchFn(options.registryUrl);
@@ -4109,7 +4201,7 @@ function createMastercardRegistry(options = {}) {
4109
4201
  const keys = body.keys ?? [];
4110
4202
  for (const k of keys) {
4111
4203
  if (k.kid === kid) {
4112
- cache.set(kid, { jwk: k, expiresAt: Date.now() + ttlSec * 1e3 });
4204
+ cache2.set(kid, { jwk: k, expiresAt: Date.now() + ttlSec * 1e3 });
4113
4205
  return k;
4114
4206
  }
4115
4207
  }
@@ -4124,7 +4216,7 @@ function createMastercardRegistry(options = {}) {
4124
4216
  // src/transport/registry/web-bot-auth.ts
4125
4217
  var DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
4126
4218
  function createWebBotAuthRegistry(options = {}) {
4127
- const cache = /* @__PURE__ */ new Map();
4219
+ const cache2 = /* @__PURE__ */ new Map();
4128
4220
  const ttlSec = options.cacheTtlSec ?? 3600;
4129
4221
  const fetchFn = options.fetch ?? globalThis.fetch;
4130
4222
  return {
@@ -4133,7 +4225,7 @@ function createWebBotAuthRegistry(options = {}) {
4133
4225
  if (!kid) return null;
4134
4226
  const directoryUrl = resolveDirectoryUrl(options.directoryUrl, context?.origin);
4135
4227
  if (!directoryUrl) return null;
4136
- const cached = cache.get(directoryUrl);
4228
+ const cached = cache2.get(directoryUrl);
4137
4229
  const now = Date.now();
4138
4230
  if (cached && cached.expiresAt > now) {
4139
4231
  return findKeyByKid(cached.keys, kid);
@@ -4143,7 +4235,7 @@ function createWebBotAuthRegistry(options = {}) {
4143
4235
  if (!res.ok) return null;
4144
4236
  const body = await res.json();
4145
4237
  const keys = body.keys ?? [];
4146
- cache.set(directoryUrl, { keys, expiresAt: now + ttlSec * 1e3 });
4238
+ cache2.set(directoryUrl, { keys, expiresAt: now + ttlSec * 1e3 });
4147
4239
  return findKeyByKid(keys, kid);
4148
4240
  } catch {
4149
4241
  return null;
@@ -4282,19 +4374,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
4282
4374
  }
4283
4375
  return { value: void 0, source: void 0 };
4284
4376
  }
4285
- function mcpToPdlss(parsed, headerPurpose, headerAction) {
4286
- const resource = parsed.toolName ? `mcp:tool/${parsed.toolName}` : `mcp:method/${parsed.method}`;
4377
+ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
4378
+ const resource = toolGate?.resource ?? requestPath;
4287
4379
  let purpose;
4288
4380
  let purposeSource;
4289
- if (headerPurpose) {
4381
+ if (toolGate?.purpose !== void 0) {
4382
+ purpose = toolGate.purpose;
4383
+ purposeSource = "tool_gate";
4384
+ } else if (headerPurpose) {
4290
4385
  purpose = headerPurpose;
4291
4386
  purposeSource = "header";
4292
4387
  } else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
4293
4388
  purpose = parsed.purposeFromBody;
4294
4389
  purposeSource = parsed.purposeSourceFromBody;
4295
4390
  } else {
4296
- purpose = "mcp_invoke";
4297
- purposeSource = "default_mcp_invoke";
4391
+ purpose = void 0;
4392
+ purposeSource = void 0;
4298
4393
  }
4299
4394
  let action;
4300
4395
  let actionSource;
@@ -4319,6 +4414,9 @@ function mcpRiskTier(parsed) {
4319
4414
  }
4320
4415
 
4321
4416
  // src/adapters/mcp.ts
4417
+ function normalizeToolGate(gate) {
4418
+ return typeof gate === "string" ? { minAccessLevel: gate } : gate;
4419
+ }
4322
4420
  function readSingleHeader(value) {
4323
4421
  if (typeof value === "string") return value;
4324
4422
  if (Array.isArray(value)) return value[0];
@@ -4334,6 +4432,9 @@ function dedupeFailures2(result) {
4334
4432
  return true;
4335
4433
  });
4336
4434
  }
4435
+ if (result.denialReasons && result.denialReasons.length > 1) {
4436
+ result.denialReasons = [...new Set(result.denialReasons)];
4437
+ }
4337
4438
  }
4338
4439
  function defaultMcpDenied(result, req, res) {
4339
4440
  const id = req.body?.id ?? null;
@@ -4359,11 +4460,17 @@ function defaultMcpDenied(result, req, res) {
4359
4460
  });
4360
4461
  }
4361
4462
  function resolveMinAccessLevel(parsed, opts) {
4362
- if (parsed.toolName && opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
4363
- return { level: opts.toolGates[parsed.toolName], source: "toolGate" };
4463
+ if (!parsed.toolName) {
4464
+ if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
4465
+ return { level: opts.methodGates[parsed.method], source: "methodGate" };
4466
+ }
4467
+ return { level: "none", source: "discovery_default" };
4364
4468
  }
4365
- if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
4366
- return { level: opts.methodGates[parsed.method], source: "methodGate" };
4469
+ if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
4470
+ return {
4471
+ level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
4472
+ source: "toolGate"
4473
+ };
4367
4474
  }
4368
4475
  return { level: mcpRiskTier(parsed), source: "tier" };
4369
4476
  }
@@ -4381,6 +4488,10 @@ function createMcpMiddleware(options) {
4381
4488
  failOnError = "open",
4382
4489
  ...config
4383
4490
  } = options;
4491
+ if (config.apiBaseUrl) {
4492
+ prefetchWellKnown(config.apiBaseUrl).catch(() => {
4493
+ });
4494
+ }
4384
4495
  return async (req, res, next) => {
4385
4496
  try {
4386
4497
  if (skip) return next();
@@ -4393,6 +4504,7 @@ function createMcpMiddleware(options) {
4393
4504
  return next();
4394
4505
  }
4395
4506
  req.mcpRequest = parsed;
4507
+ const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
4396
4508
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
4397
4509
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
4398
4510
  const bodyAstraId = parsed.agentIdFromBody;
@@ -4446,9 +4558,17 @@ function createMcpMiddleware(options) {
4446
4558
  }
4447
4559
  return next();
4448
4560
  }
4561
+ const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
4562
+ const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
4449
4563
  const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
4450
4564
  const headerAction = readSingleHeader(req.headers["x-astra-action"]);
4451
- const pdlss = mcpToPdlss(parsed, headerPurpose, headerAction);
4565
+ const pdlss = mcpToPdlss(
4566
+ parsed,
4567
+ req.path,
4568
+ headerPurpose,
4569
+ headerAction,
4570
+ gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
4571
+ );
4452
4572
  if (config.debug) {
4453
4573
  console.debug("[mcp-middleware] pdlss resolved", {
4454
4574
  purpose_source: pdlss.purposeSource,
@@ -4509,6 +4629,13 @@ function createMcpMiddleware(options) {
4509
4629
  };
4510
4630
  result.failures = [...result.failures ?? [], insufficientFailure];
4511
4631
  result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
4632
+ if (!result.guidance && wellKnownUrls) {
4633
+ result.guidance = {
4634
+ message: insufficientFailure.message,
4635
+ registrationUrl: wellKnownUrls.registrationUrl,
4636
+ documentationUrl: wellKnownUrls.documentationUrl
4637
+ };
4638
+ }
4512
4639
  if (shouldRecordDecisions) {
4513
4640
  const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
4514
4641
  const override = {
@@ -4577,6 +4704,14 @@ function createMcpMiddleware(options) {
4577
4704
  verifiedAt: /* @__PURE__ */ new Date(),
4578
4705
  correlationId
4579
4706
  };
4707
+ const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
4708
+ if (catchUrls) {
4709
+ result.guidance = {
4710
+ message: `Middleware threw ${errorClass} \u2014 failing closed`,
4711
+ registrationUrl: catchUrls.registrationUrl,
4712
+ documentationUrl: catchUrls.documentationUrl
4713
+ };
4714
+ }
4580
4715
  dedupeFailures2(result);
4581
4716
  return onDenied(result, req, res);
4582
4717
  }
@@ -5286,11 +5421,14 @@ export {
5286
5421
  extractCredentials,
5287
5422
  extractMcpCredentials,
5288
5423
  getAccessLevelForScore,
5424
+ getCachedWellKnownUrls,
5289
5425
  getCapabilities,
5290
5426
  getTrustLevel,
5427
+ getWellKnownUrls,
5291
5428
  hasCredentials,
5292
5429
  hasMinimumAccess,
5293
5430
  nextjs_exports as nextjs,
5431
+ prefetchWellKnown,
5294
5432
  quickVerify,
5295
5433
  recordDecision2 as recordDecision,
5296
5434
  sdk_exports as sdk,