@fuzdev/fuz_app 0.140.0 → 0.142.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 (311) hide show
  1. package/dist/actions/CLAUDE.md +72 -22
  2. package/dist/actions/action_rpc.d.ts +5 -1
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +9 -1
  5. package/dist/actions/connection_closer.d.ts +44 -4
  6. package/dist/actions/connection_closer.d.ts.map +1 -1
  7. package/dist/actions/connection_closer.js +33 -3
  8. package/dist/actions/perform_action.d.ts +10 -0
  9. package/dist/actions/perform_action.d.ts.map +1 -1
  10. package/dist/actions/perform_action.js +23 -9
  11. package/dist/actions/register_action_ws.d.ts +42 -6
  12. package/dist/actions/register_action_ws.d.ts.map +1 -1
  13. package/dist/actions/register_action_ws.js +87 -19
  14. package/dist/actions/register_ws_endpoint.d.ts +15 -6
  15. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  16. package/dist/actions/register_ws_endpoint.js +10 -4
  17. package/dist/actions/socket.svelte.d.ts +19 -4
  18. package/dist/actions/socket.svelte.d.ts.map +1 -1
  19. package/dist/actions/socket.svelte.js +31 -4
  20. package/dist/actions/transports.d.ts +30 -1
  21. package/dist/actions/transports.d.ts.map +1 -1
  22. package/dist/actions/transports.js +30 -1
  23. package/dist/actions/transports_ws_auth_guard.d.ts +13 -6
  24. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  25. package/dist/actions/transports_ws_auth_guard.js +24 -10
  26. package/dist/actions/transports_ws_backend.d.ts +51 -5
  27. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  28. package/dist/actions/transports_ws_backend.js +62 -9
  29. package/dist/actions/ws_endpoint_spec.d.ts +11 -10
  30. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  31. package/dist/auth/CLAUDE.md +44 -22
  32. package/dist/auth/account_actions.d.ts.map +1 -1
  33. package/dist/auth/account_actions.js +12 -1
  34. package/dist/auth/account_route_schema.d.ts +1 -0
  35. package/dist/auth/account_route_schema.d.ts.map +1 -1
  36. package/dist/auth/account_route_schema.js +12 -0
  37. package/dist/auth/account_routes.d.ts +7 -2
  38. package/dist/auth/account_routes.d.ts.map +1 -1
  39. package/dist/auth/account_routes.js +240 -187
  40. package/dist/auth/admin_action_specs.d.ts +6 -1
  41. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  42. package/dist/auth/admin_action_specs.js +6 -1
  43. package/dist/auth/admin_actions.d.ts.map +1 -1
  44. package/dist/auth/admin_actions.js +14 -2
  45. package/dist/auth/api_token.d.ts +20 -0
  46. package/dist/auth/api_token.d.ts.map +1 -1
  47. package/dist/auth/api_token.js +21 -1
  48. package/dist/auth/api_token_queries.d.ts +22 -4
  49. package/dist/auth/api_token_queries.d.ts.map +1 -1
  50. package/dist/auth/api_token_queries.js +11 -6
  51. package/dist/auth/audit_log_routes.d.ts +10 -0
  52. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  53. package/dist/auth/audit_log_routes.js +20 -0
  54. package/dist/auth/audit_log_schema.d.ts +30 -5
  55. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  56. package/dist/auth/audit_log_schema.js +30 -0
  57. package/dist/auth/bearer_auth.d.ts +4 -2
  58. package/dist/auth/bearer_auth.d.ts.map +1 -1
  59. package/dist/auth/bearer_auth.js +19 -8
  60. package/dist/auth/bootstrap_route_schema.d.ts +1 -0
  61. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -1
  62. package/dist/auth/bootstrap_route_schema.js +1 -0
  63. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  64. package/dist/auth/bootstrap_routes.js +85 -76
  65. package/dist/auth/cell_action_specs.d.ts +7 -3
  66. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  67. package/dist/auth/cell_action_specs.js +9 -5
  68. package/dist/auth/cell_actions.d.ts +3 -3
  69. package/dist/auth/cell_actions.d.ts.map +1 -1
  70. package/dist/auth/cell_actions.js +67 -38
  71. package/dist/auth/cell_authorize.d.ts +69 -8
  72. package/dist/auth/cell_authorize.d.ts.map +1 -1
  73. package/dist/auth/cell_authorize.js +89 -9
  74. package/dist/auth/cell_field_action_specs.d.ts +5 -4
  75. package/dist/auth/cell_field_action_specs.d.ts.map +1 -1
  76. package/dist/auth/cell_field_action_specs.js +7 -7
  77. package/dist/auth/cell_field_actions.d.ts +7 -5
  78. package/dist/auth/cell_field_actions.d.ts.map +1 -1
  79. package/dist/auth/cell_field_actions.js +24 -22
  80. package/dist/auth/cell_grant_action_specs.d.ts +6 -0
  81. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  82. package/dist/auth/cell_grant_action_specs.js +6 -0
  83. package/dist/auth/cell_grant_actions.d.ts +3 -1
  84. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  85. package/dist/auth/cell_grant_actions.js +16 -2
  86. package/dist/auth/cell_item_action_specs.d.ts +5 -4
  87. package/dist/auth/cell_item_action_specs.d.ts.map +1 -1
  88. package/dist/auth/cell_item_action_specs.js +7 -7
  89. package/dist/auth/cell_item_actions.d.ts +6 -5
  90. package/dist/auth/cell_item_actions.d.ts.map +1 -1
  91. package/dist/auth/cell_item_actions.js +23 -22
  92. package/dist/auth/cell_relation_list_limit.d.ts +20 -0
  93. package/dist/auth/cell_relation_list_limit.d.ts.map +1 -0
  94. package/dist/auth/cell_relation_list_limit.js +27 -0
  95. package/dist/auth/cell_relation_visibility.d.ts +4 -3
  96. package/dist/auth/cell_relation_visibility.d.ts.map +1 -1
  97. package/dist/auth/cell_relation_visibility.js +6 -5
  98. package/dist/auth/cell_steward.d.ts +45 -0
  99. package/dist/auth/cell_steward.d.ts.map +1 -0
  100. package/dist/auth/cell_steward.js +53 -0
  101. package/dist/auth/cleanup.d.ts +9 -9
  102. package/dist/auth/cleanup.js +9 -9
  103. package/dist/auth/credential_deadline.d.ts +95 -0
  104. package/dist/auth/credential_deadline.d.ts.map +1 -0
  105. package/dist/auth/credential_deadline.js +139 -0
  106. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  107. package/dist/auth/daemon_token_middleware.js +4 -1
  108. package/dist/auth/middleware.d.ts +90 -12
  109. package/dist/auth/middleware.d.ts.map +1 -1
  110. package/dist/auth/middleware.js +115 -16
  111. package/dist/auth/request_context.d.ts +8 -2
  112. package/dist/auth/request_context.d.ts.map +1 -1
  113. package/dist/auth/request_context.js +20 -4
  114. package/dist/auth/resolved_auth.d.ts +13 -2
  115. package/dist/auth/resolved_auth.d.ts.map +1 -1
  116. package/dist/auth/resolved_auth.js +5 -4
  117. package/dist/auth/role_grant_offer_action_specs.d.ts +5 -0
  118. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  119. package/dist/auth/role_grant_offer_action_specs.js +6 -1
  120. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  121. package/dist/auth/role_grant_offer_actions.js +23 -16
  122. package/dist/auth/role_grant_offer_queries.d.ts +8 -6
  123. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  124. package/dist/auth/role_grant_offer_queries.js +16 -8
  125. package/dist/auth/session_cookie.d.ts +4 -0
  126. package/dist/auth/session_cookie.d.ts.map +1 -1
  127. package/dist/auth/session_cookie.js +3 -2
  128. package/dist/auth/session_middleware.d.ts +1 -1
  129. package/dist/auth/session_middleware.d.ts.map +1 -1
  130. package/dist/auth/session_middleware.js +4 -1
  131. package/dist/auth/session_queries.d.ts +19 -2
  132. package/dist/auth/session_queries.d.ts.map +1 -1
  133. package/dist/auth/session_queries.js +8 -2
  134. package/dist/auth/signup_route_schema.d.ts.map +1 -1
  135. package/dist/auth/signup_route_schema.js +1 -0
  136. package/dist/auth/signup_routes.d.ts +4 -3
  137. package/dist/auth/signup_routes.d.ts.map +1 -1
  138. package/dist/auth/signup_routes.js +157 -134
  139. package/dist/auth/token_scope.d.ts +7 -1
  140. package/dist/auth/token_scope.d.ts.map +1 -1
  141. package/dist/auth/token_scope.js +8 -2
  142. package/dist/db/CLAUDE.md +32 -19
  143. package/dist/db/cell_ddl.d.ts +1 -2
  144. package/dist/db/cell_ddl.d.ts.map +1 -1
  145. package/dist/db/cell_ddl.js +1 -2
  146. package/dist/db/cell_field_queries.d.ts +20 -18
  147. package/dist/db/cell_field_queries.d.ts.map +1 -1
  148. package/dist/db/cell_field_queries.js +28 -22
  149. package/dist/db/cell_item_queries.d.ts +20 -14
  150. package/dist/db/cell_item_queries.d.ts.map +1 -1
  151. package/dist/db/cell_item_queries.js +28 -18
  152. package/dist/db/cell_moderation.d.ts +67 -0
  153. package/dist/db/cell_moderation.d.ts.map +1 -0
  154. package/dist/db/cell_moderation.js +66 -0
  155. package/dist/db/cell_queries.d.ts +44 -14
  156. package/dist/db/cell_queries.d.ts.map +1 -1
  157. package/dist/db/cell_queries.js +68 -45
  158. package/dist/db/cell_view_sql.d.ts +102 -0
  159. package/dist/db/cell_view_sql.d.ts.map +1 -0
  160. package/dist/db/cell_view_sql.js +112 -0
  161. package/dist/db/db_pg.d.ts +1 -1
  162. package/dist/db/db_pg.js +1 -1
  163. package/dist/hono_context.d.ts +23 -0
  164. package/dist/hono_context.d.ts.map +1 -1
  165. package/dist/hono_context.js +19 -0
  166. package/dist/http/CLAUDE.md +41 -15
  167. package/dist/http/auth_shape.d.ts +5 -2
  168. package/dist/http/auth_shape.d.ts.map +1 -1
  169. package/dist/http/auth_shape.js +5 -2
  170. package/dist/http/ip_canonical.d.ts +30 -3
  171. package/dist/http/ip_canonical.d.ts.map +1 -1
  172. package/dist/http/ip_canonical.js +50 -3
  173. package/dist/http/middleware_spec.d.ts +16 -0
  174. package/dist/http/middleware_spec.d.ts.map +1 -1
  175. package/dist/http/origin.d.ts +113 -18
  176. package/dist/http/origin.d.ts.map +1 -1
  177. package/dist/http/origin.js +247 -127
  178. package/dist/http/route_spec.d.ts +21 -0
  179. package/dist/http/route_spec.d.ts.map +1 -1
  180. package/dist/http/schema_helpers.d.ts +22 -0
  181. package/dist/http/schema_helpers.d.ts.map +1 -1
  182. package/dist/http/schema_helpers.js +24 -0
  183. package/dist/http/surface.d.ts +6 -1
  184. package/dist/http/surface.d.ts.map +1 -1
  185. package/dist/http/surface.js +12 -5
  186. package/dist/primitive_schemas.d.ts +10 -1
  187. package/dist/primitive_schemas.d.ts.map +1 -1
  188. package/dist/primitive_schemas.js +11 -2
  189. package/dist/rate_limiter.d.ts +179 -12
  190. package/dist/rate_limiter.d.ts.map +1 -1
  191. package/dist/rate_limiter.js +352 -42
  192. package/dist/realtime/sse_auth_guard.d.ts +22 -6
  193. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  194. package/dist/realtime/sse_auth_guard.js +31 -12
  195. package/dist/realtime/subscriber_registry.d.ts +10 -1
  196. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  197. package/dist/realtime/subscriber_registry.js +10 -1
  198. package/dist/server/app_server.d.ts +20 -7
  199. package/dist/server/app_server.d.ts.map +1 -1
  200. package/dist/server/app_server.js +8 -3
  201. package/dist/server/env.d.ts +2 -1
  202. package/dist/server/env.d.ts.map +1 -1
  203. package/dist/server/env.js +7 -17
  204. package/dist/server/serve_fact_route.d.ts +8 -4
  205. package/dist/server/serve_fact_route.d.ts.map +1 -1
  206. package/dist/server/serve_fact_route.js +16 -6
  207. package/dist/testing/CLAUDE.md +164 -25
  208. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  209. package/dist/testing/adversarial_headers.js +13 -6
  210. package/dist/testing/connection_closer_helpers.d.ts +6 -1
  211. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  212. package/dist/testing/connection_closer_helpers.js +4 -0
  213. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  214. package/dist/testing/cross_backend/account_lifecycle.js +37 -2
  215. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  216. package/dist/testing/cross_backend/body_size.js +14 -65
  217. package/dist/testing/cross_backend/capabilities.d.ts +26 -2
  218. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  219. package/dist/testing/cross_backend/capabilities.js +9 -3
  220. package/dist/testing/cross_backend/cell_gated_create.d.ts +38 -0
  221. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  222. package/dist/testing/cross_backend/cell_gated_create.js +401 -5
  223. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  224. package/dist/testing/cross_backend/cell_relations.js +145 -2
  225. package/dist/testing/cross_backend/conformance_case.d.ts +11 -3
  226. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  227. package/dist/testing/cross_backend/conformance_case.js +6 -0
  228. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  229. package/dist/testing/cross_backend/conformance_table.js +20 -19
  230. package/dist/testing/cross_backend/credential_deadline.d.ts +25 -0
  231. package/dist/testing/cross_backend/credential_deadline.d.ts.map +1 -0
  232. package/dist/testing/cross_backend/credential_deadline.js +185 -0
  233. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  234. package/dist/testing/cross_backend/default_backend_configs.js +13 -2
  235. package/dist/testing/cross_backend/expected_schema.json +0 -1
  236. package/dist/testing/cross_backend/full_spine_mount.d.ts +3 -0
  237. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  238. package/dist/testing/cross_backend/full_spine_mount.js +7 -1
  239. package/dist/testing/cross_backend/login_security.d.ts +16 -0
  240. package/dist/testing/cross_backend/login_security.d.ts.map +1 -1
  241. package/dist/testing/cross_backend/login_security.js +154 -0
  242. package/dist/testing/cross_backend/narrowed_api_token.d.ts +23 -0
  243. package/dist/testing/cross_backend/narrowed_api_token.d.ts.map +1 -0
  244. package/dist/testing/cross_backend/narrowed_api_token.js +40 -0
  245. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  246. package/dist/testing/cross_backend/origin.js +6 -26
  247. package/dist/testing/cross_backend/origin_conformance.d.ts +152 -0
  248. package/dist/testing/cross_backend/origin_conformance.d.ts.map +1 -0
  249. package/dist/testing/cross_backend/origin_conformance.js +136 -0
  250. package/dist/testing/cross_backend/origin_conformance.json +1679 -0
  251. package/dist/testing/cross_backend/origin_scope.d.ts +16 -0
  252. package/dist/testing/cross_backend/origin_scope.d.ts.map +1 -0
  253. package/dist/testing/cross_backend/origin_scope.js +140 -0
  254. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  255. package/dist/testing/cross_backend/query_shape.js +24 -2
  256. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts +35 -0
  257. package/dist/testing/cross_backend/role_grant_cross_helpers.d.ts.map +1 -0
  258. package/dist/testing/cross_backend/role_grant_cross_helpers.js +64 -0
  259. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +8 -0
  260. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  261. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +44 -33
  262. package/dist/testing/cross_backend/role_grant_participation.d.ts.map +1 -1
  263. package/dist/testing/cross_backend/role_grant_participation.js +64 -0
  264. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  265. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -4
  266. package/dist/testing/cross_backend/spine_surface_constants.d.ts +20 -1
  267. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  268. package/dist/testing/cross_backend/spine_surface_constants.js +23 -1
  269. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  270. package/dist/testing/cross_backend/testing_backdoor.js +20 -7
  271. package/dist/testing/cross_backend/testing_reset_actions.d.ts +180 -5
  272. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  273. package/dist/testing/cross_backend/testing_reset_actions.js +154 -5
  274. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  275. package/dist/testing/cross_backend/token_scope_surface.js +25 -21
  276. package/dist/testing/cross_backend/transport_composition.d.ts +60 -0
  277. package/dist/testing/cross_backend/transport_composition.d.ts.map +1 -0
  278. package/dist/testing/cross_backend/transport_composition.js +364 -0
  279. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  280. package/dist/testing/cross_backend/ts_spine_backend_config.js +21 -6
  281. package/dist/testing/cross_backend/ws_action_rate_limit.d.ts.map +1 -1
  282. package/dist/testing/cross_backend/ws_action_rate_limit.js +21 -1
  283. package/dist/testing/cross_backend/ws_role_gated.d.ts +38 -0
  284. package/dist/testing/cross_backend/ws_role_gated.d.ts.map +1 -0
  285. package/dist/testing/cross_backend/ws_role_gated.js +142 -0
  286. package/dist/testing/entities.d.ts +23 -0
  287. package/dist/testing/entities.d.ts.map +1 -1
  288. package/dist/testing/entities.js +37 -0
  289. package/dist/testing/integration.d.ts.map +1 -1
  290. package/dist/testing/integration.js +3 -1
  291. package/dist/testing/middleware.d.ts +10 -0
  292. package/dist/testing/middleware.d.ts.map +1 -1
  293. package/dist/testing/middleware.js +31 -7
  294. package/dist/testing/schema_introspect.js +1 -1
  295. package/dist/testing/schema_parity.d.ts +4 -3
  296. package/dist/testing/schema_parity.d.ts.map +1 -1
  297. package/dist/testing/schema_parity.js +4 -3
  298. package/dist/testing/schema_ready_fixture.d.ts +2 -4
  299. package/dist/testing/schema_ready_fixture.d.ts.map +1 -1
  300. package/dist/testing/schema_ready_fixture.js +2 -4
  301. package/dist/testing/stubs.d.ts +16 -3
  302. package/dist/testing/stubs.d.ts.map +1 -1
  303. package/dist/testing/stubs.js +20 -7
  304. package/dist/testing/surface_invariants.js +2 -2
  305. package/dist/ui/auth_state.svelte.d.ts +10 -0
  306. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  307. package/dist/ui/auth_state.svelte.js +22 -4
  308. package/package.json +1 -1
  309. package/dist/db/cell_history_ddl.d.ts +0 -38
  310. package/dist/db/cell_history_ddl.d.ts.map +0 -1
  311. package/dist/db/cell_history_ddl.js +0 -61
