@astrasyncai/verification-gateway 4.2.0 → 4.3.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 (112) hide show
  1. package/README.md +45 -21
  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 +56 -4
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +56 -4
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/mcp.d.mts +38 -30
  11. package/dist/adapters/mcp.d.ts +38 -30
  12. package/dist/adapters/mcp.js +22 -5
  13. package/dist/adapters/mcp.js.map +1 -1
  14. package/dist/adapters/mcp.mjs +22 -5
  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 +54 -4
  19. package/dist/adapters/nextjs.js.map +1 -1
  20. package/dist/adapters/nextjs.mjs +54 -4
  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/agent/index.d.mts +2 -2
  25. package/dist/agent/index.d.ts +2 -2
  26. package/dist/agent/index.js +2 -2
  27. package/dist/agent/index.js.map +1 -1
  28. package/dist/agent/index.mjs +2 -2
  29. package/dist/agent/index.mjs.map +1 -1
  30. package/dist/bin/astrasync-claude-hook.js +2 -2
  31. package/dist/bin/astrasync-codex-hook.js +2 -2
  32. package/dist/bin/astrasync-guard.js +2 -2
  33. package/dist/bin/astrasync.js +14 -4
  34. package/dist/browser/background.js +2 -2
  35. package/dist/browser/background.js.map +1 -1
  36. package/dist/browser/background.mjs +2 -2
  37. package/dist/browser/background.mjs.map +1 -1
  38. package/dist/browser/browser-adapter.d.mts +2 -2
  39. package/dist/browser/browser-adapter.d.ts +2 -2
  40. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  41. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  42. package/dist/cli/index.d.mts +2 -2
  43. package/dist/cli/index.d.ts +2 -2
  44. package/dist/codex/index.d.mts +2 -2
  45. package/dist/codex/index.d.ts +2 -2
  46. package/dist/codex/index.js +2 -2
  47. package/dist/codex/index.js.map +1 -1
  48. package/dist/codex/index.mjs +2 -2
  49. package/dist/codex/index.mjs.map +1 -1
  50. package/dist/cursor/cursor-adapter.d.mts +2 -2
  51. package/dist/cursor/cursor-adapter.d.ts +2 -2
  52. package/dist/cursor/extension.d.mts +2 -2
  53. package/dist/cursor/extension.d.ts +2 -2
  54. package/dist/cursor/extension.js +2 -2
  55. package/dist/cursor/extension.js.map +1 -1
  56. package/dist/cursor/extension.mjs +2 -2
  57. package/dist/cursor/extension.mjs.map +1 -1
  58. package/dist/edge-config.d.mts +1 -1
  59. package/dist/edge-config.d.ts +1 -1
  60. package/dist/edge-core/index.d.mts +1 -1
  61. package/dist/edge-core/index.d.ts +1 -1
  62. package/dist/edge-core/index.js +25 -5
  63. package/dist/edge-core/index.js.map +1 -1
  64. package/dist/edge-core/index.mjs +25 -5
  65. package/dist/edge-core/index.mjs.map +1 -1
  66. package/dist/{express-D_4hTn5Z.d.mts → express-CR6F3njD.d.mts} +1 -1
  67. package/dist/{express-BVd1_3FE.d.ts → express-DaqL11tx.d.ts} +1 -1
  68. package/dist/gateway/gateway.d.mts +2 -2
  69. package/dist/gateway/gateway.d.ts +2 -2
  70. package/dist/gateway/gateway.js +2 -2
  71. package/dist/gateway/gateway.js.map +1 -1
  72. package/dist/gateway/gateway.mjs +2 -2
  73. package/dist/gateway/gateway.mjs.map +1 -1
  74. package/dist/git-trigger/git-hooks.d.mts +2 -2
  75. package/dist/git-trigger/git-hooks.d.ts +2 -2
  76. package/dist/{index-B0YHu_SP.d.ts → index-B5Ts1WtM.d.ts} +3 -3
  77. package/dist/{index-T1aBoUcc.d.ts → index-BfIxCYIZ.d.ts} +1 -1
  78. package/dist/{index-BPEBlOsE.d.mts → index-Cub7lK-T.d.mts} +3 -3
  79. package/dist/{index-DQb5-_1X.d.mts → index-CwAh7Tln.d.mts} +1 -1
  80. package/dist/index.d.mts +7 -7
  81. package/dist/index.d.ts +7 -7
  82. package/dist/index.js +74 -35
  83. package/dist/index.js.map +1 -1
  84. package/dist/index.mjs +71 -32
  85. package/dist/index.mjs.map +1 -1
  86. package/dist/local-evaluator/evaluator.d.mts +2 -2
  87. package/dist/local-evaluator/evaluator.d.ts +2 -2
  88. package/dist/metadata-capture.d.mts +18 -5
  89. package/dist/metadata-capture.d.ts +18 -5
  90. package/dist/metadata-capture.js +21 -4
  91. package/dist/metadata-capture.js.map +1 -1
  92. package/dist/metadata-capture.mjs +20 -4
  93. package/dist/metadata-capture.mjs.map +1 -1
  94. package/dist/{nextjs-w7RTbP1P.d.ts → nextjs-DL6uE1ye.d.ts} +1 -1
  95. package/dist/{nextjs-WyeVr2Kp.d.mts → nextjs-DlX6xwbU.d.mts} +1 -1
  96. package/dist/{sdk-CbTNIkAa.d.mts → sdk-Bbh9B_dw.d.mts} +1 -1
  97. package/dist/{sdk-QeX0Z8Ho.d.ts → sdk-C6SASmsh.d.ts} +1 -1
  98. package/dist/transport/index.d.mts +2 -2
  99. package/dist/transport/index.d.ts +2 -2
  100. package/dist/transport/index.js +5 -5
  101. package/dist/transport/index.js.map +1 -1
  102. package/dist/transport/index.mjs +2 -2
  103. package/dist/transport/index.mjs.map +1 -1
  104. package/dist/{types-r850cOt0.d.mts → types-C0zfSXMB.d.mts} +22 -0
  105. package/dist/{types-DqfPU5Bl.d.mts → types-C7Rb-w0d.d.mts} +1 -1
  106. package/dist/{types-sMxBT-nM.d.ts → types-DwSfO7Sb.d.ts} +22 -0
  107. package/dist/{types-BLUx92FJ.d.ts → types-nIh3HtuA.d.ts} +1 -1
  108. package/dist/ui/index.d.mts +1 -1
  109. package/dist/ui/index.d.ts +1 -1
  110. package/dist/verify.d.mts +1 -1
  111. package/dist/verify.d.ts +1 -1
  112. package/package.json +2 -4
