@astrasyncai/verification-gateway 5.11.0 → 5.12.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 (98) hide show
  1. package/README.md +4 -0
  2. package/dist/adapters/express.d.mts +1 -1
  3. package/dist/adapters/express.d.ts +1 -1
  4. package/dist/adapters/express.js +359 -30
  5. package/dist/adapters/express.js.map +1 -1
  6. package/dist/adapters/express.mjs +359 -30
  7. package/dist/adapters/express.mjs.map +1 -1
  8. package/dist/adapters/mcp.d.mts +5 -1
  9. package/dist/adapters/mcp.d.ts +5 -1
  10. package/dist/adapters/mcp.js +168 -41
  11. package/dist/adapters/mcp.js.map +1 -1
  12. package/dist/adapters/mcp.mjs +168 -41
  13. package/dist/adapters/mcp.mjs.map +1 -1
  14. package/dist/adapters/nextjs.d.mts +1 -1
  15. package/dist/adapters/nextjs.d.ts +1 -1
  16. package/dist/adapters/nextjs.js +352 -13
  17. package/dist/adapters/nextjs.js.map +1 -1
  18. package/dist/adapters/nextjs.mjs +352 -13
  19. package/dist/adapters/nextjs.mjs.map +1 -1
  20. package/dist/adapters/sdk.d.mts +9 -1
  21. package/dist/adapters/sdk.d.ts +9 -1
  22. package/dist/adapters/sdk.js +100 -8
  23. package/dist/adapters/sdk.js.map +1 -1
  24. package/dist/adapters/sdk.mjs +100 -8
  25. package/dist/adapters/sdk.mjs.map +1 -1
  26. package/dist/agent/index.js +1 -1
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +1 -1
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +284 -7
  31. package/dist/bin/astrasync-codex-hook.js +284 -7
  32. package/dist/bin/astrasync-guard.js +284 -7
  33. package/dist/bin/astrasync.js +297 -9
  34. package/dist/browser/background.js +284 -7
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +284 -7
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/cli/index.js +1 -1
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/index.mjs +1 -1
  41. package/dist/cli/index.mjs.map +1 -1
  42. package/dist/codex/index.js +284 -7
  43. package/dist/codex/index.js.map +1 -1
  44. package/dist/codex/index.mjs +284 -7
  45. package/dist/codex/index.mjs.map +1 -1
  46. package/dist/cursor/extension.js +284 -7
  47. package/dist/cursor/extension.js.map +1 -1
  48. package/dist/cursor/extension.mjs +284 -7
  49. package/dist/cursor/extension.mjs.map +1 -1
  50. package/dist/edge-config.d.mts +1 -1
  51. package/dist/edge-config.d.ts +1 -1
  52. package/dist/edge-config.js +1 -1
  53. package/dist/edge-config.js.map +1 -1
  54. package/dist/edge-config.mjs +1 -1
  55. package/dist/edge-config.mjs.map +1 -1
  56. package/dist/edge-core/index.d.mts +128 -13
  57. package/dist/edge-core/index.d.ts +128 -13
  58. package/dist/edge-core/index.js +337 -55
  59. package/dist/edge-core/index.js.map +1 -1
  60. package/dist/edge-core/index.mjs +334 -55
  61. package/dist/edge-core/index.mjs.map +1 -1
  62. package/dist/gateway/gateway.js +284 -7
  63. package/dist/gateway/gateway.js.map +1 -1
  64. package/dist/gateway/gateway.mjs +284 -7
  65. package/dist/gateway/gateway.mjs.map +1 -1
  66. package/dist/git-trigger/git-hooks.d.mts +1 -1
  67. package/dist/git-trigger/git-hooks.d.ts +1 -1
  68. package/dist/index.d.mts +118 -5
  69. package/dist/index.d.ts +118 -5
  70. package/dist/index.js +486 -63
  71. package/dist/index.js.map +1 -1
  72. package/dist/index.mjs +480 -63
  73. package/dist/index.mjs.map +1 -1
  74. package/dist/platform-signatures.js +4 -1
  75. package/dist/platform-signatures.js.map +1 -1
  76. package/dist/platform-signatures.mjs +4 -1
  77. package/dist/platform-signatures.mjs.map +1 -1
  78. package/dist/registration/index.js +1 -1
  79. package/dist/registration/index.js.map +1 -1
  80. package/dist/registration/index.mjs +1 -1
  81. package/dist/registration/index.mjs.map +1 -1
  82. package/dist/transport/index.js +1 -1
  83. package/dist/transport/index.js.map +1 -1
  84. package/dist/transport/index.mjs +1 -1
  85. package/dist/transport/index.mjs.map +1 -1
  86. package/dist/{types-CTmUpNpS.d.mts → types-BCkwBA84.d.mts} +12 -0
  87. package/dist/{types-CTmUpNpS.d.ts → types-BCkwBA84.d.ts} +12 -0
  88. package/dist/{types-CtFXmDzm.d.ts → types-C_b4QTyO.d.ts} +18 -0
  89. package/dist/{types-DQrn_kei.d.mts → types-DlshIIR-.d.mts} +18 -0
  90. package/dist/ui/index.d.mts +1 -1
  91. package/dist/ui/index.d.ts +1 -1
  92. package/dist/verify.d.mts +35 -4
  93. package/dist/verify.d.ts +35 -4
  94. package/dist/verify.js +130 -16
  95. package/dist/verify.js.map +1 -1
  96. package/dist/verify.mjs +129 -16
  97. package/dist/verify.mjs.map +1 -1
  98. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var SDK_VERSION = "5.11.0";