@@ -27,9 +27,15 @@
27
27
  * canonicalization because the dotted form is the only form the
28
28
  * strip can recognize symmetrically.
29
29
  *
30
- * Mirrors `zzz_server::proxy::normalize_ip` (landed 2026-05-16) which
31
- * uses the same parse-then-canonicalize-then-strip ordering for the
32
- * same rate-limit-key-poisoning surface.
30
+ * Mirrors the Rust spine's `fuz_http::normalize_ip`, which uses the same
31
+ * parse-then-canonicalize-then-strip ordering for the same
32
+ * rate-limit-key-poisoning surface.
33
+ *
34
+ * `rate_limit_ip_key` is the bucket key an IP-keyed rate limiter uses: an
35
+ * IPv6 address collapses to its `/64`, the smallest block a host is commonly
36
+ * assigned, so rotating through one's own subnet buys no fresh budget. Only
37
+ * the limiter call sites apply it — the client IP stays the full address
38
+ * everywhere else (audit rows, token `last_used_ip`, `REMOTE_ADDR`).
33
39
  *
34
40
  * @module
35
41
  */
@@ -97,4 +103,25 @@ export declare const canonicalize_ip: (ip: string) => string;
97
103
  * @throws {RangeError} when `bits` is negative or exceeds 128 bits