package/README.md CHANGED
@@ -229,20 +229,39 @@ The CLI uses the same response logic — it will print a "Sign in to approve" me
229
229
 
230
230
  > A `PDLSSConfig` type exists in both `/registration` and `/agent`, with **different shapes**. `/registration`'s `PDLSSConfig` is the boundary declaration submitted at register time; `/agent`'s `PDLSSConfig` is the per-request runtime request shape. Disambiguate via the import path — the root export deliberately does not re-export either.
231
231
 
232
- ### Inner-hop verification (MCP → REST dedupe with X-Astra-Verified-Hop)
233
-
234
- When an MCP tool calls an inner REST endpoint that ALSO runs the
235
- verification gateway, two verify-access calls fire for the same agent in
236
- the same logical request — audit gets noisy and you pay a doubled
237
- round-trip. The `X-Astra-Verified-Hop` header marks the inner hop as
238
- already-verified by the upstream so the inner middleware skips a
239
- redundant verify-access call.
240
-
241
- The MCP middleware emits the marker automatically on its outbound
242
- response. Tool handlers calling inner REST hops need to forward it on
243
- outgoing fetches; the inner middleware reads it via `parseVerifiedHop` +
244
- `isVerifiedHopValidFor` and skips a duplicate verify-access if the
245
- marker is fresh AND matches the agent claimed on the inner hop.
232
+ ### Inner-hop verification (cross-hop dedupe with X-Astra-Verified-Hop)
233
+
234
+ When an upstream AstraSync hop and an inner gateway-protected endpoint
235
+ both verify the same agent in the same logical request, two verify-access
236
+ calls fire — audit gets noisy and you pay a doubled round-trip. The
237
+ `X-Astra-Verified-Hop` header marks the inner hop as already-verified by
238
+ the upstream so the inner middleware skips the redundant call.
239
+
240
+ Two upstreams emit the marker automatically (SDK 4.3.0+):
241
+
242
+ - the **MCP middleware**, on its outbound response (tool handlers calling
243
+ inner REST hops forward it on outgoing fetches — worked example below);
244
+ - the **edge gateway** (`adapter-lambda` / `adapter-edge`), toward the
245
+ origin whenever its verify-access call produced identity (authenticate /
246
+ authorize depth). An SDK-middleware origin behind the TAG then skips the
247
+ double verify by setting `trustVerifiedHop: true` — at the default
248
+ classify depth no marker is emitted and the origin verifies as usual.
249
+
250
+ `trustVerifiedHop` + `verifiedHopMaxAgeMs` are supported on the `express`,
251
+ `nextjs`, and `mcp` middleware options (all default off). The inner
252
+ middleware validates the marker via `parseVerifiedHop` +
253
+ `isVerifiedHopValidFor` and skips a duplicate verify-access only if the
254
+ marker is fresh AND matches the agent claimed on the inner hop. The edge
255
+ gateway strips any inbound `X-Astra-Verified-Hop` on evaluated passes, so
256
+ a viewer can never smuggle one through the TAG.
257
+
258
+ **Trust model (read before enabling):** the marker is UNSIGNED — a dedupe
259
+ advisory, not proof. Enable `trustVerifiedHop` only when every network
260
+ path to the inner hop crosses a stripping gateway (the TAG) or an
261
+ equivalent trusted boundary; inside that boundary a forger could reduce
262
+ verify-access from "every request" to "once per freshness window" for an
263
+ agent id it already presents. This is the same trust model as the
264
+ unsigned `X-AstraSync-*` attestation headers.
246
265
 
