@astrasyncai/verification-gateway 2.3.7 → 2.3.9

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 (84) hide show
  1. package/README.md +93 -10
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +81 -7
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +81 -7
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/mcp.d.mts +1 -1
  11. package/dist/adapters/mcp.d.ts +1 -1
  12. package/dist/adapters/mcp.js +84 -12
  13. package/dist/adapters/mcp.js.map +1 -1
  14. package/dist/adapters/mcp.mjs +84 -12
  15. package/dist/adapters/mcp.mjs.map +1 -1
  16. package/dist/adapters/nextjs.d.mts +2 -2
  17. package/dist/adapters/nextjs.d.ts +2 -2
  18. package/dist/adapters/nextjs.js +40 -6
  19. package/dist/adapters/nextjs.js.map +1 -1
  20. package/dist/adapters/nextjs.mjs +40 -6
  21. package/dist/adapters/nextjs.mjs.map +1 -1
  22. package/dist/adapters/sdk.d.mts +2 -2
  23. package/dist/adapters/sdk.d.ts +2 -2
  24. package/dist/adapters/sdk.js +40 -6
  25. package/dist/adapters/sdk.js.map +1 -1
  26. package/dist/adapters/sdk.mjs +40 -6
  27. package/dist/adapters/sdk.mjs.map +1 -1
  28. package/dist/agent/index.d.mts +2 -2
  29. package/dist/agent/index.d.ts +2 -2
  30. package/dist/browser/background.js +39 -5
  31. package/dist/browser/background.js.map +1 -1
  32. package/dist/browser/background.mjs +39 -5
  33. package/dist/browser/background.mjs.map +1 -1
  34. package/dist/browser/browser-adapter.d.mts +2 -2
  35. package/dist/browser/browser-adapter.d.ts +2 -2
  36. package/dist/cli/index.d.mts +2 -2
  37. package/dist/cli/index.d.ts +2 -2
  38. package/dist/cursor/cursor-adapter.d.mts +2 -2
  39. package/dist/cursor/cursor-adapter.d.ts +2 -2
  40. package/dist/cursor/extension.d.mts +2 -2
  41. package/dist/cursor/extension.d.ts +2 -2
  42. package/dist/cursor/extension.js +39 -5
  43. package/dist/cursor/extension.js.map +1 -1
  44. package/dist/cursor/extension.mjs +39 -5
  45. package/dist/cursor/extension.mjs.map +1 -1
  46. package/dist/{express-D9oRsseg.d.mts → express-BiB51d5t.d.mts} +1 -1
  47. package/dist/{express-DMSIl20m.d.ts → express-D6tEDU08.d.ts} +1 -1
  48. package/dist/gateway/gateway.d.mts +2 -2
  49. package/dist/gateway/gateway.d.ts +2 -2
  50. package/dist/gateway/gateway.js +39 -5
  51. package/dist/gateway/gateway.js.map +1 -1
  52. package/dist/gateway/gateway.mjs +39 -5
  53. package/dist/gateway/gateway.mjs.map +1 -1
  54. package/dist/git-trigger/git-hooks.d.mts +2 -2
  55. package/dist/git-trigger/git-hooks.d.ts +2 -2
  56. package/dist/{index-EwUWXC5T.d.ts → index-8DFMpITk.d.ts} +1 -1
  57. package/dist/{index-YNPs800Z.d.mts → index-B--6fiDp.d.mts} +1 -1
  58. package/dist/{index-Bn_7eGjb.d.mts → index-CAykfMWK.d.mts} +1 -1
  59. package/dist/{index-BtU9yFda.d.ts → index-Yt02MRyu.d.ts} +1 -1
  60. package/dist/index.d.mts +7 -7
  61. package/dist/index.d.ts +7 -7
  62. package/dist/index.js +87 -13
  63. package/dist/index.js.map +1 -1
  64. package/dist/index.mjs +87 -13
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/local-evaluator/evaluator.d.mts +2 -2
  67. package/dist/local-evaluator/evaluator.d.ts +2 -2
  68. package/dist/{nextjs-B5ZBpHra.d.ts → nextjs-CK5F_tVZ.d.ts} +1 -1
  69. package/dist/{nextjs-BLtjRbc-.d.mts → nextjs-CpxqfQqD.d.mts} +1 -1
  70. package/dist/{sdk-BhkxvqnK.d.mts → sdk-BMvauMgP.d.ts} +9 -2
  71. package/dist/{sdk-YmE3RG8n.d.ts → sdk-yJjO7yzn.d.mts} +9 -2
  72. package/dist/transport/index.d.mts +2 -2
  73. package/dist/transport/index.d.ts +2 -2
  74. package/dist/{types-DxY5zt4z.d.mts → types-CKafuHDn.d.mts} +1 -1
  75. package/dist/{types-Bxqj1sKY.d.mts → types-UYT4GdPW.d.mts} +42 -4
  76. package/dist/{types-Bxqj1sKY.d.ts → types-UYT4GdPW.d.ts} +42 -4
  77. package/dist/{types-BecRpozv.d.ts → types-ppkhdldJ.d.ts} +1 -1
  78. package/dist/ui/index.d.mts +1 -1
  79. package/dist/ui/index.d.ts +1 -1
  80. package/dist/ui/index.js +3 -3
  81. package/dist/ui/index.js.map +1 -1
  82. package/dist/ui/index.mjs +3 -3
  83. package/dist/ui/index.mjs.map +1 -1
  84. package/package.json +1 -1