98
104
  */
99
105
  export declare const ipv6_bigint_to_canonical: (bits: bigint) => string;
106
+ /**
107
+ * The bucket key an IP-keyed rate limiter uses for client IP `ip`.
108
+ *
109
+ * An IPv6 address keys its `/64`: the low 64 bits masked off, rendered in
110
+ * RFC 5952 canonical form with a `/64` suffix (`2001:db8:1:2::/64`; the
111
+ * all-zero prefix is `::/64`). A zone suffix (`%eth0`) is dropped first. An
112
+ * IPv4 address — an IPv4-mapped IPv6 one included — keys its canonical form,
113
+ * with no suffix. Anything that doesn't parse (`'unknown'` among them) is
114
+ * returned unchanged. Twin of the Rust spine's `fuz_http::rate_limit_ip_key`.
115
+ *
116
+ * For limiter keys only: audit rows and every other record of the client
117
+ * keep the full address.
118
+ *
119
+ * @example
120
+ * rate_limit_ip_key('2001:db8:1:2::1') // → '2001:db8:1:2::/64'
121
+ * rate_limit_ip_key('2001:db8:1:2:8000::') // → '2001:db8:1:2::/64'
122
+ * rate_limit_ip_key('fe80::1%eth0') // → 'fe80::/64'
123
+ * rate_limit_ip_key('203.0.113.7') // → '203.0.113.7'
124
+ * rate_limit_ip_key('unknown') // → 'unknown'
125
+ */
126
+ export declare const rate_limit_ip_key: (ip: string) => string;
100
127
  //# sourceMappingURL=ip_canonical.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ip_canonical.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/ip_canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,QAAqB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAmC5C,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAgEvD,CAAC"}
