@kya-os/mcp 1.7.0 → 1.9.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 (232) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +219 -3
  3. package/dist/card/build.d.ts +41 -0
  4. package/dist/card/build.d.ts.map +1 -0
  5. package/dist/card/build.js +47 -0
  6. package/dist/card/build.js.map +1 -0
  7. package/dist/card/builder.d.ts +99 -0
  8. package/dist/card/builder.d.ts.map +1 -0
  9. package/dist/card/builder.js +147 -0
  10. package/dist/card/builder.js.map +1 -0
  11. package/dist/card/cimd.d.ts +92 -0
  12. package/dist/card/cimd.d.ts.map +1 -0
  13. package/dist/card/cimd.js +225 -0
  14. package/dist/card/cimd.js.map +1 -0
  15. package/dist/card/delegation.d.ts +145 -0
  16. package/dist/card/delegation.d.ts.map +1 -0
  17. package/dist/card/delegation.js +293 -0
  18. package/dist/card/delegation.js.map +1 -0
  19. package/dist/card/emit.d.ts +133 -0
  20. package/dist/card/emit.d.ts.map +1 -0
  21. package/dist/card/emit.js +127 -0
  22. package/dist/card/emit.js.map +1 -0
  23. package/dist/card/index.d.ts +43 -0
  24. package/dist/card/index.d.ts.map +1 -0
  25. package/dist/card/index.js +46 -0
  26. package/dist/card/index.js.map +1 -0
  27. package/dist/card/middleware.d.ts +112 -0
  28. package/dist/card/middleware.d.ts.map +1 -0
  29. package/dist/card/middleware.js +121 -0
  30. package/dist/card/middleware.js.map +1 -0
  31. package/dist/card/proof/build.d.ts +22 -0
  32. package/dist/card/proof/build.d.ts.map +1 -0
  33. package/dist/card/proof/build.js +55 -0
  34. package/dist/card/proof/build.js.map +1 -0
  35. package/dist/card/proof/canonical.d.ts +66 -0
  36. package/dist/card/proof/canonical.d.ts.map +1 -0
  37. package/dist/card/proof/canonical.js +131 -0
  38. package/dist/card/proof/canonical.js.map +1 -0
  39. package/dist/card/proof/http-sig.d.ts +69 -0
  40. package/dist/card/proof/http-sig.d.ts.map +1 -0
  41. package/dist/card/proof/http-sig.js +101 -0
  42. package/dist/card/proof/http-sig.js.map +1 -0
  43. package/dist/card/proof/index.d.ts +25 -0
  44. package/dist/card/proof/index.d.ts.map +1 -0
  45. package/dist/card/proof/index.js +25 -0
  46. package/dist/card/proof/index.js.map +1 -0
  47. package/dist/card/proof/nonce-cache.d.ts +67 -0
  48. package/dist/card/proof/nonce-cache.d.ts.map +1 -0
  49. package/dist/card/proof/nonce-cache.js +88 -0
  50. package/dist/card/proof/nonce-cache.js.map +1 -0
  51. package/dist/card/proof/signer.d.ts +32 -0
  52. package/dist/card/proof/signer.d.ts.map +1 -0
  53. package/dist/card/proof/signer.js +59 -0
  54. package/dist/card/proof/signer.js.map +1 -0
  55. package/dist/card/proof/types.d.ts +219 -0
  56. package/dist/card/proof/types.d.ts.map +1 -0
  57. package/dist/card/proof/types.js +87 -0
  58. package/dist/card/proof/types.js.map +1 -0
  59. package/dist/card/proof/verify.d.ts +27 -0
  60. package/dist/card/proof/verify.d.ts.map +1 -0
  61. package/dist/card/proof/verify.js +236 -0
  62. package/dist/card/proof/verify.js.map +1 -0
  63. package/dist/card/resolve.d.ts +97 -0
  64. package/dist/card/resolve.d.ts.map +1 -0
  65. package/dist/card/resolve.js +223 -0
  66. package/dist/card/resolve.js.map +1 -0
  67. package/dist/card/revocation.d.ts +96 -0
  68. package/dist/card/revocation.d.ts.map +1 -0
  69. package/dist/card/revocation.js +190 -0
  70. package/dist/card/revocation.js.map +1 -0
  71. package/dist/card/schema.d.ts +177 -0
  72. package/dist/card/schema.d.ts.map +1 -0
  73. package/dist/card/schema.js +119 -0
  74. package/dist/card/schema.js.map +1 -0
  75. package/dist/card/verify.d.ts +144 -0
  76. package/dist/card/verify.d.ts.map +1 -0
  77. package/dist/card/verify.js +132 -0
  78. package/dist/card/verify.js.map +1 -0
  79. package/dist/delegation/bitstring.d.ts +9 -7
  80. package/dist/delegation/bitstring.d.ts.map +1 -1
  81. package/dist/delegation/bitstring.js +70 -37
  82. package/dist/delegation/bitstring.js.map +1 -1
  83. package/dist/delegation/did-linkage.d.ts +23 -0
  84. package/dist/delegation/did-linkage.d.ts.map +1 -0
  85. package/dist/delegation/did-linkage.js +57 -0
  86. package/dist/delegation/did-linkage.js.map +1 -0
  87. package/dist/delegation/did-resolver-registry.d.ts +7 -0
  88. package/dist/delegation/did-resolver-registry.d.ts.map +1 -0
  89. package/dist/delegation/did-resolver-registry.js +20 -0
  90. package/dist/delegation/did-resolver-registry.js.map +1 -0
  91. package/dist/delegation/did-web-resolver.d.ts +29 -18
  92. package/dist/delegation/did-web-resolver.d.ts.map +1 -1
  93. package/dist/delegation/did-web-resolver.js +65 -35
  94. package/dist/delegation/did-web-resolver.js.map +1 -1
  95. package/dist/delegation/index.d.ts +3 -0
  96. package/dist/delegation/index.d.ts.map +1 -1
  97. package/dist/delegation/index.js +3 -0
  98. package/dist/delegation/index.js.map +1 -1
  99. package/dist/delegation/statuslist-manager.d.ts.map +1 -1
  100. package/dist/delegation/statuslist-manager.js +16 -1
  101. package/dist/delegation/statuslist-manager.js.map +1 -1
  102. package/dist/delegation/vc-jwt-verify.d.ts +61 -0
  103. package/dist/delegation/vc-jwt-verify.d.ts.map +1 -0
  104. package/dist/delegation/vc-jwt-verify.js +131 -0
  105. package/dist/delegation/vc-jwt-verify.js.map +1 -0
  106. package/dist/delegation/vc-verification-checks.d.ts +50 -0
  107. package/dist/delegation/vc-verification-checks.d.ts.map +1 -0
  108. package/dist/delegation/vc-verification-checks.js +212 -0
  109. package/dist/delegation/vc-verification-checks.js.map +1 -0
  110. package/dist/delegation/vc-verifier.d.ts +24 -61
  111. package/dist/delegation/vc-verifier.d.ts.map +1 -1
  112. package/dist/delegation/vc-verifier.js +57 -180
  113. package/dist/delegation/vc-verifier.js.map +1 -1
  114. package/dist/delegation/vc-verifier.types.d.ts +88 -0
  115. package/dist/delegation/vc-verifier.types.d.ts.map +1 -0
  116. package/dist/delegation/vc-verifier.types.js +9 -0
  117. package/dist/delegation/vc-verifier.types.js.map +1 -0
  118. package/dist/index.d.ts +3 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js +2 -0
  121. package/dist/index.js.map +1 -1
  122. package/dist/integrations/cheqd/dlr.d.ts +44 -0
  123. package/dist/integrations/cheqd/dlr.d.ts.map +1 -0
  124. package/dist/integrations/cheqd/dlr.js +86 -0
  125. package/dist/integrations/cheqd/dlr.js.map +1 -0
  126. package/dist/integrations/cheqd/index.d.ts +5 -0
  127. package/dist/integrations/cheqd/index.d.ts.map +1 -0
  128. package/dist/integrations/cheqd/index.js +5 -0
  129. package/dist/integrations/cheqd/index.js.map +1 -0
  130. package/dist/integrations/cheqd/linkage.d.ts +20 -0
  131. package/dist/integrations/cheqd/linkage.d.ts.map +1 -0
  132. package/dist/integrations/cheqd/linkage.js +32 -0
  133. package/dist/integrations/cheqd/linkage.js.map +1 -0
  134. package/dist/integrations/cheqd/registrar.d.ts +85 -0
  135. package/dist/integrations/cheqd/registrar.d.ts.map +1 -0
  136. package/dist/integrations/cheqd/registrar.js +304 -0
  137. package/dist/integrations/cheqd/registrar.js.map +1 -0
  138. package/dist/integrations/cheqd/resolver.d.ts +38 -0
  139. package/dist/integrations/cheqd/resolver.d.ts.map +1 -0
  140. package/dist/integrations/cheqd/resolver.js +156 -0
  141. package/dist/integrations/cheqd/resolver.js.map +1 -0
  142. package/dist/middleware/index.d.ts +2 -0
  143. package/dist/middleware/index.d.ts.map +1 -1
  144. package/dist/middleware/index.js +5 -0
  145. package/dist/middleware/index.js.map +1 -1
  146. package/dist/middleware/with-kya-os.config-types.d.ts +139 -0
  147. package/dist/middleware/with-kya-os.config-types.d.ts.map +1 -0
  148. package/dist/middleware/with-kya-os.config-types.js +9 -0
  149. package/dist/middleware/with-kya-os.config-types.js.map +1 -0
  150. package/dist/middleware/with-kya-os.d.ts +3 -267
  151. package/dist/middleware/with-kya-os.d.ts.map +1 -1
  152. package/dist/middleware/with-kya-os.delegation-gate.d.ts +19 -0
  153. package/dist/middleware/with-kya-os.delegation-gate.d.ts.map +1 -0
  154. package/dist/middleware/with-kya-os.delegation-gate.js +175 -0
  155. package/dist/middleware/with-kya-os.delegation-gate.js.map +1 -0
  156. package/dist/middleware/with-kya-os.delegation-verify.d.ts +51 -0
  157. package/dist/middleware/with-kya-os.delegation-verify.d.ts.map +1 -0
  158. package/dist/middleware/with-kya-os.delegation-verify.js +165 -0
  159. package/dist/middleware/with-kya-os.delegation-verify.js.map +1 -0
  160. package/dist/middleware/with-kya-os.deps.d.ts +40 -0
  161. package/dist/middleware/with-kya-os.deps.d.ts.map +1 -0
  162. package/dist/middleware/with-kya-os.deps.js +9 -0
  163. package/dist/middleware/with-kya-os.deps.js.map +1 -0
  164. package/dist/middleware/with-kya-os.grants.d.ts +30 -0
  165. package/dist/middleware/with-kya-os.grants.d.ts.map +1 -0
  166. package/dist/middleware/with-kya-os.grants.js +145 -0
  167. package/dist/middleware/with-kya-os.grants.js.map +1 -0
  168. package/dist/middleware/with-kya-os.helpers.d.ts +24 -0
  169. package/dist/middleware/with-kya-os.helpers.d.ts.map +1 -0
  170. package/dist/middleware/with-kya-os.helpers.js +57 -0
  171. package/dist/middleware/with-kya-os.helpers.js.map +1 -0
  172. package/dist/middleware/with-kya-os.js +45 -891
  173. package/dist/middleware/with-kya-os.js.map +1 -1
  174. package/dist/middleware/with-kya-os.policy-gate.d.ts +16 -0
  175. package/dist/middleware/with-kya-os.policy-gate.d.ts.map +1 -0
  176. package/dist/middleware/with-kya-os.policy-gate.js +98 -0
  177. package/dist/middleware/with-kya-os.policy-gate.js.map +1 -0
  178. package/dist/middleware/with-kya-os.protocol.d.ts +29 -0
  179. package/dist/middleware/with-kya-os.protocol.d.ts.map +1 -0
  180. package/dist/middleware/with-kya-os.protocol.js +121 -0
  181. package/dist/middleware/with-kya-os.protocol.js.map +1 -0
  182. package/dist/middleware/with-kya-os.session.d.ts +32 -0
  183. package/dist/middleware/with-kya-os.session.d.ts.map +1 -0
  184. package/dist/middleware/with-kya-os.session.js +201 -0
  185. package/dist/middleware/with-kya-os.session.js.map +1 -0
  186. package/dist/middleware/with-kya-os.types.d.ts +178 -0
  187. package/dist/middleware/with-kya-os.types.d.ts.map +1 -0
  188. package/dist/middleware/with-kya-os.types.js +13 -0
  189. package/dist/middleware/with-kya-os.types.js.map +1 -0
  190. package/dist/providers/runtime-fetch.d.ts +8 -0
  191. package/dist/providers/runtime-fetch.d.ts.map +1 -1
  192. package/dist/providers/runtime-fetch.js +17 -0
  193. package/dist/providers/runtime-fetch.js.map +1 -1
  194. package/dist/providers/web-crypto.d.ts.map +1 -1
  195. package/dist/providers/web-crypto.js +15 -7
  196. package/dist/providers/web-crypto.js.map +1 -1
  197. package/dist/utils/guards.d.ts +2 -0
  198. package/dist/utils/guards.d.ts.map +1 -0
  199. package/dist/utils/guards.js +4 -0
  200. package/dist/utils/guards.js.map +1 -0
  201. package/dist/utils/index.d.ts +3 -0
  202. package/dist/utils/index.d.ts.map +1 -1
  203. package/dist/utils/index.js +3 -0
  204. package/dist/utils/index.js.map +1 -1
  205. package/dist/utils/ip-classifier.d.ts +12 -0
  206. package/dist/utils/ip-classifier.d.ts.map +1 -0
  207. package/dist/utils/ip-classifier.js +153 -0
  208. package/dist/utils/ip-classifier.js.map +1 -0
  209. package/dist/utils/safe-fetch-transports.d.ts +40 -0
  210. package/dist/utils/safe-fetch-transports.d.ts.map +1 -0
  211. package/dist/utils/safe-fetch-transports.js +121 -0
  212. package/dist/utils/safe-fetch-transports.js.map +1 -0
  213. package/dist/utils/safe-fetch-types.d.ts +66 -0
  214. package/dist/utils/safe-fetch-types.d.ts.map +1 -0
  215. package/dist/utils/safe-fetch-types.js +10 -0
  216. package/dist/utils/safe-fetch-types.js.map +1 -0
  217. package/dist/utils/safe-fetch.d.ts +40 -0
  218. package/dist/utils/safe-fetch.d.ts.map +1 -0
  219. package/dist/utils/safe-fetch.js +188 -0
  220. package/dist/utils/safe-fetch.js.map +1 -0
  221. package/dist/utils/statuslist-purpose.d.ts +12 -0
  222. package/dist/utils/statuslist-purpose.d.ts.map +1 -0
  223. package/dist/utils/statuslist-purpose.js +19 -0
  224. package/dist/utils/statuslist-purpose.js.map +1 -0
  225. package/dist/utils/url.d.ts +2 -0
  226. package/dist/utils/url.d.ts.map +1 -0
  227. package/dist/utils/url.js +8 -0
  228. package/dist/utils/url.js.map +1 -0
  229. package/package.json +25 -5
  230. package/schemas/README.md +2 -1
  231. package/schemas/card-delegation-credential.json +239 -0
  232. package/schemas/kya-os-card.schema.json +284 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * SSRF-hardened fetch — the Node I/O adapters (the `SafeFetchTransport` + `DnsLookup` defaults).