247
266
  **Worked example — upstream MCP → inner REST hop:**
248
267
 
@@ -589,13 +608,18 @@ The **Trusted Agent Gateway (TAG)** is AstraSync's verification gateway at the C
589
608
 
590
609
  Both consume the same dashboard-managed policy (`./edge-config`), the same platform fingerprint registry (`./platform-signatures`), and the same header sanitiser (`./metadata-capture`) as this SDK — one classification everywhere.
591
610
 
592
- ### What each integration captures
611
+ ### What each integration captures (canonical capture matrix)
612
+
613
+ Header-level capture (`ObservedMetadata.headers` etc.) is identical everywhere: **automatic on every verify** for the edge adapters and the `express` / `nextjs` / `mcp` SDK adapters (no config, no opt-out). What differs is the **connection layer** — and the deciding variable is _who operates the CDN in front of your origin_:
614
+
615
+ | Integration | Connection layer | TLS fingerprint (JA3/JA4) |
616
+ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
617
+ | **Edge adapter** (`adapter-lambda`, `adapter-edge`) | **Full** — native platform signals (IP, ASN, geo, TLS, HTTP version, device class, header structure) | CloudFront: **JA3 + JA4** (2.2.0+, via the template's origin request policy). Cloudflare Workers: Bot Management zones only. Vercel: JA4 with BotID. Fastly: none |
618
+ | **SDK behind a CDN you control** (your own Cloudflare / Fastly / Vercel / CloudFront distribution) | **Derived** — geo/ASN/TLS promoted from the CDN's injected headers (`deriveConnectionFromHeaders`) | Cloudflare: JA3 + JA4 (`cf-ja3-hash` / `cf-ja4`, plan-dependent). CloudFront: JA3 + JA4 — add the `CloudFront-Viewer-JA3/JA4-Fingerprint` headers to your origin request policy |
619
+ | **SDK behind a platform-managed CDN** (Railway, Render, Fly, Heroku, …) | **IP only** — platform CDNs forward `x-forwarded-for` but NOT their provider-specific geo/TLS headers to customer origins | None — no fingerprint header reaches your origin |
620
+ | **SDK, no CDN** | Absent (IP from `x-forwarded-for` when a proxy sets it) | None |
593
621
 
594
- | Integration | Header-level capture (`ObservedMetadata`) | Connection layer (IP / ASN / country / TLS / HTTP version / device class) |
595
- | ------------------------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
596
- | Edge adapters (`adapter-lambda`, `adapter-edge`) | Yes | **Yes** — native platform signals (the authoritative source) |
597
- | `express` + `nextjs` SDK adapters | Yes — **automatic on every verify** (no config, no opt-out) | Derived from CDN-injected headers when your origin sits behind Cloudflare / Fastly / Vercel / CloudFront (`deriveConnectionFromHeaders`, from 4.1.0); absent otherwise |
598
- | `mcp` SDK adapter | Yes — from 4.1.0 | Same CDN-header derivation as express/nextjs |
622
+ > "Behind Fastly" ≠ "receives Fastly's headers": platform-managed CDNs are operated by the platform, and they do not forward `Fastly-Client-IP`-style headers to your origin. If connection-layer signals or fingerprints matter to you on a managed platform, deploy the edge adapter in front (bucket 1) — that is the whole point of the TAG.
599
623
 
600
624
  > **Note:** `runCommercePipeline` (on the `./transport` subpath) is the **server-side** orchestrator of commerce-protocol verification — it runs inside the AstraSync verify-access service. The edge adapters forward raw commerce artifacts to verify-access and never bundle or call the pipeline themselves.
601
625
 
@@ -622,7 +646,7 @@ The ONE sanitiser deciding what is safe to store from an inbound agent's request
622
646
  - `sanitizeHeaders(raw)` → `{ headers, apiKeyFormat?, platformHeaders? }` — drops genuine secrets (cookies, signatures, AstraSync credential headers), reduces credential headers to a safe **format prefix** (e.g. `sk-ant-api03` — a platform signal, never the key), keeps everything else verbatim, and enforces size caps (`MAX_HEADERS`, `MAX_HEADER_VALUE_BYTES`, `MAX_TOTAL_BYTES`).
623
647
  - `extractApiKeyFormat(value)` — the safe key-format prefix of a credential header value, or `undefined` for unrecognised/opaque tokens.
624
648
  - `extractPlatformHeaders(sanitized)` — the platform-signal subset (`openai-organization`, `anthropic-version`, `x-goog-user-project`, …) of an already-sanitised map.
625
- - `deriveConnectionFromHeaders(headers)` — promotes CDN-injected headers (Cloudflare / Fastly / Vercel / CloudFront geo, client-IP, and TLS-fingerprint headers, incl. `cf-ja3-hash` / `cf-ja4`) into the `connection` block, so SDK-only deployments behind a CDN still get connection-layer attribution. Native edge signals remain authoritative; this is the SDK-level fallback.
649
+ - `deriveConnectionFromHeaders(headers)` — promotes CDN-injected headers (Cloudflare / Fastly / Vercel / CloudFront geo, client-IP, TLS-fingerprint, and header-structure headers — incl. `cf-ja3-hash` / `cf-ja4` and `cloudfront-viewer-ja3-fingerprint` / `-ja4-fingerprint` from 4.3.0) into the `connection` block, so SDK-only deployments behind a CDN **you control** still get connection-layer attribution (see the capture matrix for the platform-managed-CDN caveat). Values are truncated to `MAX_CONNECTION_VALUE_CHARS` (256 — the platform bound). Native edge signals remain authoritative; this is the SDK-level fallback.
626
650
  - `buildSdkObservedMetadata(rawHeaders)` — the one builder the express / nextjs / mcp adapters share: sanitised headers + `apiKeyFormat` + `platformHeaders` + derived `connection` + `schemaVersion`.
627
651
  - `CAPTURE_SCHEMA_VERSION` — the capture-semantics version stamped on every `ObservedMetadata` (currently `1`). Capture semantics are **frozen within a major version**: `schemaVersion` bumps only on a semantic change to what is kept/dropped/derived, never for additive signals. The verify body's `sdkVersion` disambiguates builds.
628
652
  - Types: `ObservedMetadata` (`{ headers, apiKeyFormat?, platformHeaders?, connection?, schemaVersion? }` — `connection` is populated natively by edge adapters and via CDN-header derivation by SDK adapters), `SanitizeHeadersResult`.
@@ -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-DqfPU5Bl.mjs';
3
- import '../types-r850cOt0.mjs';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-C7Rb-w0d.mjs';
3
+ import '../types-C0zfSXMB.mjs';
4
4
  import '../metadata-capture.mjs';
5
5
 
6
6
  /**
@@ -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-BLUx92FJ.js';
3
- import '../types-sMxBT-nM.js';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-nIh3HtuA.js';
3
+ import '../types-DwSfO7Sb.js';
4
4
  import '../metadata-capture.js';
5
5
 
6
6
  /**
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- import '../types-r850cOt0.mjs';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-D_4hTn5Z.mjs';
2
+ import '../types-C0zfSXMB.mjs';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-CR6F3njD.mjs';
4
4
  import '../metadata-capture.mjs';
@@ -1,4 +1,4 @@
1
1
  import 'express';
2
- import '../types-sMxBT-nM.js';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-BVd1_3FE.js';
2
+ import '../types-DwSfO7Sb.js';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-DaqL11tx.js';
4
4
  import '../metadata-capture.js';
@@ -775,6 +775,31 @@ function extractHttpCredentials(headers) {
775
775
  return credentials;
776
776
  }
777
777
 
778
+ // src/verified-hop.ts
779
+ var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
780
+ var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
781
+ function parseVerifiedHop(value) {
782
+ if (!value) return null;
783
+ const parts = value.split(";");
784
+ if (parts.length !== 3) return null;
785
+ const [astraId, sessionId, checkedAtRaw] = parts;
786
+ if (!astraId) return null;
787
+ const checkedAt = Number(checkedAtRaw);
788
+ if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
789
+ return {
790
+ astraId,
791
+ ...sessionId ? { sessionId } : {},
792
+ checkedAt
793
+ };
794
+ }
795
+ function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
796
+ if (!marker) return false;
797
+ if (marker.astraId !== expectedAstraId) return false;
798
+ const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
799
+ const now = opts.now ?? Date.now();
800
+ return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
801
+ }
802
+
778
803
  // src/adapters/http-pdlss.ts
779
804
  var HTTP_METHOD_ACTION_TABLE = {
780
805
  GET: "data.read",
@@ -925,7 +950,9 @@ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
925
950
  "x-astrasync-signature",
926
951
  "x-astrasync-secret",
927
952
  "x-hub-signature",
928
- "x-hub-signature-256"
953
+ "x-hub-signature-256",
954
+ // Short-lived verify dedupe token — must never persist in stored metadata.
955
+ "x-astra-verified-hop"
929
956
  ]);
930
957
  var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
931
958
  var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -1060,16 +1087,25 @@ var CONNECTION_HEADER_SOURCES = [
1060
1087
  ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1061
1088
  ],
1062
1089
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1090
+ ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
1063
1091
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1064
1092
  ["asn", ["cloudfront-viewer-asn"]],
1065
1093
  ["tlsVersion", ["cloudfront-viewer-tls"]],
1066
1094
  ["httpVersion", ["cloudfront-viewer-http-version"]],
1095
+ ["timeZone", ["x-vercel-ip-timezone", "cloudfront-viewer-time-zone"]],
1096
+ ["metroCode", ["cloudfront-viewer-metro-code"]],
1067
1097
  // TLS fingerprints can only be computed where TLS terminates. When a CDN
1068
1098
  // terminates TLS in front of the merchant, its fingerprint headers ARE the
1069
1099
  // connection-layer truth — normalising them here is the permanent design.
1070
- ["tlsFingerprint", ["cf-ja3-hash"]],
1071
- ["ja4", ["cf-ja4"]]
1100
+ // CloudFront delivers JA3/JA4 only via an origin request policy and only
1101
+ // for HTTPS viewer connections.
1102
+ ["tlsFingerprint", ["cf-ja3-hash", "cloudfront-viewer-ja3-fingerprint"]],
1103
+ ["ja4", ["cf-ja4", "cloudfront-viewer-ja4-fingerprint"]],
1104
+ // Header-structure fingerprint: browser/SDK-distinctive header ordering.
1105
+ ["headerOrder", ["cloudfront-viewer-header-order"]],
1106
+ ["headerCount", ["cloudfront-viewer-header-count"]]
1072
1107
  ];
1108
+ var MAX_CONNECTION_VALUE_CHARS = 256;
1073
1109
  function deriveConnectionFromHeaders(headers) {
1074
1110
  if (!headers) return void 0;
1075
1111
  const lower = {};
@@ -1082,11 +1118,15 @@ function deriveConnectionFromHeaders(headers) {
1082
1118
  for (const source of sources) {
1083
1119
  const value = lower[source]?.trim();
1084
1120
  if (value) {
1085
- connection[key] = value;
1121
+ connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);
1086
1122
  break;
1087
1123
  }
1088
1124
  }
1089
1125
  }
1126
+ if (!connection.ip) {
1127
+ const addr = lower["cloudfront-viewer-address"]?.trim();
1128
+ if (addr) connection.ip = addr.replace(/:\d+$/, "");
1129
+ }
1090
1130
  if (!connection.ip) {
1091
1131
  const xff = lower["x-forwarded-for"];
1092
1132
  const client = xff?.split(",")[0]?.trim();
@@ -1527,6 +1567,8 @@ function createMiddleware(options) {
1527
1567
  failOnError = "open",
1528
1568
  caseInsensitiveRouteMatch = false,
1529
1569
  anonymousBeacon = true,
1570
+ trustVerifiedHop = false,
1571
+ verifiedHopMaxAgeMs,
1530
1572
  ...config
1531
1573
  } = options;
1532
1574
  if (config.apiBaseUrl) {
@@ -1619,6 +1661,16 @@ function createMiddleware(options) {
1619
1661
  beaconPassThrough?.(req);
1620
1662
  return next();
1621
1663
  }
1664
+ if (trustVerifiedHop && credentials.astraId) {
1665
+ const hopRaw = req.headers[MCP_VERIFIED_HOP_HEADER.toLowerCase()];
1666
+ const hopValue = typeof hopRaw === "string" ? hopRaw : Array.isArray(hopRaw) ? hopRaw[0] : void 0;
1667
+ const marker = parseVerifiedHop(hopValue);
1668
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
1669
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
1670
+ })) {
1671
+ return next();
1672
+ }
1673
+ }
1622
1674
  const pdlssPair = resolveRequestPdlss(
1623
1675
  req,
1624
1676
  routeConfig,