1
+ {"version":3,"file":"ip_canonical.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/ip_canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,QAAqB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAmC5C,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MAgEvD,CAAC;AAKF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB,GAAI,IAAI,MAAM,KAAG,MAc9C,CAAC"}
@@ -27,9 +27,15 @@
27
27
  * canonicalization because the dotted form is the only form the
28
28
  * strip can recognize symmetrically.
29
29
  *
30
- * Mirrors `zzz_server::proxy::normalize_ip` (landed 2026-05-16) which
31
- * uses the same parse-then-canonicalize-then-strip ordering for the
32
- * same rate-limit-key-poisoning surface.
30
+ * Mirrors the Rust spine's `fuz_http::normalize_ip`, which uses the same
31
+ * parse-then-canonicalize-then-strip ordering for the same
32
+ * rate-limit-key-poisoning surface.
33
+ *
34
+ * `rate_limit_ip_key` is the bucket key an IP-keyed rate limiter uses: an
35
+ * IPv6 address collapses to its `/64`, the smallest block a host is commonly
36
+ * assigned, so rotating through one's own subnet buys no fresh budget. Only
37
+ * the limiter call sites apply it — the client IP stays the full address
38
+ * everywhere else (audit rows, token `last_used_ip`, `REMOTE_ADDR`).
33
39
  *