3
+ *
4
+ * Two transports ship. The DEFAULT `nodeHttpsTransport` PINS the connection to the pre-validated
5
+ * IP (closing the DNS-rebinding TOCTOU window) via a custom `node:https` `lookup`. That pinned path
6
+ * misbehaves in some serverless runtimes — it failed 100% of fetches in the Vercel Node serverless
7
+ * runtime on 2026-07-02, a failure the mocked-transport unit suite cannot catch. For those runtimes,
8
+ * select `fetchTransport`: it connects by HOSTNAME through global `fetch` (the SSRF screen has
9
+ * already run in the policy layer via `resolveAndPin`). All addressing policy lives in
10
+ * `./ip-classifier`; the seam vocabulary lives in `./safe-fetch-types`. No crypto here.
11
+ */
12
+ import type { DnsLookup, SafeFetchTransport } from './safe-fetch-types.js';
13
+ /** Default DNS seam — resolve every address (`all: true`) for full SSRF screening. */
14
+ export declare const defaultLookup: DnsLookup;
15
+ /**
16
+ * Fetch-based transport — for runtimes where the `node:https` pinned-connect path misbehaves
17
+ * (the Vercel Node serverless runtime failed 100% of fetches on 2026-07-02). Select it explicitly
18
+ * with `transport: fetchTransport`, or rely on the auto-select when `node:https` is unavailable.
19
+ *
20
+ * The SSRF screen is UNCHANGED: for non-first-party origins `resolveAndPin` has already run EVERY
21
+ * resolved address through the `lookup` seam + `isBlockedAddress` policy before this transport is
22
+ * called. This variant then connects by HOSTNAME via global `fetch` (`redirect: 'manual'`, so a
23
+ * 3xx flows back to the caller's redirect loop) — it does NOT pin `init.pinnedAddress`.
24
+ *
25
+ * TOCTOU: `nodeHttpsTransport` PINS the validated IP, closing the DNS-rebinding window. This
26
+ * variant validates-then-reconnects-by-name, so a hostile resolver could hand `lookup` a public
27
+ * IP and `fetch`'s own resolution a private one — a narrow validated-then-reconnected-by-name
28
+ * window it knowingly accepts for serverless compatibility. Prefer the pinned transport where
29
+ * `node:https` works; the first-party escape hatch avoids the window for a caller's own origin.
30
+ */
31
+ export declare const fetchTransport: SafeFetchTransport;
32
+ /** Default transport — node:https pinned to the validated IP, TLS SNI preserved, body-capped. */
33
+ export declare const nodeHttpsTransport: SafeFetchTransport;
34
+ /**
35
+ * Pick the default transport: the `node:https` pinned transport when it is available, else the
36
+ * fetch-based transport. This only covers ABSENCE of `node:https`; a runtime where the pinned
37
+ * path is present-but-broken (e.g. Vercel Node serverless) must pass `transport: fetchTransport`.
38
+ */
39
+ export declare function selectDefaultTransport(): SafeFetchTransport;
40
+ //# sourceMappingURL=safe-fetch-transports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch-transports.d.ts","sourceRoot":"","sources":["../../src/utils/safe-fetch-transports.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAe,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAExF,sFAAsF;AACtF,eAAO,MAAM,aAAa,EAAE,SAG3B,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,EAAE,kBAG5B,CAAC;AA2BF,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,EAAE,kBAsC7B,CAAC;AAgBL;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,kBAAkB,CAE3D"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * SSRF-hardened fetch — the Node I/O adapters (the `SafeFetchTransport` + `DnsLookup` defaults).
3
+ *
4
+ * Two transports ship. The DEFAULT `nodeHttpsTransport` PINS the connection to the pre-validated
5
+ * IP (closing the DNS-rebinding TOCTOU window) via a custom `node:https` `lookup`. That pinned path
6
+ * misbehaves in some serverless runtimes — it failed 100% of fetches in the Vercel Node serverless
7
+ * runtime on 2026-07-02, a failure the mocked-transport unit suite cannot catch. For those runtimes,
8
+ * select `fetchTransport`: it connects by HOSTNAME through global `fetch` (the SSRF screen has
9
+ * already run in the policy layer via `resolveAndPin`). All addressing policy lives in
10
+ * `./ip-classifier`; the seam vocabulary lives in `./safe-fetch-types`. No crypto here.
11
+ */
12
+ import { lookup as nodeDnsLookup } from 'node:dns/promises';
13
+ import { request as httpsRequest } from 'node:https';
14
+ /** Default DNS seam — resolve every address (`all: true`) for full SSRF screening. */
15
+ export const defaultLookup = async (hostname) => {
16
+ const resolved = await nodeDnsLookup(hostname, { all: true, verbatim: true });
17
+ return resolved.map((entry) => ({ address: entry.address, family: entry.family }));
18
+ };
19
+ /**
20
+ * Fetch-based transport — for runtimes where the `node:https` pinned-connect path misbehaves
21
+ * (the Vercel Node serverless runtime failed 100% of fetches on 2026-07-02). Select it explicitly
22
+ * with `transport: fetchTransport`, or rely on the auto-select when `node:https` is unavailable.
23
+ *
24
+ * The SSRF screen is UNCHANGED: for non-first-party origins `resolveAndPin` has already run EVERY
25
+ * resolved address through the `lookup` seam + `isBlockedAddress` policy before this transport is
26
+ * called. This variant then connects by HOSTNAME via global `fetch` (`redirect: 'manual'`, so a
27
+ * 3xx flows back to the caller's redirect loop) — it does NOT pin `init.pinnedAddress`.
28
+ *
29
+ * TOCTOU: `nodeHttpsTransport` PINS the validated IP, closing the DNS-rebinding window. This
30
+ * variant validates-then-reconnects-by-name, so a hostile resolver could hand `lookup` a public
31
+ * IP and `fetch`'s own resolution a private one — a narrow validated-then-reconnected-by-name
32
+ * window it knowingly accepts for serverless compatibility. Prefer the pinned transport where
33
+ * `node:https` works; the first-party escape hatch avoids the window for a caller's own origin.
34
+ */
35
+ export const fetchTransport = async (url, init) => {
36
+ const response = await fetch(url, { method: 'GET', redirect: 'manual', signal: init.signal });
37
+ return readCapped(response, init.maxBytes);
38
+ };
39
+ /** Stream a fetch Response body into a RawResponse, enforcing the byte cap mid-stream. */
40
+ async function readCapped(response, maxBytes) {
41
+ const reader = response.body?.getReader();
42
+ const chunks = [];
43
+ let size = 0;
44
+ if (reader) {
45
+ for (;;) {
46
+ const { done, value } = await reader.read();
47
+ if (done)
48
+ break;
49
+ size += value.length;
50
+ if (size > maxBytes) {
51
+ await reader.cancel();
52
+ throw new Error(`safe-fetch: response body exceeds size cap ${maxBytes}`);
53
+ }
54
+ chunks.push(value);
55
+ }
56
+ }
57
+ const body = Buffer.concat(chunks).toString('utf8');
58
+ return {
59
+ status: response.status,
60
+ headers: { get: (name) => response.headers.get(name) },
61
+ text: () => Promise.resolve(body),
62
+ };
63
+ }
64
+ /** Default transport — node:https pinned to the validated IP, TLS SNI preserved, body-capped. */
65
+ export const nodeHttpsTransport = (url, init) => new Promise((resolve, reject) => {
66
+ // Return the pinned IP for BOTH lookup call-shapes. Node ≥20 defaults to
67
+ // `autoSelectFamily=true`, which invokes a custom lookup with `{ all: true }` and expects an
68
+ // ARRAY of `{ address, family }`; a scalar 3-arg callback yields "Invalid IP address:
69
+ // undefined" and breaks every real request. Honour the array form when `all` is asked,
70
+ // the scalar form otherwise.
71
+ const family = init.family === 6 ? 6 : 4;
72
+ const pinnedLookup = (_hostname, options, cb) => {
73
+ if (typeof options === 'object' && options?.all) {
74
+ cb(null, [
75
+ { address: init.pinnedAddress, family },
76
+ ]);
77
+ }
78
+ else {
79
+ cb(null, init.pinnedAddress, family);
80
+ }
81
+ };
82
+ const req = httpsRequest(url, { method: 'GET', servername: init.hostname, signal: init.signal, lookup: pinnedLookup }, (res) => {
83
+ const chunks = [];
84
+ let size = 0;
85
+ res.on('data', (chunk) => {
86
+ size += chunk.length;
87
+ if (size > init.maxBytes) {
88
+ req.destroy();
89
+ reject(new Error(`safe-fetch: response body exceeds size cap ${init.maxBytes}`));
90
+ return;
91
+ }
92
+ chunks.push(chunk);
93
+ });
94
+ res.on('end', () => resolve(toRawResponse(res, chunks)));
95
+ res.on('error', reject);
96
+ });
97
+ req.on('error', reject);
98
+ req.end();
99
+ });
100
+ /** Project a node:http IncomingMessage + buffered chunks into the RawResponse shape. */
101
+ function toRawResponse(res, chunks) {
102
+ return {
103
+ status: res.statusCode ?? 0,
104
+ headers: {
105
+ get: (name) => {
106
+ const value = res.headers[name.toLowerCase()];
107
+ return Array.isArray(value) ? (value[0] ?? null) : (value ?? null);
108
+ },
109
+ },
110
+ text: () => Promise.resolve(Buffer.concat(chunks).toString('utf8')),
111
+ };
112
+ }
113
+ /**
114
+ * Pick the default transport: the `node:https` pinned transport when it is available, else the
115
+ * fetch-based transport. This only covers ABSENCE of `node:https`; a runtime where the pinned
116
+ * path is present-but-broken (e.g. Vercel Node serverless) must pass `transport: fetchTransport`.
117
+ */
118
+ export function selectDefaultTransport() {
119
+ return typeof httpsRequest === 'function' ? nodeHttpsTransport : fetchTransport;
120
+ }
121
+ //# sourceMappingURL=safe-fetch-transports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch-transports.js","sourceRoot":"","sources":["../../src/utils/safe-fetch-transports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * SSRF-hardened fetch — the shared seam types.
3
+ *
4
+ * Kept in their own module so the pure address classifier (`./ip-classifier`), the Node I/O
5
+ * adapters (`./safe-fetch-transports`), and the policy orchestrator (`./safe-fetch`) can all
6
+ * depend on the vocabulary without any import cycle. `./safe-fetch` re-exports every name here,
7
+ * so the public surface (`@kya-os/mcp` barrels) is unchanged.
8
+ */
9
+ /** A resolved address + its IP family (4 or 6). */
10
+ export interface DnsAddress {
11
+ address: string;
12
+ family: number;
13
+ }
14
+ /** DNS resolution seam — returns every address the host resolves to. */
15
+ export type DnsLookup = (hostname: string) => Promise<DnsAddress[]>;
16
+ /** The minimal response a transport yields; safe-fetch interprets status/headers/body. */
17
+ export interface RawResponse {
18
+ status: number;
19
+ headers: {
20
+ get(name: string): string | null;
21
+ };
22
+ text: () => Promise<string>;
23
+ }
24
+ /** Per-request context handed to the transport (connection already validated + pinned). */
25
+ export interface TransportInit {
26
+ signal: AbortSignal;
27
+ hostname: string;
28
+ pinnedAddress: string;
29
+ family: number;
30
+ maxBytes: number;
31
+ }
32
+ /** Transport seam — performs ONE request (no redirect following) to the pinned address. */
33
+ export type SafeFetchTransport = (url: string, init: TransportInit) => Promise<RawResponse>;
34
+ /** The fetcher safe-fetch returns — structurally compatible with the card's `FetchLike`. */
35
+ export interface SafeFetchResponse {
36
+ ok: boolean;
37
+ status: number;
38
+ json: () => Promise<unknown>;
39
+ }
40
+ export type SafeFetch = (url: string) => Promise<SafeFetchResponse>;
41
+ export interface SafeFetchOptions {
42
+ /** DNS seam (default: `node:dns` resolving every address). */
43
+ lookup?: DnsLookup;
44
+ /**
45
+ * Transport seam. Default auto-selects `nodeHttpsTransport` when `node:https` is available,
46
+ * else `fetchTransport`. Serverless runtimes where the pinned path is broken (not absent)
47
+ * should pass `fetchTransport` explicitly.
48
+ */
49
+ transport?: SafeFetchTransport;
50
+ /**
51
+ * First-party origins the caller OWNS (e.g. `["https://api.example.com"]`). A request whose
52
+ * origin equals one of these skips the SSRF resolve-and-pin screen and is fetched plain by
53
+ * name — origin-equality is not attacker-routable. Each entry MUST be a valid https origin
54
+ * (fail-closed otherwise); every non-listed origin still takes the guarded path.
55
+ */
56
+ allowOrigins?: string[];
57
+ /** Convenience singular form of {@link SafeFetchOptions.allowOrigins}. */
58
+ firstPartyOrigin?: string;
59
+ /** Request timeout per hop, in ms (default 5000). */
60
+ timeoutMs?: number;
61
+ /** Maximum response body size, in bytes (default 1 MiB). */
62
+ maxBytes?: number;
63
+ /** Maximum same-origin redirects to follow (default 3). */
64
+ maxRedirects?: number;
65
+ }
66
+ //# sourceMappingURL=safe-fetch-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch-types.d.ts","sourceRoot":"","sources":["../../src/utils/safe-fetch-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,mDAAmD;AACnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;AAEpE,0FAA0F;AAC1F,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED,2FAA2F;AAC3F,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,2FAA2F;AAC3F,MAAM,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAE5F,4FAA4F;AAC5F,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9B;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEpE,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SSRF-hardened fetch — the shared seam types.
3
+ *
4
+ * Kept in their own module so the pure address classifier (`./ip-classifier`), the Node I/O
5
+ * adapters (`./safe-fetch-transports`), and the policy orchestrator (`./safe-fetch`) can all
6
+ * depend on the vocabulary without any import cycle. `./safe-fetch` re-exports every name here,
7
+ * so the public surface (`@kya-os/mcp` barrels) is unchanged.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=safe-fetch-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch-types.js","sourceRoot":"","sources":["../../src/utils/safe-fetch-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * SSRF-hardened outbound fetch (safe-fetch) — the policy orchestrator.
3
+ *
4
+ * `resolveCard` — and, later, JWKS + status-list resolution — follows URLs an attacker can
5
+ * influence across four discovery surfaces, so every outbound request goes through this
6
+ * guard. `createSafeFetch` returns a fetcher structurally compatible with the card's
7
+ * `FetchLike` ({ ok, status, json }) that enforces, fail-closed:
8
+ *
9
+ * - https-only (no http:, file:, data:, …);
10
+ * - the connection IP is a PUBLIC UNICAST address (RFC1918, loopback, link-local incl. the
11
+ * 169.254.169.254 cloud-metadata endpoint, IPv6 ULA/link-local, NAT64/6to4/Teredo transition
12
+ * ranges are denied — see `./ip-classifier`). Each HOP is resolved ONCE and its connection is
13
+ * PINNED to that validated address, closing the DNS-rebinding (TOCTOU) window within the hop; a
14
+ * same-origin redirect to a different host triggers a fresh resolve-and-pin cycle for that hop;
15
+ * - cross-origin redirects are blocked (manual handling; a 3xx to a different origin is
16
+ * rejected, same-origin redirects are followed up to a small cap);
17
+ * - a response size cap and an AbortSignal-backed timeout bound resource use.
18
+ *
19
+ * The two Node transports live in `./safe-fetch-transports`; the seam vocabulary in
20
+ * `./safe-fetch-types`; the address policy in `./ip-classifier`. This module owns only the
21
+ * hop-by-hop POLICY (scheme, resolve-and-pin, redirect, size cap, timeout, first-party escape
22
+ * hatch) and re-exports the transports + classifier + types so the public surface is one import.
23
+ *
24
+ * First-party escape hatch: `allowOrigins` / `firstPartyOrigin` list origins a caller owns. A
25
+ * request whose origin equals a configured one is fetched plain (origin-equality is not
26
+ * attacker-routable); every other origin still takes the guarded path, fail-closed.
27
+ *
28
+ * All I/O is injected via pluggable seams (`lookup`, `transport`) so the policy is unit tested
29
+ * with mocked DNS + transport. No crypto here — no mcp-i-core dependency leaks in.
30
+ */
31
+ import type { SafeFetch, SafeFetchOptions } from './safe-fetch-types.js';
32
+ export * from './safe-fetch-types.js';
33
+ export { isBlockedAddress } from './ip-classifier.js';
34
+ export { defaultLookup, fetchTransport, nodeHttpsTransport, selectDefaultTransport, } from './safe-fetch-transports.js';
35
+ /**
36
+ * Build an SSRF-hardened fetcher. The returned function is structurally compatible with the
37
+ * card's `FetchLike`: `(url) => Promise<{ ok, status, json }>`.
38
+ */
39
+ export declare function createSafeFetch(options?: SafeFetchOptions): SafeFetch;
40
+ //# sourceMappingURL=safe-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch.d.ts","sourceRoot":"","sources":["../../src/utils/safe-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAQH,OAAO,KAAK,EAIV,SAAS,EACT,gBAAgB,EAGjB,MAAM,uBAAuB,CAAC;AAG/B,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AA6EpC;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAuBzE"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * SSRF-hardened outbound fetch (safe-fetch) — the policy orchestrator.
3
+ *
4
+ * `resolveCard` — and, later, JWKS + status-list resolution — follows URLs an attacker can
5
+ * influence across four discovery surfaces, so every outbound request goes through this
6
+ * guard. `createSafeFetch` returns a fetcher structurally compatible with the card's
7
+ * `FetchLike` ({ ok, status, json }) that enforces, fail-closed:
8
+ *
9
+ * - https-only (no http:, file:, data:, …);
10
+ * - the connection IP is a PUBLIC UNICAST address (RFC1918, loopback, link-local incl. the
11
+ * 169.254.169.254 cloud-metadata endpoint, IPv6 ULA/link-local, NAT64/6to4/Teredo transition
12
+ * ranges are denied — see `./ip-classifier`). Each HOP is resolved ONCE and its connection is
13
+ * PINNED to that validated address, closing the DNS-rebinding (TOCTOU) window within the hop; a
14
+ * same-origin redirect to a different host triggers a fresh resolve-and-pin cycle for that hop;
15
+ * - cross-origin redirects are blocked (manual handling; a 3xx to a different origin is
16
+ * rejected, same-origin redirects are followed up to a small cap);
17
+ * - a response size cap and an AbortSignal-backed timeout bound resource use.
18
+ *
19
+ * The two Node transports live in `./safe-fetch-transports`; the seam vocabulary in
20
+ * `./safe-fetch-types`; the address policy in `./ip-classifier`. This module owns only the
21
+ * hop-by-hop POLICY (scheme, resolve-and-pin, redirect, size cap, timeout, first-party escape
22
+ * hatch) and re-exports the transports + classifier + types so the public surface is one import.
23
+ *
24
+ * First-party escape hatch: `allowOrigins` / `firstPartyOrigin` list origins a caller owns. A
25
+ * request whose origin equals a configured one is fetched plain (origin-equality is not
26
+ * attacker-routable); every other origin still takes the guarded path, fail-closed.
27
+ *
28
+ * All I/O is injected via pluggable seams (`lookup`, `transport`) so the policy is unit tested
29
+ * with mocked DNS + transport. No crypto here — no mcp-i-core dependency leaks in.
30
+ */
31
+ import { isBlockedAddress } from './ip-classifier.js';
32
+ import { defaultLookup, fetchTransport, selectDefaultTransport, } from './safe-fetch-transports.js';
33
+ // Re-export the full public surface so a consumer needs only `./safe-fetch` (barrels unchanged).
34
+ export * from './safe-fetch-types.js';
35
+ export { isBlockedAddress } from './ip-classifier.js';
36
+ export { defaultLookup, fetchTransport, nodeHttpsTransport, selectDefaultTransport, } from './safe-fetch-transports.js';
37
+ const DEFAULT_TIMEOUT_MS = 5_000;
38
+ const DEFAULT_MAX_BYTES = 1_048_576;
39
+ const DEFAULT_MAX_REDIRECTS = 3;
40
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
41
+ /** Parse + require https; returns the normalized href. */
42
+ function assertHttpsUrl(raw) {
43
+ let url;
44
+ try {
45
+ url = new URL(raw);
46
+ }
47
+ catch {
48
+ throw new Error(`safe-fetch: invalid URL "${raw}"`);
49
+ }
50
+ if (url.protocol !== 'https:') {
51
+ throw new Error(`safe-fetch: only https is allowed (got "${url.protocol}")`);
52
+ }
53
+ return url.href;
54
+ }
55
+ /** Resolve the host ONCE, reject if ANY resolved address is non-public, and pin the first. */
56
+ async function resolveAndPin(hostname, lookup) {
57
+ const addresses = await lookup(hostname);
58
+ const [first] = addresses;
59
+ if (!first) {
60
+ throw new Error(`safe-fetch: DNS lookup for "${hostname}" returned no addresses`);
61
+ }
62
+ for (const candidate of addresses) {
63
+ if (isBlockedAddress(candidate.address)) {
64
+ throw new Error(`safe-fetch: refusing non-public address ${candidate.address} for host "${hostname}"`);
65
+ }
66
+ }
67
+ return first;
68
+ }
69
+ /** Resolve a 3xx into the next URL, rejecting cross-origin / non-https / missing-Location. */
70
+ function nextRedirectUrl(currentUrl, raw) {
71
+ const location = raw.headers.get('location');
72
+ if (!location) {
73
+ throw new Error(`safe-fetch: ${raw.status} redirect without a Location header`);
74
+ }
75
+ let target;
76
+ try {
77
+ target = new URL(location, currentUrl);
78
+ }
79
+ catch {
80
+ // Keep the module's fail-closed convention: a malformed Location surfaces a `safe-fetch:`-
81
+ // prefixed error rather than a raw TypeError leaking out of the URL constructor.
82
+ throw new Error(`safe-fetch: redirect location is not a valid URL "${location}"`);
83
+ }
84
+ if (target.protocol !== 'https:') {
85
+ throw new Error(`safe-fetch: redirect to non-https target "${target.protocol}" blocked`);
86
+ }
87
+ const origin = new URL(currentUrl).origin;
88
+ if (target.origin !== origin) {
89
+ throw new Error(`safe-fetch: cross-origin redirect blocked (${origin} → ${target.origin})`);
90
+ }
91
+ return target.href;
92
+ }
93
+ /** Enforce the size cap and project a RawResponse into the FetchLike-compatible shape. */
94
+ async function finalize(raw, maxBytes) {
95
+ const declared = Number(raw.headers.get('content-length'));
96
+ if (Number.isFinite(declared) && declared > maxBytes) {
97
+ throw new Error(`safe-fetch: declared content-length ${declared} exceeds cap ${maxBytes}`);
98
+ }
99
+ const body = await raw.text();
100
+ if (Buffer.byteLength(body, 'utf8') > maxBytes) {
101
+ throw new Error(`safe-fetch: response body exceeds size cap ${maxBytes}`);
102
+ }
103
+ return {
104
+ ok: raw.status >= 200 && raw.status < 300,
105
+ status: raw.status,
106
+ json: () => Promise.resolve(JSON.parse(body)),
107
+ };
108
+ }
109
+ /**
110
+ * Build an SSRF-hardened fetcher. The returned function is structurally compatible with the
111
+ * card's `FetchLike`: `(url) => Promise<{ ok, status, json }>`.
112
+ */
113
+ export function createSafeFetch(options = {}) {
114
+ const cfg = {
115
+ lookup: options.lookup ?? defaultLookup,
116
+ transport: options.transport ?? selectDefaultTransport(),
117
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
118
+ maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES,
119
+ trustedOrigins: normalizeTrustedOrigins(options),
120
+ };
121
+ const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
122
+ return async function safeFetch(initialUrl) {
123
+ let url = assertHttpsUrl(initialUrl);
124
+ for (let hop = 0;; hop++) {
125
+ const raw = await requestOnce(url, cfg);
126
+ if (!REDIRECT_STATUSES.has(raw.status)) {
127
+ return finalize(raw, cfg.maxBytes);
128
+ }
129
+ if (hop >= maxRedirects) {
130
+ throw new Error(`safe-fetch: too many redirects (> ${maxRedirects})`);
131
+ }
132
+ url = nextRedirectUrl(url, raw);
133
+ }
134
+ };
135
+ }
136
+ /**
137
+ * Perform ONE hop. First-party origins skip the resolve-and-pin SSRF screen (origin-equality is
138
+ * not attacker-routable) and are fetched plain by name; every other origin is resolved, screened,
139
+ * and pinned before the configured transport runs. Both branches are timeout-bounded.
140
+ */
141
+ async function requestOnce(url, cfg) {
142
+ const { hostname, origin } = new URL(url);
143
+ if (cfg.trustedOrigins.has(origin)) {
144
+ return withTimeout(hostname, cfg.timeoutMs, (signal) => fetchTransport(url, { signal, hostname, pinnedAddress: '', family: 4, maxBytes: cfg.maxBytes }));
145
+ }
146
+ const pinned = await resolveAndPin(hostname, cfg.lookup);
147
+ return withTimeout(hostname, cfg.timeoutMs, (signal) => cfg.transport(url, {
148
+ signal,
149
+ hostname,
150
+ pinnedAddress: pinned.address,
151
+ family: pinned.family,
152
+ maxBytes: cfg.maxBytes,
153
+ }));
154
+ }
155
+ /** Run a transport call under an AbortSignal that fires after `timeoutMs`. */
156
+ async function withTimeout(hostname, timeoutMs, run) {
157
+ const controller = new AbortController();
158
+ const timer = setTimeout(() => controller.abort(new Error(`safe-fetch: request to ${hostname} timed out after ${timeoutMs}ms`)), timeoutMs);
159
+ try {
160
+ return await run(controller.signal);
161
+ }
162
+ finally {
163
+ clearTimeout(timer);
164
+ }
165
+ }
166
+ /**
167
+ * Build the set of first-party origins that bypass the SSRF screen. Each `allowOrigins` /
168
+ * `firstPartyOrigin` entry MUST parse as an https origin — fail-closed (throw) otherwise.
169
+ */
170
+ function normalizeTrustedOrigins(options) {
171
+ const raw = [...(options.allowOrigins ?? []), ...(options.firstPartyOrigin ? [options.firstPartyOrigin] : [])];
172
+ const origins = new Set();
173
+ for (const entry of raw) {
174
+ let url;
175
+ try {
176
+ url = new URL(entry);
177
+ }
178
+ catch {
179
+ throw new Error(`safe-fetch: invalid first-party origin "${entry}"`);
180
+ }
181
+ if (url.protocol !== 'https:') {
182
+ throw new Error(`safe-fetch: first-party origin must be https (got "${entry}")`);
183
+ }
184
+ origins.add(url.origin);
185
+ }
186
+ return origins;
187
+ }
188
+ //# sourceMappingURL=safe-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../../src/utils/safe-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared fail-closed status-list `statusPurpose` check — the SINGLE source of truth for the
3
+ * invariant both the card revocation reader (`src/card/revocation.ts`) and the delegation status
4
+ * manager (`src/delegation/statuslist-manager.ts`) enforce, so the two readers cannot drift.
5
+ */
6
+ /**
7
+ * Fail CLOSED unless the resolved status list's `statusPurpose` is present AND equals `wanted`. A
8
+ * list of a DIFFERENT kind (e.g. a `suspension` list read as `revocation`) must not be accepted —
9
+ * its clear bit would otherwise report "not revoked" from the wrong list, a fail-open bypass.
10
+ */
11
+ export declare function assertStatusPurpose(listPurpose: unknown, wanted: string): void;
12
+ //# sourceMappingURL=statuslist-purpose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statuslist-purpose.d.ts","sourceRoot":"","sources":["../../src/utils/statuslist-purpose.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAO9E"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shared fail-closed status-list `statusPurpose` check — the SINGLE source of truth for the
3
+ * invariant both the card revocation reader (`src/card/revocation.ts`) and the delegation status
4
+ * manager (`src/delegation/statuslist-manager.ts`) enforce, so the two readers cannot drift.
5
+ */
6
+ /**
7
+ * Fail CLOSED unless the resolved status list's `statusPurpose` is present AND equals `wanted`. A
8
+ * list of a DIFFERENT kind (e.g. a `suspension` list read as `revocation`) must not be accepted —
9
+ * its clear bit would otherwise report "not revoked" from the wrong list, a fail-open bypass.
10
+ */
11
+ export function assertStatusPurpose(listPurpose, wanted) {
12
+ if (typeof listPurpose !== 'string') {
13
+ throw new Error(`status list has no statusPurpose (expected "${wanted}") — fail-closed`);
14
+ }
15
+ if (listPurpose !== wanted) {
16
+ throw new Error(`status list statusPurpose mismatch: "${listPurpose}" ≠ "${wanted}"`);
17
+ }
18
+ }
19
+ //# sourceMappingURL=statuslist-purpose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statuslist-purpose.js","sourceRoot":"","sources":["../../src/utils/statuslist-purpose.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAoB,EAAE,MAAc;IACtE,IAAI,OAAO,WAAW,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,+CAA+C,MAAM,kBAAkB,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wCAAwC,WAAW,QAAQ,MAAM,GAAG,CAAC,CAAC;IACxF,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function stripTrailingSlashes(value: string): string;
2
+ //# sourceMappingURL=url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM1D"}
@@ -0,0 +1,8 @@
1
+ export function stripTrailingSlashes(value) {
2
+ let end = value.length;
3
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
4
+ end--;
5
+ }
6
+ return value.slice(0, end);
7
+ }
8
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,IAAI,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;QACnD,GAAG,EAAE,CAAC;IACR,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC7B,CAAC"}