@opensyber/tokenforge 0.1.2 → 1.1.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 (184) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +329 -52
  3. package/dist/adapters/astro.d.ts +64 -0
  4. package/dist/adapters/astro.d.ts.map +1 -0
  5. package/dist/adapters/astro.js +104 -0
  6. package/dist/adapters/astro.js.map +1 -0
  7. package/dist/adapters/cloud-v2-bridge.d.ts +8 -0
  8. package/dist/adapters/cloud-v2-bridge.d.ts.map +1 -0
  9. package/dist/adapters/cloud-v2-bridge.js +24 -0
  10. package/dist/adapters/cloud-v2-bridge.js.map +1 -0
  11. package/dist/adapters/express.d.ts +30 -2
  12. package/dist/adapters/express.d.ts.map +1 -1
  13. package/dist/adapters/express.js +65 -0
  14. package/dist/adapters/express.js.map +1 -1
  15. package/dist/adapters/fastify.d.ts +32 -6
  16. package/dist/adapters/fastify.d.ts.map +1 -1
  17. package/dist/adapters/fastify.js +65 -0
  18. package/dist/adapters/fastify.js.map +1 -1
  19. package/dist/adapters/hono.d.ts +13 -1
  20. package/dist/adapters/hono.d.ts.map +1 -1
  21. package/dist/adapters/hono.js +14 -1
  22. package/dist/adapters/hono.js.map +1 -1
  23. package/dist/adapters/index.d.ts +5 -3
  24. package/dist/adapters/index.d.ts.map +1 -1
  25. package/dist/adapters/index.js +5 -3
  26. package/dist/adapters/index.js.map +1 -1
  27. package/dist/adapters/nextjs.d.ts +31 -0
  28. package/dist/adapters/nextjs.d.ts.map +1 -1
  29. package/dist/adapters/nextjs.js +83 -30
  30. package/dist/adapters/nextjs.js.map +1 -1
  31. package/dist/adapters/sveltekit.d.ts +66 -0
  32. package/dist/adapters/sveltekit.d.ts.map +1 -0
  33. package/dist/adapters/sveltekit.js +101 -0
  34. package/dist/adapters/sveltekit.js.map +1 -0
  35. package/dist/client/action-signer.d.ts +53 -0
  36. package/dist/client/action-signer.d.ts.map +1 -0
  37. package/dist/client/action-signer.js +48 -0
  38. package/dist/client/action-signer.js.map +1 -0
  39. package/dist/client/binding.d.ts +24 -7
  40. package/dist/client/binding.d.ts.map +1 -1
  41. package/dist/client/binding.js +17 -6
  42. package/dist/client/binding.js.map +1 -1
  43. package/dist/client/cloud-device.d.ts +54 -0
  44. package/dist/client/cloud-device.d.ts.map +1 -0
  45. package/dist/client/cloud-device.js +301 -0
  46. package/dist/client/cloud-device.js.map +1 -0
  47. package/dist/client/crypto.d.ts.map +1 -1
  48. package/dist/client/crypto.js.map +1 -1
  49. package/dist/client/index.d.ts +14 -0
  50. package/dist/client/index.d.ts.map +1 -1
  51. package/dist/client/index.js +30 -2
  52. package/dist/client/index.js.map +1 -1
  53. package/dist/client/interceptor.d.ts.map +1 -1
  54. package/dist/client/interceptor.js +27 -3
  55. package/dist/client/interceptor.js.map +1 -1
  56. package/dist/client/signer.d.ts +40 -3
  57. package/dist/client/signer.d.ts.map +1 -1
  58. package/dist/client/signer.js +56 -5
  59. package/dist/client/signer.js.map +1 -1
  60. package/dist/client/storage.d.ts.map +1 -1
  61. package/dist/client/storage.js.map +1 -1
  62. package/dist/client/webauthn.d.ts +45 -0
  63. package/dist/client/webauthn.d.ts.map +1 -0
  64. package/dist/client/webauthn.js +118 -0
  65. package/dist/client/webauthn.js.map +1 -0
  66. package/dist/protocol/v2.d.ts +23 -0
  67. package/dist/protocol/v2.d.ts.map +1 -0
  68. package/dist/protocol/v2.js +157 -0
  69. package/dist/protocol/v2.js.map +1 -0
  70. package/dist/react/index.d.ts.map +1 -1
  71. package/dist/react/index.js.map +1 -1
  72. package/dist/react/provider.d.ts.map +1 -1
  73. package/dist/react/provider.js.map +1 -1
  74. package/dist/server/action-verify.d.ts +56 -0
  75. package/dist/server/action-verify.d.ts.map +1 -0
  76. package/dist/server/action-verify.js +47 -0
  77. package/dist/server/action-verify.js.map +1 -0
  78. package/dist/server/activity-histogram.d.ts +47 -0
  79. package/dist/server/activity-histogram.d.ts.map +1 -0
  80. package/dist/server/activity-histogram.js +76 -0
  81. package/dist/server/activity-histogram.js.map +1 -0
  82. package/dist/server/aitm-heuristics.d.ts +57 -0
  83. package/dist/server/aitm-heuristics.d.ts.map +1 -0
  84. package/dist/server/aitm-heuristics.js +120 -0
  85. package/dist/server/aitm-heuristics.js.map +1 -0
  86. package/dist/server/bind-with-id-token.d.ts +55 -0
  87. package/dist/server/bind-with-id-token.d.ts.map +1 -0
  88. package/dist/server/bind-with-id-token.js +70 -0
  89. package/dist/server/bind-with-id-token.js.map +1 -0
  90. package/dist/server/bound-cookie.d.ts +39 -0
  91. package/dist/server/bound-cookie.d.ts.map +1 -0
  92. package/dist/server/bound-cookie.js +59 -0
  93. package/dist/server/bound-cookie.js.map +1 -0
  94. package/dist/server/cbor.d.ts +20 -0
  95. package/dist/server/cbor.d.ts.map +1 -0
  96. package/dist/server/cbor.js +110 -0
  97. package/dist/server/cbor.js.map +1 -0
  98. package/dist/server/cloud-bind.d.ts +42 -0
  99. package/dist/server/cloud-bind.d.ts.map +1 -0
  100. package/dist/server/cloud-bind.js +387 -0
  101. package/dist/server/cloud-bind.js.map +1 -0
  102. package/dist/server/cloud-middleware.d.ts +46 -0
  103. package/dist/server/cloud-middleware.d.ts.map +1 -0
  104. package/dist/server/cloud-middleware.js +344 -0
  105. package/dist/server/cloud-middleware.js.map +1 -0
  106. package/dist/server/cloud-verify.d.ts +30 -0
  107. package/dist/server/cloud-verify.d.ts.map +1 -0
  108. package/dist/server/cloud-verify.js +206 -0
  109. package/dist/server/cloud-verify.js.map +1 -0
  110. package/dist/server/dbsc-challenge.d.ts +51 -0
  111. package/dist/server/dbsc-challenge.d.ts.map +1 -0
  112. package/dist/server/dbsc-challenge.js +78 -0
  113. package/dist/server/dbsc-challenge.js.map +1 -0
  114. package/dist/server/dbsc-endpoint.d.ts +20 -0
  115. package/dist/server/dbsc-endpoint.d.ts.map +1 -0
  116. package/dist/server/dbsc-endpoint.js +85 -0
  117. package/dist/server/dbsc-endpoint.js.map +1 -0
  118. package/dist/server/dbsc-verify.d.ts +32 -0
  119. package/dist/server/dbsc-verify.d.ts.map +1 -0
  120. package/dist/server/dbsc-verify.js +65 -0
  121. package/dist/server/dbsc-verify.js.map +1 -0
  122. package/dist/server/identity-profile.d.ts +18 -0
  123. package/dist/server/identity-profile.d.ts.map +1 -0
  124. package/dist/server/identity-profile.js +113 -0
  125. package/dist/server/identity-profile.js.map +1 -0
  126. package/dist/server/index.d.ts +12 -0
  127. package/dist/server/index.d.ts.map +1 -1
  128. package/dist/server/index.js +7 -0
  129. package/dist/server/index.js.map +1 -1
  130. package/dist/server/jws-verify.d.ts +46 -0
  131. package/dist/server/jws-verify.d.ts.map +1 -0
  132. package/dist/server/jws-verify.js +64 -0
  133. package/dist/server/jws-verify.js.map +1 -0
  134. package/dist/server/middleware.d.ts +24 -0
  135. package/dist/server/middleware.d.ts.map +1 -1
  136. package/dist/server/middleware.js +87 -20
  137. package/dist/server/middleware.js.map +1 -1
  138. package/dist/server/oidc-verify.d.ts +71 -0
  139. package/dist/server/oidc-verify.d.ts.map +1 -0
  140. package/dist/server/oidc-verify.js +115 -0
  141. package/dist/server/oidc-verify.js.map +1 -0
  142. package/dist/server/policy.d.ts +47 -0
  143. package/dist/server/policy.d.ts.map +1 -0
  144. package/dist/server/policy.js +84 -0
  145. package/dist/server/policy.js.map +1 -0
  146. package/dist/server/step-up-policy.d.ts +36 -0
  147. package/dist/server/step-up-policy.d.ts.map +1 -0
  148. package/dist/server/step-up-policy.js +101 -0
  149. package/dist/server/step-up-policy.js.map +1 -0
  150. package/dist/server/storage/index.d.ts +3 -0
  151. package/dist/server/storage/index.d.ts.map +1 -0
  152. package/dist/server/storage/index.js +2 -0
  153. package/dist/server/storage/index.js.map +1 -0
  154. package/dist/server/storage/interface.d.ts +73 -0
  155. package/dist/server/storage/interface.d.ts.map +1 -0
  156. package/dist/server/storage/interface.js +2 -0
  157. package/dist/server/storage/interface.js.map +1 -0
  158. package/dist/server/storage/memory.d.ts +50 -0
  159. package/dist/server/storage/memory.d.ts.map +1 -0
  160. package/dist/server/storage/memory.js +121 -0
  161. package/dist/server/storage/memory.js.map +1 -0
  162. package/dist/server/webauthn-verify.d.ts +41 -0
  163. package/dist/server/webauthn-verify.d.ts.map +1 -0
  164. package/dist/server/webauthn-verify.js +175 -0
  165. package/dist/server/webauthn-verify.js.map +1 -0
  166. package/dist/server/webhooks.d.ts +42 -0
  167. package/dist/server/webhooks.d.ts.map +1 -0
  168. package/dist/server/webhooks.js +77 -0
  169. package/dist/server/webhooks.js.map +1 -0
  170. package/dist/shared/action-hash.d.ts +23 -0
  171. package/dist/shared/action-hash.d.ts.map +1 -0
  172. package/dist/shared/action-hash.js +35 -0
  173. package/dist/shared/action-hash.js.map +1 -0
  174. package/dist/shared/identity.d.ts +45 -0
  175. package/dist/shared/identity.d.ts.map +1 -0
  176. package/dist/shared/identity.js +176 -0
  177. package/dist/shared/identity.js.map +1 -0
  178. package/dist/shared/types-internal.d.ts.map +1 -1
  179. package/dist/shared/types-internal.js.map +1 -1
  180. package/dist/shared/types.d.ts +49 -2
  181. package/dist/shared/types.d.ts.map +1 -1
  182. package/dist/shared/types.js +1 -0
  183. package/dist/shared/types.js.map +1 -1
  184. package/package.json +31 -33
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Activity Histogram — 24-bucket time-of-day anomaly detector.
3
+ *
4
+ * Tracks request counts per hour-of-day and scores how unusual the
5
+ * current hour is relative to the user's historical pattern.
6
+ *
7
+ * Storage key pattern: `tf:activity:{userId}`
8
+ */
9
+ /** 24-bucket histogram (index 0-23 = hour of day). */
10
+ export interface ActivityHistogram {
11
+ /** Request counts per hour (index = hour 0-23). */
12
+ buckets: number[];
13
+ /** Sum of all buckets — avoids recomputing on every call. */
14
+ totalRequests: number;
15
+ }
16
+ /** Storage key prefix for activity histograms. */
17
+ export declare const ACTIVITY_KEY_PREFIX = "tf:activity:";
18
+ /**
19
+ * Create an empty histogram with 24 zero-filled buckets.
20
+ * @returns A fresh ActivityHistogram with no recorded activity.
21
+ */
22
+ export declare function emptyHistogram(): ActivityHistogram;
23
+ /**
24
+ * Record a request at the given hour, returning the updated histogram.
25
+ * Pure function — does not mutate the input.
26
+ * @param histogram - Current histogram state.
27
+ * @param hour - Hour of day (0-23).
28
+ * @returns New histogram with the incremented bucket.
29
+ */
30
+ export declare function recordActivity(histogram: ActivityHistogram, hour: number): ActivityHistogram;
31
+ /**
32
+ * Score the current hour against the user's historical activity pattern.
33
+ *
34
+ * Returns 0-5 points:
35
+ * 5 — hour is within normal range (<=1 stddev from mean)
36
+ * 3 — hour is slightly unusual (1-2 stddev from mean)
37
+ * 1 — hour is very unusual (>2 stddev from mean)
38
+ * 0 — hour has zero historical requests AND histogram has >=30 total
39
+ *
40
+ * If totalRequests < 30, returns 5 (not enough data to penalize).
41
+ *
42
+ * @param histogram - The user's activity histogram.
43
+ * @param currentHour - Current hour of day (0-23).
44
+ * @returns Trust score contribution (0-5).
45
+ */
46
+ export declare function scoreTimeOfDay(histogram: ActivityHistogram, currentHour: number): number;
47
+ //# sourceMappingURL=activity-histogram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-histogram.d.ts","sourceRoot":"../src/","sources":["server/activity-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6DAA6D;IAC7D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,CAElD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,iBAAiB,EAC5B,IAAI,EAAE,MAAM,GACX,iBAAiB,CAKnB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,iBAAiB,EAC5B,WAAW,EAAE,MAAM,GAClB,MAAM,CAiBR"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Activity Histogram — 24-bucket time-of-day anomaly detector.
3
+ *
4
+ * Tracks request counts per hour-of-day and scores how unusual the
5
+ * current hour is relative to the user's historical pattern.
6
+ *
7
+ * Storage key pattern: `tf:activity:{userId}`
8
+ */
9
+ /** Storage key prefix for activity histograms. */
10
+ export const ACTIVITY_KEY_PREFIX = 'tf:activity:';
11
+ /**
12
+ * Create an empty histogram with 24 zero-filled buckets.
13
+ * @returns A fresh ActivityHistogram with no recorded activity.
14
+ */
15
+ export function emptyHistogram() {
16
+ return { buckets: new Array(24).fill(0), totalRequests: 0 };
17
+ }
18
+ /**
19
+ * Record a request at the given hour, returning the updated histogram.
20
+ * Pure function — does not mutate the input.
21
+ * @param histogram - Current histogram state.
22
+ * @param hour - Hour of day (0-23).
23
+ * @returns New histogram with the incremented bucket.
24
+ */
25
+ export function recordActivity(histogram, hour) {
26
+ const h = Math.max(0, Math.min(23, Math.floor(hour)));
27
+ const buckets = [...histogram.buckets];
28
+ buckets[h] = (buckets[h] ?? 0) + 1;
29
+ return { buckets, totalRequests: histogram.totalRequests + 1 };
30
+ }
31
+ /**
32
+ * Score the current hour against the user's historical activity pattern.
33
+ *
34
+ * Returns 0-5 points:
35
+ * 5 — hour is within normal range (<=1 stddev from mean)
36
+ * 3 — hour is slightly unusual (1-2 stddev from mean)
37
+ * 1 — hour is very unusual (>2 stddev from mean)
38
+ * 0 — hour has zero historical requests AND histogram has >=30 total
39
+ *
40
+ * If totalRequests < 30, returns 5 (not enough data to penalize).
41
+ *
42
+ * @param histogram - The user's activity histogram.
43
+ * @param currentHour - Current hour of day (0-23).
44
+ * @returns Trust score contribution (0-5).
45
+ */
46
+ export function scoreTimeOfDay(histogram, currentHour) {
47
+ if (histogram.totalRequests < 30)
48
+ return 5;
49
+ const h = Math.max(0, Math.min(23, Math.floor(currentHour)));
50
+ const count = histogram.buckets[h];
51
+ if (count === 0)
52
+ return 0;
53
+ const { mean, stddev } = bucketStats(histogram.buckets);
54
+ if (stddev === 0)
55
+ return 5;
56
+ const deviations = Math.abs((count ?? 0) - mean) / stddev;
57
+ if (deviations <= 1)
58
+ return 5;
59
+ if (deviations <= 2)
60
+ return 3;
61
+ return 1;
62
+ }
63
+ /**
64
+ * Compute mean and standard deviation of non-zero buckets.
65
+ * @param buckets - 24-element array of request counts.
66
+ * @returns Object with mean and stddev of non-zero buckets.
67
+ */
68
+ function bucketStats(buckets) {
69
+ const nonZero = buckets.filter((b) => b > 0);
70
+ if (nonZero.length === 0)
71
+ return { mean: 0, stddev: 0 };
72
+ const mean = nonZero.reduce((a, b) => a + b, 0) / nonZero.length;
73
+ const variance = nonZero.reduce((sum, b) => sum + (b - mean) ** 2, 0) / nonZero.length;
74
+ return { mean, stddev: Math.sqrt(variance) };
75
+ }
76
+ //# sourceMappingURL=activity-histogram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-histogram.js","sourceRoot":"../src/","sources":["server/activity-histogram.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,kDAAkD;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,SAA4B,EAC5B,IAAY;IAEZ,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;AACjE,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,cAAc,CAC5B,SAA4B,EAC5B,WAAmB;IAEnB,IAAI,SAAS,CAAC,aAAa,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IAE3C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE1B,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAExD,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;IAE1D,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,UAAU,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,OAAiB;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAExD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACjE,MAAM,QAAQ,GACZ,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAExE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Adversary-in-the-middle (AitM) heuristic engine. Pure module.
3
+ * Compares a bind-time fingerprint vs. current request, emits anomalies.
4
+ * trust-score subtracts the delta; device-telemetry surfaces the list.
5
+ */
6
+ export interface ClientFingerprint {
7
+ userAgent: string;
8
+ language: string;
9
+ timezone: string;
10
+ colorDepth: number;
11
+ screenWidth: number;
12
+ screenHeight: number;
13
+ /** Optional — only present on protected routes that capture them. */
14
+ origin?: string;
15
+ sni?: string;
16
+ host?: string;
17
+ rttMs?: number;
18
+ /** True when TLS exporter binding succeeded for this request. */
19
+ channelBound?: boolean;
20
+ }
21
+ export type AitmAnomalyKind = 'origin_mismatch' | 'latency_floor' | 'ua_drift' | 'tz_drift' | 'color_drift' | 'resolution_swap' | 'locale_drift' | 'channel_unbound';
22
+ export type AitmConfidence = 'low' | 'medium' | 'high';
23
+ export interface AitmAnomaly {
24
+ kind: AitmAnomalyKind;
25
+ confidence: AitmConfidence;
26
+ details?: Record<string, unknown>;
27
+ }
28
+ export interface AitmEvalOptions {
29
+ /**
30
+ * Persistent latency floor delta (ms) above baseline that's considered
31
+ * suspicious. Default 40ms — typical AitM proxy adds at least this much
32
+ * even after EWMA smoothing.
33
+ */
34
+ rttFloorDeltaMs?: number;
35
+ /**
36
+ * When true, missing TLS-exporter binding emits `channel_unbound`.
37
+ * Default true. Disable on routes that don't require channel binding.
38
+ */
39
+ requireChannelBinding?: boolean;
40
+ }
41
+ /**
42
+ * Evaluate the eight AitM heuristics. Pure function.
43
+ *
44
+ * @param baseline - fingerprint captured at device-bind time
45
+ * @param current - fingerprint of the current request
46
+ * @param opts - thresholds (override per route / tenant)
47
+ * @returns list of anomalies; empty when nothing trips
48
+ */
49
+ export declare function evaluateAitm(baseline: ClientFingerprint, current: ClientFingerprint, opts?: AitmEvalOptions): AitmAnomaly[];
50
+ /**
51
+ * Convert anomalies to a trust-score delta. Negative number; used by
52
+ * TrustScoreEngine to drop score on suspicious sessions. Cap at -60 so
53
+ * a single anomalous request can never block a clean baseline outright
54
+ * — that's the trust score's job once it falls below `step_up_threshold`.
55
+ */
56
+ export declare function aitmScoreDelta(anomalies: AitmAnomaly[]): number;
57
+ //# sourceMappingURL=aitm-heuristics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aitm-heuristics.d.ts","sourceRoot":"../src/","sources":["server/aitm-heuristics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GACvB,iBAAiB,GACjB,eAAe,GACf,UAAU,GACV,UAAU,GACV,aAAa,GACb,iBAAiB,GACjB,cAAc,GACd,iBAAiB,CAAC;AAEtB,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAID;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,EAAE,iBAAiB,EAC1B,IAAI,GAAE,eAAoB,GACzB,WAAW,EAAE,CA0Ff;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,MAAM,CAQ/D"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Adversary-in-the-middle (AitM) heuristic engine. Pure module.
3
+ * Compares a bind-time fingerprint vs. current request, emits anomalies.
4
+ * trust-score subtracts the delta; device-telemetry surfaces the list.
5
+ */
6
+ const DEFAULT_RTT_FLOOR_DELTA_MS = 40;
7
+ /**
8
+ * Evaluate the eight AitM heuristics. Pure function.
9
+ *
10
+ * @param baseline - fingerprint captured at device-bind time
11
+ * @param current - fingerprint of the current request
12
+ * @param opts - thresholds (override per route / tenant)
13
+ * @returns list of anomalies; empty when nothing trips
14
+ */
15
+ export function evaluateAitm(baseline, current, opts = {}) {
16
+ const out = [];
17
+ const rttFloorDelta = opts.rttFloorDeltaMs ?? DEFAULT_RTT_FLOOR_DELTA_MS;
18
+ const requireChannel = opts.requireChannelBinding ?? true;
19
+ if (current.origin && current.sni && current.host) {
20
+ const originHost = safeHost(current.origin);
21
+ const sni = current.sni.toLowerCase();
22
+ const host = current.host.toLowerCase();
23
+ const all = [originHost, sni, host].filter(Boolean);
24
+ const allMatch = all.every((h) => h === all[0]);
25
+ if (!allMatch) {
26
+ out.push({
27
+ kind: 'origin_mismatch',
28
+ confidence: 'high',
29
+ details: { origin: originHost, sni, host },
30
+ });
31
+ }
32
+ }
33
+ if (baseline.userAgent !== current.userAgent) {
34
+ out.push({
35
+ kind: 'ua_drift',
36
+ confidence: 'high',
37
+ details: { from: baseline.userAgent, to: current.userAgent },
38
+ });
39
+ }
40
+ if (baseline.timezone !== current.timezone) {
41
+ out.push({
42
+ kind: 'tz_drift',
43
+ confidence: 'medium',
44
+ details: { from: baseline.timezone, to: current.timezone },
45
+ });
46
+ }
47
+ if (baseline.language !== current.language) {
48
+ out.push({
49
+ kind: 'locale_drift',
50
+ confidence: 'medium',
51
+ details: { from: baseline.language, to: current.language },
52
+ });
53
+ }
54
+ if (baseline.colorDepth !== current.colorDepth) {
55
+ out.push({
56
+ kind: 'color_drift',
57
+ confidence: 'low',
58
+ details: { from: baseline.colorDepth, to: current.colorDepth },
59
+ });
60
+ }
61
+ const baseDim = `${baseline.screenWidth}x${baseline.screenHeight}`;
62
+ const curDim = `${current.screenWidth}x${current.screenHeight}`;
63
+ const swapped = baseline.screenWidth === current.screenHeight &&
64
+ baseline.screenHeight === current.screenWidth &&
65
+ baseline.screenWidth !== baseline.screenHeight;
66
+ if (swapped) {
67
+ out.push({
68
+ kind: 'resolution_swap',
69
+ confidence: 'medium',
70
+ details: { from: baseDim, to: curDim },
71
+ });
72
+ }
73
+ if (typeof baseline.rttMs === 'number' &&
74
+ typeof current.rttMs === 'number' &&
75
+ current.rttMs - baseline.rttMs >= rttFloorDelta) {
76
+ out.push({
77
+ kind: 'latency_floor',
78
+ confidence: 'medium',
79
+ details: {
80
+ baselineMs: baseline.rttMs,
81
+ currentMs: current.rttMs,
82
+ deltaMs: current.rttMs - baseline.rttMs,
83
+ },
84
+ });
85
+ }
86
+ if (requireChannel && current.channelBound === false) {
87
+ out.push({
88
+ kind: 'channel_unbound',
89
+ confidence: 'high',
90
+ });
91
+ }
92
+ return out;
93
+ }
94
+ /**
95
+ * Convert anomalies to a trust-score delta. Negative number; used by
96
+ * TrustScoreEngine to drop score on suspicious sessions. Cap at -60 so
97
+ * a single anomalous request can never block a clean baseline outright
98
+ * — that's the trust score's job once it falls below `step_up_threshold`.
99
+ */
100
+ export function aitmScoreDelta(anomalies) {
101
+ let delta = 0;
102
+ for (const a of anomalies) {
103
+ if (a.confidence === 'high')
104
+ delta -= 25;
105
+ else if (a.confidence === 'medium')
106
+ delta -= 12;
107
+ else
108
+ delta -= 5;
109
+ }
110
+ return Math.max(delta, -60);
111
+ }
112
+ function safeHost(originUrl) {
113
+ try {
114
+ return new URL(originUrl).host.toLowerCase();
115
+ }
116
+ catch {
117
+ return undefined;
118
+ }
119
+ }
120
+ //# sourceMappingURL=aitm-heuristics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aitm-heuristics.js","sourceRoot":"../src/","sources":["server/aitm-heuristics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkDH,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAEtC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,QAA2B,EAC3B,OAA0B,EAC1B,OAAwB,EAAE;IAE1B,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,IAAI,0BAA0B,CAAC;IACzE,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,IAAI,IAAI,CAAC;IAE1D,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAClD,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,iBAAiB;gBACvB,UAAU,EAAE,MAAM;gBAClB,OAAO,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,SAAS,KAAK,OAAO,CAAC,SAAS,EAAE,CAAC;QAC7C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,UAAU;YAChB,UAAU,EAAE,MAAM;YAClB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,UAAU;YAChB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC;QAC3C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,cAAc;YACpB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,OAAO,CAAC,QAAQ,EAAE;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,aAAa;YACnB,UAAU,EAAE,KAAK;YACjB,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,UAAU,EAAE;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;IACnE,MAAM,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAChE,MAAM,OAAO,GACX,QAAQ,CAAC,WAAW,KAAK,OAAO,CAAC,YAAY;QAC7C,QAAQ,CAAC,YAAY,KAAK,OAAO,CAAC,WAAW;QAC7C,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,YAAY,CAAC;IACjD,IAAI,OAAO,EAAE,CAAC;QACZ,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,iBAAiB;YACvB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE;SACvC,CAAC,CAAC;IACL,CAAC;IAED,IACE,OAAO,QAAQ,CAAC,KAAK,KAAK,QAAQ;QAClC,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ;QACjC,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,aAAa,EAC/C,CAAC;QACD,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,eAAe;YACrB,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE;gBACP,UAAU,EAAE,QAAQ,CAAC,KAAK;gBAC1B,SAAS,EAAE,OAAO,CAAC,KAAK;gBACxB,OAAO,EAAE,OAAO,CAAC,KAAK,GAAG,QAAQ,CAAC,KAAK;aACxC;SACF,CAAC,CAAC;IACL,CAAC;IAED,IAAI,cAAc,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,iBAAiB;YACvB,UAAU,EAAE,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,SAAwB;IACrD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM;YAAE,KAAK,IAAI,EAAE,CAAC;aACpC,IAAI,CAAC,CAAC,UAAU,KAAK,QAAQ;YAAE,KAAK,IAAI,EAAE,CAAC;;YAC3C,KAAK,IAAI,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,QAAQ,CAAC,SAAiB;IACjC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Server-side device bind using an IdP-issued ID token.
3
+ *
4
+ * The device keypair is generated in the browser and is non-extractable, so
5
+ * the public key must originate there. The *identity*, however, should not:
6
+ * a browser holds only a publishable key and can assert any user id. Relaying
7
+ * the bind through your own backend — which holds a secret API key and already
8
+ * knows who is signed in — is what makes the resulting identity trustworthy.
9
+ *
10
+ * With an ID token attached, TokenForge verifies it against the workforce
11
+ * app's JWKS and records a `verified` identity. Without one, the bind still
12
+ * succeeds but is graded `attested` rather than `verified`.
13
+ */
14
+ export interface BindWithIdTokenInput {
15
+ /** TokenForge API base, e.g. https://tokenforge-api.opensyber.cloud */
16
+ apiBase: string;
17
+ /** Secret `tf_` API key. Must never reach the browser. */
18
+ apiKey: string;
19
+ /** JWK exported from the browser's non-extractable device keypair. */
20
+ publicKey: string;
21
+ /** Your application's session identifier for this user. */
22
+ sessionId: string;
23
+ /** IdP-issued OIDC ID token. Omit for an `attested` bind. */
24
+ idToken?: string;
25
+ /** Workforce app the token was issued for. Required with `idToken`. */
26
+ workforceAppId?: string;
27
+ /** Your authenticated user id. Used when no ID token is supplied. */
28
+ userId?: string;
29
+ /** Optional device fingerprint from the browser SDK. */
30
+ fingerprint?: string;
31
+ /** Injected for tests. */
32
+ fetchImpl?: typeof fetch;
33
+ }
34
+ export type BindWithIdTokenResult = {
35
+ ok: true;
36
+ deviceId: string;
37
+ sessionId: string;
38
+ expiresAt: string;
39
+ trustScore: number;
40
+ } | {
41
+ ok: false;
42
+ status: number;
43
+ error: string;
44
+ };
45
+ /**
46
+ * Bind a device from your backend, attaching a verified identity when possible.
47
+ *
48
+ * @param input Connection details plus the browser-generated public key.
49
+ * @returns The created device session, or a structured failure. Never throws
50
+ * on an API error — network faults still reject.
51
+ * @throws {Error} When `idToken` is supplied without `workforceAppId`, which is
52
+ * always a caller mistake rather than a runtime condition.
53
+ */
54
+ export declare function bindDeviceWithIdToken(input: BindWithIdTokenInput): Promise<BindWithIdTokenResult>;
55
+ //# sourceMappingURL=bind-with-id-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-with-id-token.d.ts","sourceRoot":"../src/","sources":["server/bind-with-id-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACxF;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC,CAoDhC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Server-side device bind using an IdP-issued ID token.
3
+ *
4
+ * The device keypair is generated in the browser and is non-extractable, so
5
+ * the public key must originate there. The *identity*, however, should not:
6
+ * a browser holds only a publishable key and can assert any user id. Relaying
7
+ * the bind through your own backend — which holds a secret API key and already
8
+ * knows who is signed in — is what makes the resulting identity trustworthy.
9
+ *
10
+ * With an ID token attached, TokenForge verifies it against the workforce
11
+ * app's JWKS and records a `verified` identity. Without one, the bind still
12
+ * succeeds but is graded `attested` rather than `verified`.
13
+ */
14
+ /**
15
+ * Bind a device from your backend, attaching a verified identity when possible.
16
+ *
17
+ * @param input Connection details plus the browser-generated public key.
18
+ * @returns The created device session, or a structured failure. Never throws
19
+ * on an API error — network faults still reject.
20
+ * @throws {Error} When `idToken` is supplied without `workforceAppId`, which is
21
+ * always a caller mistake rather than a runtime condition.
22
+ */
23
+ export async function bindDeviceWithIdToken(input) {
24
+ if (input.idToken && !input.workforceAppId) {
25
+ throw new Error('workforceAppId is required when idToken is supplied');
26
+ }
27
+ if (!input.idToken && !input.userId) {
28
+ throw new Error('either idToken or userId must be supplied');
29
+ }
30
+ const doFetch = input.fetchImpl ?? fetch;
31
+ const body = {
32
+ publicKey: input.publicKey,
33
+ sessionId: input.sessionId,
34
+ };
35
+ if (input.fingerprint)
36
+ body.fingerprint = input.fingerprint;
37
+ if (input.idToken) {
38
+ body.idToken = input.idToken;
39
+ body.workforceAppId = input.workforceAppId;
40
+ // The verified subject from the token supersedes this, but the field is
41
+ // required by the API contract.
42
+ body.userId = input.userId ?? 'pending-idp-resolution';
43
+ }
44
+ else {
45
+ body.userId = input.userId;
46
+ // A server-relayed bind is an explicit identity assertion, not the SDK's
47
+ // cookie fallback.
48
+ body.userIdSource = 'declared';
49
+ }
50
+ const res = await doFetch(`${input.apiBase.replace(/\/+$/, '')}/v1/bind`, {
51
+ method: 'POST',
52
+ headers: {
53
+ Authorization: `Bearer ${input.apiKey}`,
54
+ 'Content-Type': 'application/json',
55
+ },
56
+ body: JSON.stringify(body),
57
+ });
58
+ const payload = (await res.json().catch(() => ({})));
59
+ if (!res.ok || !payload.data) {
60
+ return { ok: false, status: res.status, error: payload.error ?? 'bind_failed' };
61
+ }
62
+ return {
63
+ ok: true,
64
+ deviceId: payload.data.deviceId,
65
+ sessionId: payload.data.sessionId,
66
+ expiresAt: payload.data.expiresAt,
67
+ trustScore: payload.data.trustScore,
68
+ };
69
+ }
70
+ //# sourceMappingURL=bind-with-id-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-with-id-token.js","sourceRoot":"../src/","sources":["server/bind-with-id-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA2BH;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,KAA2B;IAE3B,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC;IACzC,MAAM,IAAI,GAA4B;QACpC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC;IACF,IAAI,KAAK,CAAC,WAAW;QAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IAC5D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;QAC3C,wEAAwE;QACxE,gCAAgC;QAChC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,wBAAwB,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,yEAAyE;QACzE,mBAAmB;QACnB,IAAI,CAAC,YAAY,GAAG,UAAU,CAAC;IACjC,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,EAAE;QACxE,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,KAAK,CAAC,MAAM,EAAE;YACvC,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAGlD,CAAC;IAEF,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAC7B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,aAAa,EAAE,CAAC;IAClF,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ;QAC/B,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS;QACjC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS;QACjC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU;KACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Short-lived bound-cookie issuer for DBSC.
3
+ *
4
+ * Cookie value = base64url(random 32 bytes). We never store the value
5
+ * itself — only its SHA-256 hash. On every refresh the cookie rotates,
6
+ * so a leaked cookie expires within `maxAgeSeconds` and cannot be
7
+ * extended without a fresh JWS-signed refresh.
8
+ *
9
+ * The TLS-exporter binding hook is exposed but optional: workerd does
10
+ * not yet expose RFC 9266 channel-binding material, so we fall through
11
+ * gracefully and emit a `Sec-TF-Channel-Bound: 0` warning header at the
12
+ * route layer when the runtime cannot bind.
13
+ */
14
+ export interface BoundCookie {
15
+ /** Opaque value sent to the client. */
16
+ value: string;
17
+ /** SHA-256 hash of value — store this, never the value. */
18
+ hash: string;
19
+ /** Max-Age in seconds. */
20
+ maxAgeSeconds: number;
21
+ /** Issued-at ISO timestamp. */
22
+ issuedAt: string;
23
+ /** Expires-at ISO timestamp. */
24
+ expiresAt: string;
25
+ }
26
+ export interface IssueBoundCookieOptions {
27
+ maxAgeSeconds?: number;
28
+ /**
29
+ * Optional RFC 9266 TLS exporter material. If present, the cookie
30
+ * hash mixes the exporter so a session can't be replayed across
31
+ * a different TLS connection.
32
+ */
33
+ tlsExporterHex?: string;
34
+ }
35
+ export declare function issueBoundCookie(opts?: IssueBoundCookieOptions): Promise<BoundCookie>;
36
+ export declare function hashBoundCookie(value: string, tlsExporterHex?: string): Promise<string>;
37
+ export declare function setBoundCookieHeader(cookie: BoundCookie): string;
38
+ export declare const BOUND_COOKIE_NAME = "__Secure-tf-bound";
39
+ //# sourceMappingURL=bound-cookie.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bound-cookie.d.ts","sourceRoot":"../src/","sources":["server/bound-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,gBAAgB,CACpC,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE7F;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAShE;AAED,eAAO,MAAM,iBAAiB,sBAAc,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Short-lived bound-cookie issuer for DBSC.
3
+ *
4
+ * Cookie value = base64url(random 32 bytes). We never store the value
5
+ * itself — only its SHA-256 hash. On every refresh the cookie rotates,
6
+ * so a leaked cookie expires within `maxAgeSeconds` and cannot be
7
+ * extended without a fresh JWS-signed refresh.
8
+ *
9
+ * The TLS-exporter binding hook is exposed but optional: workerd does
10
+ * not yet expose RFC 9266 channel-binding material, so we fall through
11
+ * gracefully and emit a `Sec-TF-Channel-Bound: 0` warning header at the
12
+ * route layer when the runtime cannot bind.
13
+ */
14
+ const COOKIE_BYTES = 32;
15
+ const DEFAULT_MAX_AGE_SECONDS = 300;
16
+ const COOKIE_NAME = '__Secure-tf-bound';
17
+ export async function issueBoundCookie(opts = {}) {
18
+ const bytes = new Uint8Array(COOKIE_BYTES);
19
+ crypto.getRandomValues(bytes);
20
+ const value = bytesToBase64Url(bytes);
21
+ const hash = await hashWithExporter(value, opts.tlsExporterHex);
22
+ const maxAge = opts.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
23
+ const issued = new Date();
24
+ const expires = new Date(issued.getTime() + maxAge * 1000);
25
+ return {
26
+ value,
27
+ hash,
28
+ maxAgeSeconds: maxAge,
29
+ issuedAt: issued.toISOString(),
30
+ expiresAt: expires.toISOString(),
31
+ };
32
+ }
33
+ export async function hashBoundCookie(value, tlsExporterHex) {
34
+ return hashWithExporter(value, tlsExporterHex);
35
+ }
36
+ export function setBoundCookieHeader(cookie) {
37
+ return [
38
+ `${COOKIE_NAME}=${cookie.value}`,
39
+ `Max-Age=${cookie.maxAgeSeconds}`,
40
+ 'Path=/',
41
+ 'HttpOnly',
42
+ 'Secure',
43
+ 'SameSite=Strict',
44
+ ].join('; ');
45
+ }
46
+ export const BOUND_COOKIE_NAME = COOKIE_NAME;
47
+ async function hashWithExporter(value, exporterHex) {
48
+ const enc = new TextEncoder();
49
+ const input = exporterHex ? `${value}|${exporterHex}` : value;
50
+ const buf = await crypto.subtle.digest('SHA-256', enc.encode(input));
51
+ return bytesToBase64Url(new Uint8Array(buf));
52
+ }
53
+ function bytesToBase64Url(bytes) {
54
+ let bin = '';
55
+ for (let i = 0; i < bytes.length; i++)
56
+ bin += String.fromCharCode(bytes[i]);
57
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
58
+ }
59
+ //# sourceMappingURL=bound-cookie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bound-cookie.js","sourceRoot":"../src/","sources":["server/bound-cookie.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAyBxC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgC,EAAE;IAElC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,IAAI,uBAAuB,CAAC;IAC7D,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO;QACL,KAAK;QACL,IAAI;QACJ,aAAa,EAAE,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE;QAC9B,SAAS,EAAE,OAAO,CAAC,WAAW,EAAE;KACjC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAa,EAAE,cAAuB;IAC1E,OAAO,gBAAgB,CAAC,KAAK,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAmB;IACtD,OAAO;QACL,GAAG,WAAW,IAAI,MAAM,CAAC,KAAK,EAAE;QAChC,WAAW,MAAM,CAAC,aAAa,EAAE;QACjC,QAAQ;QACR,UAAU;QACV,QAAQ;QACR,iBAAiB;KAClB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAE7C,KAAK,UAAU,gBAAgB,CAAC,KAAa,EAAE,WAA+B;IAC5E,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,OAAO,gBAAgB,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB;IACzC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal CBOR decoder — supports just enough of RFC 8949 to parse
3
+ * WebAuthn `attestationObject` and embedded COSE_Key structures.
4
+ *
5
+ * Supported major types:
6
+ * 0 (uint), 1 (negative int), 2 (byte string), 3 (text string),
7
+ * 4 (array), 5 (map).
8
+ * Unsupported: tags, floats, indefinite-length, simple values other than
9
+ * true/false/null.
10
+ *
11
+ * This is intentionally small (no npm dep) because the only inputs we ever
12
+ * decode are WebAuthn attestation blobs with a known structure. If you feed
13
+ * arbitrary CBOR you may hit `unsupported` errors — that is by design.
14
+ */
15
+ export type CborValue = number | bigint | string | Uint8Array | boolean | null | CborValue[] | {
16
+ [key: string]: CborValue;
17
+ } | Map<CborValue, CborValue>;
18
+ /** Decode a CBOR-encoded buffer. Throws on unsupported encodings. */
19
+ export declare function decodeCbor(input: ArrayBuffer | Uint8Array): CborValue;
20
+ //# sourceMappingURL=cbor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cbor.d.ts","sourceRoot":"../src/","sources":["server/cbor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,MAAM,GACN,UAAU,GACV,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC5B,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;AAI9B,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,CAKrE"}