34
40
  * @module
35
41
  */
@@ -193,3 +199,44 @@ export const ipv6_bigint_to_canonical = (bits) => {
193
199
  .join(':');
194
200
  return before + '::' + after;
195
201
  };
202
+ /** The high 64 bits of an IPv6 address — its `/64` network prefix. */
203
+ const IPV6_PREFIX_64_MASK = ((1n << 64n) - 1n) << 64n;
204
+ /**
205
+ * The bucket key an IP-keyed rate limiter uses for client IP `ip`.
206
+ *
207
+ * An IPv6 address keys its `/64`: the low 64 bits masked off, rendered in
208
+ * RFC 5952 canonical form with a `/64` suffix (`2001:db8:1:2::/64`; the
209
+ * all-zero prefix is `::/64`). A zone suffix (`%eth0`) is dropped first. An
210
+ * IPv4 address — an IPv4-mapped IPv6 one included — keys its canonical form,
211
+ * with no suffix. Anything that doesn't parse (`'unknown'` among them) is
212
+ * returned unchanged. Twin of the Rust spine's `fuz_http::rate_limit_ip_key`.
213
+ *
214
+ * For limiter keys only: audit rows and every other record of the client
215
+ * keep the full address.
216
+ *
217
+ * @example
218
+ * rate_limit_ip_key('2001:db8:1:2::1') // → '2001:db8:1:2::/64'
219
+ * rate_limit_ip_key('2001:db8:1:2:8000::') // → '2001:db8:1:2::/64'
220
+ * rate_limit_ip_key('fe80::1%eth0') // → 'fe80::/64'
221
+ * rate_limit_ip_key('203.0.113.7') // → '203.0.113.7'
222
+ * rate_limit_ip_key('unknown') // → 'unknown'
223
+ */
224
+ export const rate_limit_ip_key = (ip) => {
225
+ const zone_at = ip.indexOf('%');
226
+ const canonical = canonicalize_ip(zone_at === -1 ? ip : ip.slice(0, zone_at));
227
+ if (!IP_LITERAL_CHARS.test(canonical))
228
+ return ip;
229
+ const family = distinctRemoteAddr(canonical);
230
+ if (family === 'IPv4')
231
+ return canonical;
232
+ if (family !== 'IPv6')
233
+ return ip;
234
+ let bits;
235
+ try {
236
+ bits = convertIPv6ToBinary(canonical);
237
+ }
238
+ catch {
239
+ return ip;
240
+ }
241
+ return `${ipv6_bigint_to_canonical(bits & IPV6_PREFIX_64_MASK)}/64`;
242
+ };
@@ -8,6 +8,16 @@
8
8
  */