package/README.md CHANGED
@@ -40,15 +40,16 @@ import { createMiddleware } from '@astrasyncai/verification-gateway/express';
40
40
 
41
41
  const app = express();
42
42
 
43
+ // v2.3.7+ — per-route policy lives in the AstraSync dashboard.
44
+ // The SDK fetches it on init via counterpartyId; do NOT pass routes here.
43
45
  app.use(
44
46
  createMiddleware({
45
47
  apiBaseUrl: 'https://astrasync.ai/api',
46
- routes: [
47
- { pattern: '/api/public/*', method: '*', minAccessLevel: 'none' },
48
- { pattern: '/api/data/*', method: 'GET', minAccessLevel: 'read-only' },
49
- { pattern: '/api/data/*', method: '*', minAccessLevel: 'standard' },
50
- { pattern: '/api/admin/*', method: '*', minAccessLevel: 'internal' },
51
- ],
48
+ apiKey: process.env.ASTRASYNC_API_KEY,
49
+ counterpartyId: 'ASTRAE-...', // your endpoint id from the dashboard
50
+ setPassThroughHeader: true, // recommended for local-dev / staging
51
+ // surfaces pass-through mode when
52
+ // no per-route policy is configured
52
53
  })
53
54
  );
54
55
  ```
@@ -59,13 +60,13 @@ app.use(
59
60
  // middleware.ts
60
61
  import { createMiddleware } from '@astrasyncai/verification-gateway/nextjs';
61
62
 
63
+ // v2.3.7+ — per-route policy lives in the AstraSync dashboard.
64
+ // The SDK fetches it on init via counterpartyId; do NOT pass routes here.
62
65
  export const middleware = createMiddleware({
63
66
  apiBaseUrl: 'https://api.astrasync.ai',
67
+ apiKey: process.env.ASTRASYNC_API_KEY,
68
+ counterpartyId: 'ASTRAE-...',
64
69
  showCommerceShield: true,
65
- routes: [
66
- { pattern: '/api/*', method: '*', minAccessLevel: 'standard' },
67
- { pattern: '/dashboard/*', method: '*', minAccessLevel: 'read-only' },
68
- ],
69
70
  });
70
71
 
71
72
  export const config = {
@@ -94,6 +95,88 @@ if (result.verified && result.accessLevel !== 'none') {
94
95
  }
95
96
  ```
96
97
 