2
+ var SDK_VERSION = "5.12.0";
3
3
 
4
4
  // src/http.ts
5
5
  var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
@@ -69,23 +69,58 @@ function prefetchWellKnown(apiBaseUrl) {
69
69
  inflight.set(apiBaseUrl, promise);
70
70
  return promise;
71
71
  }
72
- async function getWellKnownUrls(apiBaseUrl) {
73
- const entry = cache.get(apiBaseUrl);
74
- if (entry) {
75
- if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
76
- prefetchWellKnown(apiBaseUrl).catch(() => {
77
- });
78
- }
79
- return entry.data;
80
- }
81
- const pending = inflight.get(apiBaseUrl);
82
- if (pending) return pending;
83
- return prefetchWellKnown(apiBaseUrl);
84
- }
85
72
  function getCachedWellKnownUrls(apiBaseUrl) {
86
73
  return cache.get(apiBaseUrl)?.data;
87
74
  }
88
75
 
76
+ // src/failure-posture.ts
77
+ var VERIFICATION_STATUS_HEADER = "X-Astra-Verification";
78
+ var DEFAULT_UNAVAILABLE_RETRY_AFTER_S = 30;
79
+ var REJECTED_IDENTITY_DIMENSIONS = /* @__PURE__ */ new Set([
80
+ "identity.invalid_agent_id",
81
+ "identity.unknown_agent",
82
+ "agent.lookup"
83
+ ]);
84
+ var UNKNOWN_AGENT_GUIDANCE = "Register your agent with AstraSync (or, inside an MCP host, add the AstraSync MCP connector for keyless, human-approved registration), then present the canonical ASTRA-* id issued after owner approval.";
85
+ var UNAVAILABLE_DIMENSIONS = /* @__PURE__ */ new Set([
86
+ "verify_access.api_error",
87
+ "middleware.internal_error"
88
+ ]);
89
+ function isVerificationUnavailable(result) {
90
+ if (result.misconfigured) return false;
91
+ return (result.failures ?? []).some((f) => UNAVAILABLE_DIMENSIONS.has(f.dimension));
92
+ }
93
+ function unavailableRetryAfterSeconds(result) {
94
+ if (typeof result.retryAfterMs === "number" && Number.isFinite(result.retryAfterMs)) {
95
+ return Math.max(1, Math.ceil(result.retryAfterMs / 1e3));
96
+ }
97
+ return DEFAULT_UNAVAILABLE_RETRY_AFTER_S;
98
+ }
99
+ function buildVerificationUnavailableBody(result) {
100
+ return {
101
+ success: false,
102
+ error: {
103
+ code: "VERIFICATION_UNAVAILABLE",
104
+ message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry after the Retry-After interval.",
105
+ retryable: true,
106
+ retryAfterSeconds: unavailableRetryAfterSeconds(result),
107
+ ...result.failures && { failures: result.failures },
108
+ ...result.correlationId && { correlationId: result.correlationId },
109
+ guidance: {
110
+ message: "Retry the request after the Retry-After interval, with exponential backoff; the condition is transient."
111
+ }
112
+ }
113
+ };
114
+ }
115
+ function isRejectedIdentityClaim(result) {
116
+ return (result.failures ?? []).some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
117
+ }
118
+ function denyStatusFor(result, policyDeniedCode) {
119
+ if (isRejectedIdentityClaim(result)) return { status: 403, code: "AGENT_IDENTITY_REJECTED" };
120
+ if (result.identityVerified) return { status: 403, code: policyDeniedCode };
121
+ return { status: 401, code: "UNAUTHORIZED" };
122
+ }
123
+
89
124
  // src/verify.ts