9
9
  import type { MiddlewareHandler } from 'hono';
10
10
  import type { RouteErrorSchemas } from './error_schemas.js';
11
+ /**
12
+ * Paths a middleware layer explicitly does not run on, and why — the named
13
+ * exception to a layer that covers every path by default (the origin check).
14
+ */
15
+ export interface MiddlewareCarveOut {
16
+ /** Hono-style pattern (`/hooks/*`, `/health`), matched like `MiddlewareSpec.path`. */
17
+ readonly path: string;
18
+ /** Why these paths are safe without the layer — required, so a carve-out is an argument, not an omission. */
19
+ readonly reason: string;
20
+ }
11
21
  /** A named middleware layer. */
12
22
  export interface MiddlewareSpec {
13
23
  name: string;
@@ -15,5 +25,11 @@ export interface MiddlewareSpec {
15
25
  handler: MiddlewareHandler;
16
26
  /** Error response schemas this middleware can produce, keyed by HTTP status code. */
17
27
  errors?: RouteErrorSchemas;
28
+ /**
29
+ * Paths under `path` the layer does not run on. The handler enforces them
30
+ * itself (Hono mounts by `path` alone); the field is what keeps the
31
+ * generated surface from claiming the layer's errors on those routes.
32
+ */
33
+ carve_outs?: ReadonlyArray<MiddlewareCarveOut>;
18
34
  }
19
35
  //# sourceMappingURL=middleware_spec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/middleware_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B"}
1
+ {"version":3,"file":"middleware_spec.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/middleware_spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,iBAAiB,CAAC;IAC3B,qFAAqF;IACrF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC;CAC/C"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Request source verification middleware for API protection.
3
3
  *
4
- * Verifies requests are coming from expected origins/referers.
4
+ * Verifies requests carry an `Origin` the server allows, or none.
5
5
  * CSRF protection is `SameSite: strict` on session cookies
6
6
  * (see `auth/session_middleware.ts`) plus the CORS simple-request refusal
7
7
  * (`http/simple_request.ts`) — `SameSite` alone admits same-site pages, which
@@ -10,9 +10,18 @@
10
10
  * from making requests as the user browses the web — and is the only gate on
11
11
  * the WebSocket upgrade, which is never preflighted.
12
12
  *
13
+ * The allowlist grammar is shared with the Rust spine entry for entry:
14
+ * `parse_allowed_origins` accepts and refuses the same entries, naming each
15
+ * refusal with the same `AllowedOriginsErrorKind` string, and an accepted list
16
+ * matches the same origins. The shared conformance table
17
+ * (`testing/cross_backend/origin_conformance.json`) pins both.
18
+ *
13
19
  * @module
14
20
  */
21
+ import type { Result } from '@fuzdev/fuz_util/result.ts';
15
22
  import type { Context, Handler } from 'hono';
23
+ import { z } from 'zod';
24
+ import type { MiddlewareCarveOut } from './middleware_spec.js';
16
25
  /**
17
26
  * True when the request looks like it originated from a browser context — it
18
27
  * carries an `Origin` or `Referer` header. Browsers attach these
@@ -31,29 +40,106 @@ import type { Context, Handler } from 'hono';
31
40
  */
32
41
  export declare const is_browser_context: (c: Context) => boolean;
33
42
  /**
34
- * Parses FUZ_ALLOWED_ORIGINS env var into regex matchers for request source verification.
43
+ * Why one allowlist entry was refused. The wire names both spines emit — the
44
+ * Rust spine's `OriginPatternError::kind` returns the same strings.
45
+ */
46
+ export declare const OriginPatternErrorKind: z.ZodEnum<{
47
+ empty_entry: "empty_entry";
48
+ not_printable_ascii: "not_printable_ascii";
49
+ missing_scheme: "missing_scheme";
50
+ unsupported_scheme: "unsupported_scheme";
51
+ has_path: "has_path";
52
+ has_userinfo: "has_userinfo";
53
+ missing_host: "missing_host";
54
+ invalid_host_char: "invalid_host_char";
55
+ unbracketed_ipv6: "unbracketed_ipv6";
56
+ partial_wildcard: "partial_wildcard";
57
+ non_canonical_ipv4: "non_canonical_ipv4";
58
+ unclosed_ipv6: "unclosed_ipv6";
59
+ invalid_ipv6: "invalid_ipv6";
60
+ invalid_after_ipv6: "invalid_after_ipv6";
61
+ invalid_port: "invalid_port";
62
+ default_port: "default_port";
63
+ }>;
64
+ export type OriginPatternErrorKind = z.infer<typeof OriginPatternErrorKind>;
65
+ /** Why an allowlist value was refused: `no_origins` (a blank value) or an entry's kind. */
66
+ export declare const AllowedOriginsErrorKind: z.ZodEnum<{
67
+ empty_entry: "empty_entry";
68
+ not_printable_ascii: "not_printable_ascii";
69
+ missing_scheme: "missing_scheme";
70
+ unsupported_scheme: "unsupported_scheme";
71
+ has_path: "has_path";
72
+ has_userinfo: "has_userinfo";
73
+ missing_host: "missing_host";
74
+ invalid_host_char: "invalid_host_char";
75
+ unbracketed_ipv6: "unbracketed_ipv6";
76
+ partial_wildcard: "partial_wildcard";
77
+ non_canonical_ipv4: "non_canonical_ipv4";
78
+ unclosed_ipv6: "unclosed_ipv6";
79
+ invalid_ipv6: "invalid_ipv6";
80
+ invalid_after_ipv6: "invalid_after_ipv6";
81
+ invalid_port: "invalid_port";
82
+ default_port: "default_port";
83
+ no_origins: "no_origins";
84
+ }>;
85
+ export type AllowedOriginsErrorKind = z.infer<typeof AllowedOriginsErrorKind>;
86
+ /** The operator-facing reason for each refusal kind. */
87
+ export declare const ALLOWED_ORIGINS_ERROR_MESSAGES: Readonly<Record<AllowedOriginsErrorKind, string>>;
88
+ /** One reason `parse_allowed_origins` refused a value. */
89
+ export interface AllowedOriginsError {
90
+ readonly kind: AllowedOriginsErrorKind;
91
+ /** The refused entry's 1-based position; absent for `no_origins`. */
92
+ readonly index?: number;
93
+ /** The refused entry as given, trimmed; absent for `no_origins`. */
94
+ readonly entry?: string;
95
+ }
96
+ /**
97
+ * Render one refusal for a boot error — the entry in double quotes with every
98
+ * character but visible ASCII and the space escaped as `\uXXXX` (and `"` and
99
+ * `\` backslash-escaped), so an env value can't put a control, bidi, or other
100
+ * non-printing character in a log, nor close the quotes it is shown in.
101
+ *
102
+ * @param error - one of `parse_allowed_origins`'s errors
103
+ * @returns the reason as one line
104
+ */
105
+ export declare const format_allowed_origins_error: (error: AllowedOriginsError) => string;
106
+ /**
107
+ * Parses an allowlist value (conventionally the `FUZ_ALLOWED_ORIGINS` env var)
108
+ * into regex matchers for request source verification.
35
109
  * Origin allowlisting for locally-running services — not the CSRF layer
36
110
  * (that's `SameSite: strict` on session cookies plus `http/simple_request.ts`).
37
111
  *
38
- * Accepts comma-separated patterns with limited wildcards:
39
- * - Exact origins: `https://api.fuz.dev`
40
- * - Wildcard subdomains: `https://*.fuz.dev` (matches exactly one subdomain level)
41
- * - Multiple wildcards: `https://*.staging.*.fuz.dev` (for deep subdomains)
42
- * - Wildcard ports: `http://localhost:*` (matches any port or no port)
43
- * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]`
112
+ * Entries are comma-separated and trimmed of ASCII whitespace. Each is written
113
+ * the way a browser writes an `Origin` — `scheme://host[:port]` — where a whole
114
+ * host label or the port may be `*`:
115
+ * - Exact origins: `https://api.fuz.dev`, `http://localhost:3000`
116
+ * - Wildcard labels: `https://*.fuz.dev` (each `*` is exactly one label, in any
117
+ * position — `api.fuz.dev` matches, `fuz.dev` and `a.b.fuz.dev` don't)
118
+ * - Wildcard ports: `http://localhost:*` (any port or none)
119
+ * - IPv6 addresses: `http://[::1]:3000`, `https://[2001:db8::1]` (exact)
44
120
  * - Combined: `https://*.fuz.dev:*`
45
121
  *
46
- * Examples:
47
- * - `http://localhost:3000,https://prod.fuz.dev`
48
- * - `https://*.api.fuz.dev,http://127.0.0.1:*`
49
- * - `http://[::1]:*,https://*.*.corp.fuz.dev:*`
122
+ * Refused, nothing normalized: anything but printable ASCII (punycode an
123
+ * international hostname), a scheme other than http or https (`null`,
124
+ * `file://`, `chrome-extension://`), a path, query, fragment or trailing
125
+ * slash, userinfo, a character no hostname holds, a partial wildcard, an IPv4
126
+ * address not written as four canonical decimal numbers (`127.1`), an IPv6
127
+ * address not in the form a browser sends, a port outside `1..=65535` or with
128
+ * leading zeros, the scheme's default port, and an empty entry. Every refused
129
+ * entry is reported, not just the first.
50
130
  *
51
- * @throws Error if any individual pattern is invalid (missing protocol, partial wildcards, etc.)
131
+ * @param raw - the comma-separated allowlist value
132
+ * @returns the matchers, or every refusal — `no_origins` when the value is blank
52
133
  */
53
- export declare const parse_allowed_origins: (env_value: string | undefined) => Array<RegExp>;
134
+ export declare const parse_allowed_origins: (raw: string) => Result<{
135
+ origins: Array<RegExp>;
136
+ }, {
137
+ errors: Array<AllowedOriginsError>;
138
+ }>;
54
139
  /**
55
- * Tests if a request source (origin or referer) matches any of the allowed patterns.
56
- * Pattern matching is case-insensitive for domains (as per web standards).
140
+ * Tests if an `Origin` value matches any of the allowed patterns.
141
+ * Pattern matching is case-insensitive (web hosts and schemes are). A value
142
+ * that isn't visible ASCII matches nothing — a browser never sends one.
57
143
  */
58
144
  export declare const should_allow_origin: (origin: string, allowed_patterns: ReadonlyArray<RegExp>) => boolean;
59
145
  /**
@@ -63,6 +149,9 @@ export declare const should_allow_origin: (origin: string, allowed_patterns: Rea
63
149
  * plus `http/simple_request.ts`):
64
150
  * - Checks the `Origin` header (if present) against the allowlist
65
151
  * - Allows requests without an `Origin` header (direct access, curl, etc.)
152
+ * - Refuses an `Origin` that isn't visible ASCII, and a duplicated one — the
153
+ * runtimes join a repeated header into one value (`a, b`), which carries a
154
+ * space and so matches nothing
66
155
  *
67
156
  * Origin-only by design — Fetch spec mandates `Origin` on every unsafe
68
157
  * method (POST / PUT / DELETE / PATCH) regardless of `Referrer-Policy`,
@@ -71,9 +160,15 @@ export declare const should_allow_origin: (origin: string, allowed_patterns: Rea
71
160
  * attached session cookies, so CSRF isn't the relevant threat there —
72
161
  * auth (bearer / daemon token) is the actual control. A Referer fallback
73
162
  * would only widen the accepted-shape envelope without closing a real
74
- * CSRF hole; mirrors `zzz_server::auth::is_request_origin_allowed`.
163
+ * CSRF hole. The Rust spine's `origin_layer` is the twin.
164
+ *
165
+ * `create_app_server` mounts it on every path (`ORIGIN_MIDDLEWARE_PATH`); a
166
+ * request whose path a `carve_outs` entry matches passes unchecked — the named,
167
+ * explicit exception, each with its reason, as the Rust stack's
168
+ * `origin_carve_out` is.
75
169
  *
76
170
  * @param allowed_patterns - compiled regex patterns from `parse_allowed_origins`
171
+ * @param carve_outs - paths the check skips, matched like a middleware path (`middleware_applies`)
77
172
  */
78
- export declare const verify_request_source: (allowed_patterns: ReadonlyArray<RegExp>) => Handler;
173
+ export declare const verify_request_source: (allowed_patterns: ReadonlyArray<RegExp>, carve_outs?: ReadonlyArray<MiddlewareCarveOut>) => Handler;
79
174
  //# sourceMappingURL=origin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAI7C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,KAAK,CAAC,MAAM,CAO5E,CAAC;AAEP;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAuD,CAAC;AAE3D;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,qBAAqB,GAChC,kBAAkB,aAAa,CAAC,MAAM,CAAC,KAAG,OAgB1C,CAAC"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC7C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,OAAO,KAAG,OAC8B,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;EAiBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,2FAA2F;AAC3F,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;EAA4D,CAAC;AACjG,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAE9E,wDAAwD;AACxD,eAAO,MAAM,8BAA8B,EAAE,QAAQ,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,CAwB5F,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;IACvC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,mBAAmB,KAAG,MAOzE,CAAC;AAwBF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,qBAAqB,GACjC,KAAK,MAAM,KACT,MAAM,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAE,EAAE;IAAE,MAAM,EAAE,KAAK,CAAC,mBAAmB,CAAC,CAAA;CAAE,CAgB3E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC/B,QAAQ,MAAM,EACd,kBAAkB,aAAa,CAAC,MAAM,CAAC,KACrC,OAAqF,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,qBAAqB,GAEhC,kBAAkB,aAAa,CAAC,MAAM,CAAC,EACvC,aAAY,aAAa,CAAC,kBAAkB,CAAM,KAChD,OAaF,CAAC"}