98
+ ### Inner-hop verification (MCP → REST dedupe with X-Astra-Verified-Hop)
99
+
100
+ When an MCP tool calls an inner REST endpoint that ALSO runs the
101
+ verification gateway, two verify-access calls fire for the same agent in
102
+ the same logical request — audit gets noisy and you pay a doubled
103
+ round-trip. The `X-Astra-Verified-Hop` header marks the inner hop as
104
+ already-verified by the upstream so the inner middleware skips a
105
+ redundant verify-access call.
106
+
107
+ The MCP middleware emits the marker automatically on its outbound
108
+ response. Tool handlers calling inner REST hops need to forward it on
109
+ outgoing fetches; the inner middleware reads it via `parseVerifiedHop` +
110
+ `isVerifiedHopValidFor` and skips a duplicate verify-access if the
111
+ marker is fresh AND matches the agent claimed on the inner hop.
112
+
113
+ **Worked example — upstream MCP → inner REST hop:**
114
+
115
+ ```typescript
116
+ // === outer MCP server ===
117
+ import express from 'express';
118
+ import { createMcpMiddleware } from '@astrasyncai/verification-gateway/mcp';
119
+
120
+ const mcp = express();
121
+ mcp.use(express.json());
122
+ mcp.use(
123
+ createMcpMiddleware({
124
+ apiBaseUrl: 'https://astrasync.ai/api',
125
+ apiKey: process.env.ASTRASYNC_API_KEY,
126
+ counterpartyId: 'ASTRAE-mcp...',
127
+ toolGates: { start_checkout: 'standard' },
128
+ })
129
+ );
130
+
131
+ // MCP middleware sets X-Astra-Verified-Hop on the response automatically
132
+ // AND populates req.agentVerification. Tool handlers can read both.
133
+ mcp.post('/mcp', async (req, res) => {
134
+ const v = req.agentVerification!;
135
+ // Forward the verified-hop marker on outgoing inner-hop calls.
136
+ // Build it from the same fields the middleware uses on the response.
137
+ const { serializeVerifiedHop, MCP_VERIFIED_HOP_HEADER } =
138
+ await import('@astrasyncai/verification-gateway/mcp');
139
+ const hop = serializeVerifiedHop({
140
+ astraId: v.agent!.astraId,
141
+ sessionId: v.sessionId,
142
+ checkedAt: Date.now(),
143
+ });
144
+
145
+ const inner = await fetch('http://internal/api/checkout/items', {
146
+ method: 'POST',
147
+ headers: {
148
+ 'Content-Type': 'application/json',
149
+ 'X-Astra-Id': v.agent!.astraId, // identity marker
150
+ [MCP_VERIFIED_HOP_HEADER]: hop, // verified-hop dedupe marker
151
+ },
152
+ body: JSON.stringify({ sku: 'sku_42' }),
153
+ });
154
+ res.status(inner.status).json(await inner.json());
155
+ });
156
+
157
+ // === inner REST hop (same fleet) ===
158
+ import { createMiddleware } from '@astrasyncai/verification-gateway/express';
159
+
160
+ const rest = express();
161
+ rest.use(
162
+ createMiddleware({
163
+ apiBaseUrl: 'https://astrasync.ai/api',
164
+ apiKey: process.env.ASTRASYNC_API_KEY,
165
+ counterpartyId: 'ASTRAE-rest...',
166
+ trustVerifiedHop: true, // accept the dedupe marker
167
+ verifiedHopMaxAgeMs: 60_000, // 60s default; tighten for high-stakes
168
+ })
169
+ );
170
+ // Inner middleware reads X-Astra-Verified-Hop, validates via
171
+ // parseVerifiedHop + isVerifiedHopValidFor, and skips verify-access
172
+ // when the marker is fresh AND its astraId matches the X-Astra-Id on
173
+ // this request.
174
+ ```
175
+
176
+ **Important:** the marker is NOT proof of identity by itself — pair it
177
+ with `X-Astra-Id` so the inner middleware can verify the marker matches
178
+ the claimed agent. The marker only gates the dedupe-skip decision.
179
+
97
180
  ## Access Levels
98
181
 
99
182
  | Level | Description |
@@ -1,6 +1,6 @@
1
1
  import { AstraSyncGateway } from '../gateway/gateway.mjs';
2
- import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-DxY5zt4z.mjs';
3
- import '../types-Bxqj1sKY.mjs';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-CKafuHDn.mjs';
3
+ import '../types-UYT4GdPW.mjs';
4
4
 