90
125
  var DEFAULT_CONFIG = {
91
126
  apiBaseUrl: "https://astrasync.ai/api",
@@ -331,11 +366,44 @@ function createGuidanceResponse(_config, reason, options = {}) {
331
366
  // coercing the absent recommendation to a policy 'deny'. Misconfig is
332
367
  // deterministic and intentionally NOT marked retryable.
333
368
  ...isApiError && { retryable: true },
369
+ ...options.httpStatus !== void 0 && { httpStatus: options.httpStatus },
370
+ ...options.retryAfterMs !== void 0 && { retryAfterMs: options.retryAfterMs },
334
371
  correlationId: options.correlationId,
335
372
  attemptId: options.attemptId,
336
373
  verifiedAt: /* @__PURE__ */ new Date()
337
374
  };
338
375
  }
376
+ async function readJsonSafe(response) {
377
+ try {
378
+ const parsed = await response.json();
379
+ return parsed && typeof parsed === "object" ? parsed : {};
380
+ } catch {
381
+ return {};
382
+ }
383
+ }
384
+ function rejectedIdentityGuidance(failures) {
385
+ const f = failures?.find((x) => REJECTED_IDENTITY_DIMENSIONS.has(x.dimension));
386
+ if (!f) return void 0;
387
+ if (f.dimension === "agent.lookup") {
388
+ return `The presented agent identifier is not registered with AstraSync. ${UNKNOWN_AGENT_GUIDANCE}`;
389
+ }
390
+ return f.guidance ? `${f.message} ${f.guidance}` : f.message;
391
+ }
392
+ var MAX_RETRY_AFTER_MS = 15 * 60 * 1e3;
393
+ function parseRetryAfterMs(response) {
394
+ const raw = response.headers?.get?.("retry-after");
395
+ if (!raw) return void 0;
396
+ const trimmed = raw.trim();
397
+ let ms;
398
+ if (/^\d+(\.\d+)?$/.test(trimmed)) {
399
+ ms = Number(trimmed) * 1e3;
400
+ } else {
401
+ const at = Date.parse(trimmed);
402
+ if (Number.isNaN(at)) return void 0;
403
+ ms = at - Date.now();
404
+ }
405
+ return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, Math.round(ms)));
406
+ }
339
407
  async function callVerifyAccessAPI(config, request) {
340
408
  const { credentials, ...requestData } = request;
341
409
  const body = {
@@ -392,7 +460,8 @@ async function callVerifyAccessAPI(config, request) {
392
460
  body: JSON.stringify(body),
393
461
  signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
394
462
  });
395
- const data = await response.json();
463
+ const data = await readJsonSafe(response);
464
+ const retryAfterMs = parseRetryAfterMs(response);
396
465
  if (response.status === 410) {
397
466
  return {
398
467
  success: true,
@@ -430,6 +499,23 @@ async function callVerifyAccessAPI(config, request) {
430
499
  }
431
500
  };
432
501
  }
502
+ if (response.status === 404 && credentials.astraId && (data?.error === "AGENT_NOT_FOUND" || typeof data?.message === "string" && /agent/i.test(data.message))) {
503
+ return {
504
+ success: true,
505
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
506
+ access: {
507
+ allowed: false,
508
+ reason: "unknown_agent",
509
+ failures: [
510
+ {
511
+ dimension: "identity.unknown_agent",
512
+ message: "The presented agent identifier is not registered with AstraSync.",
513
+ guidance: UNKNOWN_AGENT_GUIDANCE
514
+ }
515
+ ]
516
+ }
517
+ };
518
+ }
433
519
  if (!response.ok) {
434
520
  if (response.status === 401) {
435
521
  return {
@@ -444,9 +530,11 @@ async function callVerifyAccessAPI(config, request) {
444
530
  }
445
531
  return {
446
532
  success: false,
447
- error: data.message || data.error || `API returned ${response.status}`,
533
+ error: typeof data?.message === "string" && data.message || typeof data?.error === "string" && data.error || `API returned ${response.status}`,
448
534
  correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
449
- attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
535
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0,
536
+ httpStatus: response.status,
537
+ ...retryAfterMs !== void 0 && { retryAfterMs }
450
538
  };
451
539
  }
452
540
  return data;
@@ -518,12 +606,14 @@ async function verify(config, request, options) {
518
606
  correlationId: apiResponse.correlationId,
519
607
  attemptId: apiResponse.attemptId,
520
608
  remediation: apiResponse.remediation,
609
+ httpStatus: apiResponse.httpStatus,
610
+ retryAfterMs: apiResponse.retryAfterMs,
521
611
  urls
522
612
  });
523
613
  }
524
614
  if (!apiResponse.access?.allowed) {
525
615
  const aggregatedFailures = apiResponse.access?.failures;
526
- const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true;
616
+ const idVerifiedFromBackend = apiResponse.verificationContext?.idVerified === true && !aggregatedFailures?.some((f) => REJECTED_IDENTITY_DIMENSIONS.has(f.dimension));
527
617
  const result2 = {
528
618
  identityVerified: idVerifiedFromBackend,
529
619
  policyAllowed: false,
@@ -535,7 +625,10 @@ async function verify(config, request, options) {
535
625
  requiresStepUp: apiResponse.access?.requiresStepUp,
536
626
  requiresApproval: apiResponse.access?.requiresApproval,
537
627
  guidance: {
538
- message: apiResponse.access?.reason || "Access denied by PDLSS policy",
628
+ // 5.12.0: a presented-but-rejected id (malformed / unknown) carries
629
+ // actionable registration guidance on its failure — surface it
630
+ // instead of the bare machine reason ('unknown_agent').
631
+ message: rejectedIdentityGuidance(aggregatedFailures) ?? (apiResponse.access?.reason || "Access denied by PDLSS policy"),
539
632
  registrationUrl: urls?.registrationUrl ?? "",
540
633
  documentationUrl: urls?.documentationUrl ?? "",
541
634
  // 5.1.0: anonymous callers land on this branch — steer MCP-hosted
@@ -635,7 +728,7 @@ async function verify(config, request, options) {
635
728
  registrationUrl: urls?.registrationUrl ?? "",
636
729
  documentationUrl: urls?.documentationUrl ?? ""
637
730
  };
638
- } else if (result.recommendation === "step_up_required") {
731
+ } else if (result.recommendation === "step_up_required" && result.identityVerified) {
639
732
  result.requiresStepUp = true;
640
733
  result.denialReasons = serverDenialReasons ?? result.recommendationReasons ?? ["Step-up verification required"];
641
734
  }
@@ -1109,8 +1202,37 @@ function dedupeFailures(result) {
1109
1202
  result.denialReasons = [...new Set(result.denialReasons)];
1110
1203
  }
1111
1204
  }
1205
+ function isReadOnlyMcpMethod(parsed) {
1206
+ return parsed.method !== "tools/call";
1207
+ }
1112
1208
  function defaultMcpDenied(result, req, res) {
1113
1209
  const id = req.body?.id ?? null;
1210
+ if (isVerificationUnavailable(result)) {
1211
+ const retryAfterSeconds = unavailableRetryAfterSeconds(result);
1212
+ const body = buildVerificationUnavailableBody(result).error;
1213
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1214
+ res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
1215
+ res.setHeader("Retry-After", String(retryAfterSeconds));
1216
+ res.status(503).json({
1217
+ jsonrpc: "2.0",
1218
+ id,
1219
+ error: {
1220
+ // -32003 → verification temporarily unavailable (retryable)
1221
+ code: -32003,
1222
+ message: body.message,
1223
+ data: {
1224
+ code: body.code,
1225
+ recommendation: "error",
1226
+ retryable: true,
1227
+ retryAfterSeconds,
1228
+ guidance: body.guidance,
1229
+ failures: result.failures,
1230
+ correlationId: result.correlationId
1231
+ }
1232
+ }
1233
+ });
1234
+ return;
1235
+ }
1114
1236
  if (result.misconfigured) {
1115
1237
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1116
1238
  res.status(503).json({
@@ -1129,7 +1251,7 @@ function defaultMcpDenied(result, req, res) {
1129
1251
  });
1130
1252
  return;
1131
1253
  }
1132
- const status = !result.identityVerified ? 401 : 403;
1254
+ const { status, code: denyCode } = denyStatusFor(result, "INSUFFICIENT_ACCESS");
1133
1255
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1134
1256
  res.status(status).json({
1135
1257
  jsonrpc: "2.0",
@@ -1139,9 +1261,11 @@ function defaultMcpDenied(result, req, res) {
1139
1261
  // -32000 → unauthorized (no identity resolved)
1140
1262
  // -32001 → insufficient access (identity OK, policy denied)
1141
1263
  // -32002 → merchant verification misconfigured (handled above)
1264
+ // -32003 → verification temporarily unavailable (handled above)
1142
1265
  code: !result.identityVerified ? -32e3 : -32001,
1143
1266
  message: result.denialReasons?.[0] ?? "Access denied",
1144
1267
  data: {
1268
+ code: denyCode,
1145
1269
  guidance: result.guidance,
1146
1270
  // Round-10: aggregated per-dimension detail + correlation handle.
1147
1271
  failures: result.failures,
@@ -1269,6 +1393,9 @@ function createMcpMiddleware(options) {
1269
1393
  resolved_action: pdlss.action
1270
1394
  });
1271
1395
  }
1396
+ if (!pdlss.purpose && !shouldEnforce) {
1397
+ return next();
1398
+ }
1272
1399
  if (!pdlss.purpose) {
1273
1400
  const id = req.body?.id ?? null;
1274
1401
  res.status(400).json({
@@ -1288,6 +1415,9 @@ function createMcpMiddleware(options) {
1288
1415
  }
1289
1416
  const counterpartyUrl = config.counterpartyUrl || `${req.protocol}://${req.get("host")}${req.path}`;
1290
1417
  const shouldRecordDecisions = recordDecisions !== false;
1418
+ const forwardedFor = req.headers["x-forwarded-for"];
1419
+ const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
1420
+ const sourceIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
1291
1421
  const verificationRequest = {
1292
1422
  credentials,
1293
1423
  purpose: pdlss.purpose,
@@ -1302,9 +1432,10 @@ function createMcpMiddleware(options) {
1302
1432
  counterpartyType: config.counterpartyType || "mcp_server",
1303
1433
  enableRuntimeChallenge,
1304
1434
  callerMetadata: {
1305
- sourceIp: req.ip,
1435
+ sourceIp,
1306
1436
  userAgent: req.headers["user-agent"],
1307
1437
  host: req.headers.host,
1438
+ ...forwardedForStr && { forwardedFor: forwardedForStr },
1308
1439
  // Capture EVERY inbound header (parity with the express adapter —
1309
1440
  // MCP-only merchants have no edge to do it upstream) plus the
1310
1441
  // CDN-derived connection block. Local string work, secrets
@@ -1327,6 +1458,21 @@ function createMcpMiddleware(options) {
1327
1458
  result.stepUpOutcome = outcome;
1328
1459
  }
1329
1460
  }
1461
+ if (!result.policyAllowed && isVerificationUnavailable(result) && (!shouldEnforce || isReadOnlyMcpMethod(parsed) && !credentials.astraId && !credentials.jwt)) {
1462
+ res.setHeader(VERIFICATION_STATUS_HEADER, "unavailable");
1463
+ return next();
1464
+ }
1465
+ if (!shouldEnforce) {
1466
+ if (config.setPassThroughHeader) {
1467
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1468
+ res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
1469
+ }
1470
+ if (shouldRecordDecisions && sessionId) {
1471
+ recordDecision(config, sessionId, "granted").catch(() => {
1472
+ });
1473
+ }
1474
+ return next();
1475
+ }
1330
1476
  if (!result.policyAllowed) {
1331
1477
  if (shouldRecordDecisions && sessionId) {
1332
1478
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -1346,17 +1492,6 @@ function createMcpMiddleware(options) {
1346
1492
  onDenied(result, req, res);
1347
1493
  return;
1348
1494
  }
1349
- if (!shouldEnforce) {
1350
- if (config.setPassThroughHeader) {
1351
- res.setHeader("X-Astra-Gateway-Mode", "enforced");
1352
- res.setHeader("X-Astra-Gateway-Reason", "evaluated-not-enforced");
1353
- }
1354
- if (shouldRecordDecisions && sessionId) {
1355
- recordDecision(config, sessionId, "granted").catch(() => {
1356
- });
1357
- }
1358
- return next();
1359
- }
1360
1495
  if (effectiveAstraId) {
1361
1496
  res.setHeader(
1362
1497
  MCP_VERIFIED_HOP_HEADER,
@@ -1397,14 +1532,6 @@ function createMcpMiddleware(options) {
1397
1532
  verifiedAt: /* @__PURE__ */ new Date(),
1398
1533
  correlationId
1399
1534
  };
1400
- const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
1401
- if (catchUrls) {
1402
- result.guidance = {
1403
- message: `Middleware threw ${errorClass} \u2014 failing closed`,
1404
- registrationUrl: catchUrls.registrationUrl,
1405
- documentationUrl: catchUrls.documentationUrl
1406
- };
1407
- }
1408
1535
  dedupeFailures(result);
1409
1536
  return onDenied(result, req, res);
1410
1537
  }