5
5
  /**
6
6
  * PlatformAdapter Interface
@@ -1,6 +1,6 @@
1
1
  import { AstraSyncGateway } from '../gateway/gateway.js';
2
- import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-BecRpozv.js';
3
- import '../types-Bxqj1sKY.js';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-ppkhdldJ.js';
3
+ import '../types-UYT4GdPW.js';
4
4
 
5
5
  /**
6
6
  * PlatformAdapter Interface
@@ -1,3 +1,3 @@
1
1
  import 'express';
2
- import '../types-Bxqj1sKY.mjs';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-D9oRsseg.mjs';
2
+ import '../types-UYT4GdPW.mjs';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-BiB51d5t.mjs';
@@ -1,3 +1,3 @@
1
1
  import 'express';
2
- import '../types-Bxqj1sKY.js';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-DMSIl20m.js';
2
+ import '../types-UYT4GdPW.js';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-D6tEDU08.js';
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(express_exports);
28
28
  // src/access-levels.ts
29
29
  var ACCESS_LEVEL_HIERARCHY = {
30
30
  none: 0,
31
- guidance: 1,
31
+ restricted: 1,
32
32
  "read-only": 2,
33
33
  standard: 3,
34
34
  full: 4,
@@ -47,7 +47,11 @@ function hasMinimumAccess(actual, required) {
47
47
  // src/verify.ts
48
48
  var DEFAULT_CONFIG = {
49
49
  apiBaseUrl: "https://astrasync.ai/api",
50
- defaultAccessLevel: "guidance",
50
+ // v2.3.9 (defect #30): default for unconfigured callers is `'none'` (no
51
+ // access). Pre-rename this defaulted to `'guidance'`, which combined with
52
+ // a route gated at `'guidance'` to silently let unverified traffic
53
+ // through (`hasMinimumAccess('guidance', 'guidance') === true`).
54
+ defaultAccessLevel: "none",
51
55
  // minTrustScore + minTrustScoreForFull deprecated in v2.3.0 — server decides.
52
56
  cacheTtl: 300,
53
57
  // 5 minutes
@@ -140,7 +144,12 @@ function createGuidanceResponse(config, reason) {
140
144
  };
141
145
  return {
142
146
  verified: false,
143
- accessLevel: "guidance",
147
+ // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
148
+ // Adapters additionally short-circuit on `verified === false` before
149
+ // the gate check, but the access level still has to be honest at the
150
+ // data layer so downstream consumers (SDK adapters in other languages,
151
+ // custom integrations) inherit the correct semantics.
152
+ accessLevel: "none",
144
153
  guidance,
145
154
  denialReasons: reason ? [reason] : ["No valid agent credentials provided"],
146
155
  verifiedAt: /* @__PURE__ */ new Date()
@@ -197,6 +206,23 @@ async function callVerifyAccessAPI(config, request) {
197
206
  body: JSON.stringify(body)
198
207
  });
199
208
  const data = await response.json();
209
+ if (response.status === 410) {
210
+ return {
211
+ success: true,
212
+ access: {
213
+ allowed: false,
214
+ accessLevel: "none",
215
+ reason: "endpoint_deactivated",
216
+ failures: [
217
+ {
218
+ dimension: "endpoint.deactivated",
219
+ message: typeof data?.message === "string" ? data.message : "Endpoint has been deactivated",
220
+ guidance: typeof data?.guidance === "string" ? data.guidance : "Reactivate via POST /api/endpoints/{id}/reactivate, or update the URL on the calling agent."
221
+ }
222
+ ]
223
+ }
224
+ };
225
+ }
200
226
  if (!response.ok) {
201
227
  return {
202
228
  success: false,
@@ -250,7 +276,14 @@ async function verify(config, request) {
250
276
  const aggregatedFailures = apiResponse.access?.failures;
251
277
  const result2 = {
252
278
  verified: false,
253
- accessLevel: "guidance",
279
+ // v2.3.9 (defect #30): denials grant `'none'`, NEVER a positive band.
280
+ // Pre-rename this hardcoded `'guidance'`, which conflated with the
281
+ // colocated `guidance: {...}` help-payload object below and let
282
+ // denied requests pass any route gated at `'guidance'` because
283
+ // `hasMinimumAccess('guidance', 'guidance') === true`. Adapters now
284
+ // ALSO short-circuit on `verified === false` before the gate check —
285
+ // belt-and-braces.
286
+ accessLevel: "none",
254
287
  denialReasons: aggregatedFailures && aggregatedFailures.length > 0 ? aggregatedFailures.map((f) => f.message) : apiResponse.access?.reason ? [apiResponse.access.reason] : ["Access denied"],
255
288
  failures: aggregatedFailures,
256
289
  requiresStepUp: apiResponse.access?.requiresStepUp,
@@ -306,7 +339,8 @@ async function verify(config, request) {
306
339
  runtimeChallenge: apiResponse.runtimeChallenge,
307
340
  tokenGuidance: apiResponse.tokenGuidance,
308
341
  recommendation: apiResponse.recommendation,
309
- recommendationReasons: apiResponse.recommendationReasons
342
+ recommendationReasons: apiResponse.recommendationReasons,
343
+ warningHeader: apiResponse.warningHeader
310
344
  };
311
345
  if (result.recommendation === "deny") {
312
346
  result.verified = false;
@@ -333,7 +367,7 @@ async function verify(config, request) {
333
367
  }
334
368
  return result;
335
369
  }
336
- async function recordDecision(config, sessionId, decision, reason) {
370
+ async function recordDecision(config, sessionId, decision, reason, override) {
337
371
  const headers = { "Content-Type": "application/json" };
338
372
  if (config.apiKey) {
339
373
  headers["Authorization"] = `Bearer ${config.apiKey}`;
@@ -342,7 +376,16 @@ async function recordDecision(config, sessionId, decision, reason) {
342
376
  await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
343
377
  method: "POST",
344
378
  headers,
345
- body: JSON.stringify({ decision, reason })
379
+ body: JSON.stringify({
380
+ decision,
381
+ reason,
382
+ ...override && {
383
+ overriddenBy: override.overriddenBy,
384
+ toolName: override.toolName,
385
+ requestedLevel: override.requestedLevel,
386
+ grantedLevel: override.grantedLevel
387
+ }
388
+ })
346
389
  }).catch(() => {
347
390
  });
348
391
  }
@@ -540,6 +583,7 @@ function createMiddleware(options) {
540
583
  let lastFetchAt = 0;
541
584
  let refreshing = null;
542
585
  let warnedNoCounterparty = false;
586
+ let warnedEmptyRoutes = false;
543
587
  async function refreshRoutes() {
544
588
  if (!config.counterpartyId) {
545
589
  if (!warnedNoCounterparty) {
@@ -554,6 +598,13 @@ function createMiddleware(options) {
554
598
  if (fetched) {
555
599
  cachedRoutes = fetched;
556
600
  lastFetchAt = Date.now();
601
+ if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
602
+ const dashboard = config.dashboardUrl ?? "https://app.astrasync.ai";
603
+ console.warn(
604
+ `[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
605
+ );
606
+ warnedEmptyRoutes = true;
607
+ }
557
608
  }
558
609
  }
559
610
  refreshing = refreshRoutes().finally(() => {
@@ -576,9 +627,20 @@ function createMiddleware(options) {
576
627
  }
577
628
  const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method);
578
629
  if (!routeConfig) {
630
+ if (config.setPassThroughHeader) {
631
+ res.setHeader("X-Astra-Gateway-Mode", "pass-through");
632
+ res.setHeader(
633
+ "X-Astra-Gateway-Reason",
634
+ cachedRoutes.length === 0 ? "no-policy" : "no-match"
635
+ );
636
+ }
579
637
  return next();
580
638
  }
581
639
  if (routeConfig.minAccessLevel === "none") {
640
+ if (config.setPassThroughHeader) {
641
+ res.setHeader("X-Astra-Gateway-Mode", "pass-through");
642
+ res.setHeader("X-Astra-Gateway-Reason", "route-none");
643
+ }
582
644
  return next();
583
645
  }
584
646
  const credentials = customExtractCredentials ? customExtractCredentials(req) : defaultExtractCredentials(req);
@@ -637,6 +699,14 @@ function createMiddleware(options) {
637
699
  });
638
700
  req.agentVerification = result;
639
701
  const sessionId = result.sessionId;
702
+ if (!result.verified) {
703
+ if (shouldRecordDecisions && sessionId) {
704
+ recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
705
+ });
706
+ }
707
+ onDenied(result, req, res);
708
+ return;
709
+ }
640
710
  if (!hasMinimumAccess(result.accessLevel, routeConfig.minAccessLevel)) {
641
711
  if (shouldRecordDecisions && sessionId) {
642
712
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
@@ -662,6 +732,10 @@ function createMiddleware(options) {
662
732
  recordDecision(config, sessionId, "granted").catch(() => {
663
733
  });
664
734
  }
735
+ const enhancedResult = result;
736
+ if (enhancedResult.warningHeader) {
737
+ res.setHeader(enhancedResult.warningHeader.name, enhancedResult.warningHeader.value);
738
+ }
665
739
  next();
666
740
  } catch (error) {
667
741
  console.error("[VerificationGateway] Middleware error:", error);