cursedbelt-server 1.0.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 (606) hide show
  1. package/LICENSE +15 -0
  2. package/dist/server/activity/index.d.ts +36 -0
  3. package/dist/server/activity/index.js +36 -0
  4. package/dist/server/activity/migrations.d.ts +21 -0
  5. package/dist/server/activity/migrations.js +55 -0
  6. package/dist/server/activity/shareSource.d.ts +11 -0
  7. package/dist/server/activity/shareSource.js +45 -0
  8. package/dist/server/activity/store.d.ts +123 -0
  9. package/dist/server/activity/store.js +178 -0
  10. package/dist/server/app.d.ts +56 -0
  11. package/dist/server/app.js +45 -0
  12. package/dist/server/audit/index.d.ts +2 -0
  13. package/dist/server/audit/index.js +2 -0
  14. package/dist/server/audit/migrations.d.ts +2 -0
  15. package/dist/server/audit/migrations.js +79 -0
  16. package/dist/server/audit/sqliteSink.d.ts +36 -0
  17. package/dist/server/audit/sqliteSink.js +95 -0
  18. package/dist/server/audit/sqliteSink.test.d.ts +1 -0
  19. package/dist/server/audit/sqliteSink.test.js +180 -0
  20. package/dist/server/auth/cookie.d.ts +22 -0
  21. package/dist/server/auth/cookie.js +35 -0
  22. package/dist/server/auth/csrf.d.ts +42 -0
  23. package/dist/server/auth/csrf.js +36 -0
  24. package/dist/server/auth/fileTokenSigner.d.ts +10 -0
  25. package/dist/server/auth/fileTokenSigner.js +22 -0
  26. package/dist/server/auth/getOnly.d.ts +20 -0
  27. package/dist/server/auth/getOnly.js +27 -0
  28. package/dist/server/auth/index.d.ts +12 -0
  29. package/dist/server/auth/index.js +15 -0
  30. package/dist/server/auth/jwt.d.ts +18 -0
  31. package/dist/server/auth/jwt.js +56 -0
  32. package/dist/server/auth/loginThrottle.d.ts +118 -0
  33. package/dist/server/auth/loginThrottle.js +204 -0
  34. package/dist/server/auth/middleware.d.ts +14 -0
  35. package/dist/server/auth/middleware.js +47 -0
  36. package/dist/server/auth/password.d.ts +14 -0
  37. package/dist/server/auth/password.js +65 -0
  38. package/dist/server/auth/sessionStore.d.ts +19 -0
  39. package/dist/server/auth/sessionStore.js +221 -0
  40. package/dist/server/auth/totp.d.ts +34 -0
  41. package/dist/server/auth/totp.js +81 -0
  42. package/dist/server/auth/types.d.ts +133 -0
  43. package/dist/server/auth/types.js +1 -0
  44. package/dist/server/auth/vault.d.ts +27 -0
  45. package/dist/server/auth/vault.js +57 -0
  46. package/dist/server/context.d.ts +35 -0
  47. package/dist/server/context.js +1 -0
  48. package/dist/server/ctgr.d.ts +32 -0
  49. package/dist/server/ctgr.js +69 -0
  50. package/dist/server/db/kysely.d.ts +14 -0
  51. package/dist/server/db/kysely.js +16 -0
  52. package/dist/server/db/plumbingTypes.d.ts +116 -0
  53. package/dist/server/db/plumbingTypes.js +12 -0
  54. package/dist/server/dropzone/duplicates.d.ts +82 -0
  55. package/dist/server/dropzone/duplicates.js +90 -0
  56. package/dist/server/dropzone/index.d.ts +13 -0
  57. package/dist/server/dropzone/index.js +13 -0
  58. package/dist/server/dropzone/scan.d.ts +166 -0
  59. package/dist/server/dropzone/scan.js +272 -0
  60. package/dist/server/errors.d.ts +7 -0
  61. package/dist/server/errors.js +7 -0
  62. package/dist/server/guard/argon2Binding.d.ts +97 -0
  63. package/dist/server/guard/argon2Binding.js +138 -0
  64. package/dist/server/guard/index.d.ts +4 -0
  65. package/dist/server/guard/index.js +6 -0
  66. package/dist/server/guard/kdfWorker.d.ts +101 -0
  67. package/dist/server/guard/kdfWorker.js +301 -0
  68. package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
  69. package/dist/server/guard/kdfWorkerEntry.js +15 -0
  70. package/dist/server/guard/passwordVerifier.d.ts +65 -0
  71. package/dist/server/guard/passwordVerifier.js +122 -0
  72. package/dist/server/guard/revocationStore.d.ts +132 -0
  73. package/dist/server/guard/revocationStore.js +211 -0
  74. package/dist/server/guard/sessionSigner.d.ts +132 -0
  75. package/dist/server/guard/sessionSigner.js +303 -0
  76. package/dist/server/guard/verifier.d.ts +21 -0
  77. package/dist/server/guard/verifier.js +1 -0
  78. package/dist/server/incidents/channels.d.ts +161 -0
  79. package/dist/server/incidents/channels.js +357 -0
  80. package/dist/server/incidents/checks.d.ts +61 -0
  81. package/dist/server/incidents/checks.js +55 -0
  82. package/dist/server/incidents/index.d.ts +51 -0
  83. package/dist/server/incidents/index.js +51 -0
  84. package/dist/server/incidents/migrations.d.ts +20 -0
  85. package/dist/server/incidents/migrations.js +58 -0
  86. package/dist/server/incidents/notifier.d.ts +79 -0
  87. package/dist/server/incidents/notifier.js +141 -0
  88. package/dist/server/incidents/reporter.d.ts +60 -0
  89. package/dist/server/incidents/reporter.js +49 -0
  90. package/dist/server/incidents/routes.d.ts +91 -0
  91. package/dist/server/incidents/routes.js +202 -0
  92. package/dist/server/incidents/store.d.ts +75 -0
  93. package/dist/server/incidents/store.js +176 -0
  94. package/dist/server/incidents/types.d.ts +107 -0
  95. package/dist/server/incidents/types.js +32 -0
  96. package/dist/server/index.d.ts +25 -0
  97. package/dist/server/index.js +53 -0
  98. package/dist/server/jobs/fireQueue.d.ts +12 -0
  99. package/dist/server/jobs/fireQueue.js +37 -0
  100. package/dist/server/jobs/index.d.ts +6 -0
  101. package/dist/server/jobs/index.js +10 -0
  102. package/dist/server/jobs/logger.d.ts +10 -0
  103. package/dist/server/jobs/logger.js +14 -0
  104. package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
  105. package/dist/server/jobs/plainjobAdapter.js +48 -0
  106. package/dist/server/jobs/polling.d.ts +17 -0
  107. package/dist/server/jobs/polling.js +79 -0
  108. package/dist/server/jobs/runner.d.ts +24 -0
  109. package/dist/server/jobs/runner.js +186 -0
  110. package/dist/server/jobs/timeout.d.ts +28 -0
  111. package/dist/server/jobs/timeout.js +47 -0
  112. package/dist/server/jobs/types.d.ts +167 -0
  113. package/dist/server/jobs/types.js +12 -0
  114. package/dist/server/master-lock/guard.d.ts +53 -0
  115. package/dist/server/master-lock/guard.js +205 -0
  116. package/dist/server/master-lock/index.d.ts +21 -0
  117. package/dist/server/master-lock/index.js +21 -0
  118. package/dist/server/master-lock/lockPage.d.ts +34 -0
  119. package/dist/server/master-lock/lockPage.js +281 -0
  120. package/dist/server/master-lock/masterLock.d.ts +294 -0
  121. package/dist/server/master-lock/masterLock.js +523 -0
  122. package/dist/server/master-lock/principals.d.ts +76 -0
  123. package/dist/server/master-lock/principals.js +157 -0
  124. package/dist/server/master-lock/seed.d.ts +47 -0
  125. package/dist/server/master-lock/seed.js +93 -0
  126. package/dist/server/master-lock/store.d.ts +19 -0
  127. package/dist/server/master-lock/store.js +20 -0
  128. package/dist/server/media-bun/image.d.ts +7 -0
  129. package/dist/server/media-bun/image.js +96 -0
  130. package/dist/server/media-bun/index.d.ts +4 -0
  131. package/dist/server/media-bun/index.js +10 -0
  132. package/dist/server/media-bun/streaming.d.ts +86 -0
  133. package/dist/server/media-bun/streaming.js +237 -0
  134. package/dist/server/media-bun/video.d.ts +35 -0
  135. package/dist/server/media-bun/video.js +181 -0
  136. package/dist/server/metrics/metricsBuffer.d.ts +39 -0
  137. package/dist/server/metrics/metricsBuffer.js +36 -0
  138. package/dist/server/metrics/normalizeRoute.d.ts +5 -0
  139. package/dist/server/metrics/normalizeRoute.js +58 -0
  140. package/dist/server/middleware/correlationId.d.ts +9 -0
  141. package/dist/server/middleware/correlationId.js +20 -0
  142. package/dist/server/middleware/corsAllowlist.d.ts +38 -0
  143. package/dist/server/middleware/corsAllowlist.js +37 -0
  144. package/dist/server/middleware/errorEnvelope.d.ts +17 -0
  145. package/dist/server/middleware/errorEnvelope.js +32 -0
  146. package/dist/server/middleware/index.d.ts +9 -0
  147. package/dist/server/middleware/index.js +10 -0
  148. package/dist/server/middleware/localOrigin.d.ts +71 -0
  149. package/dist/server/middleware/localOrigin.js +119 -0
  150. package/dist/server/middleware/rateLimit.d.ts +56 -0
  151. package/dist/server/middleware/rateLimit.js +86 -0
  152. package/dist/server/middleware/requestLogger.d.ts +29 -0
  153. package/dist/server/middleware/requestLogger.js +47 -0
  154. package/dist/server/middleware/securityHeaders.d.ts +29 -0
  155. package/dist/server/middleware/securityHeaders.js +44 -0
  156. package/dist/server/middleware/zValidator.d.ts +29 -0
  157. package/dist/server/middleware/zValidator.js +57 -0
  158. package/dist/server/migration/runner.d.ts +42 -0
  159. package/dist/server/migration/runner.js +70 -0
  160. package/dist/server/migration/types.d.ts +16 -0
  161. package/dist/server/migration/types.js +1 -0
  162. package/dist/server/net/outboundGuard.d.ts +30 -0
  163. package/dist/server/net/outboundGuard.js +199 -0
  164. package/dist/server/notifications/hub.d.ts +67 -0
  165. package/dist/server/notifications/hub.js +187 -0
  166. package/dist/server/notifications/index.d.ts +47 -0
  167. package/dist/server/notifications/index.js +46 -0
  168. package/dist/server/notifications/migrations.d.ts +13 -0
  169. package/dist/server/notifications/migrations.js +20 -0
  170. package/dist/server/notifications/routes.d.ts +58 -0
  171. package/dist/server/notifications/routes.js +113 -0
  172. package/dist/server/notifications/service.d.ts +58 -0
  173. package/dist/server/notifications/service.js +41 -0
  174. package/dist/server/paths/appDataHome.d.ts +19 -0
  175. package/dist/server/paths/appDataHome.js +68 -0
  176. package/dist/server/private-media/crypto.d.ts +37 -0
  177. package/dist/server/private-media/crypto.js +111 -0
  178. package/dist/server/private-media/index-store.d.ts +31 -0
  179. package/dist/server/private-media/index-store.js +36 -0
  180. package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
  181. package/dist/server/private-media/mountPrivateRoutes.js +219 -0
  182. package/dist/server/private-media/pipeline.d.ts +37 -0
  183. package/dist/server/private-media/pipeline.js +97 -0
  184. package/dist/server/private-media/unlock.d.ts +52 -0
  185. package/dist/server/private-media/unlock.js +55 -0
  186. package/dist/server/resume/index.d.ts +2 -0
  187. package/dist/server/resume/index.js +2 -0
  188. package/dist/server/resume/routes.d.ts +41 -0
  189. package/dist/server/resume/routes.js +50 -0
  190. package/dist/server/resume/store.d.ts +37 -0
  191. package/dist/server/resume/store.js +38 -0
  192. package/dist/server/retention/archive.d.ts +78 -0
  193. package/dist/server/retention/archive.js +284 -0
  194. package/dist/server/retention/fleet.d.ts +50 -0
  195. package/dist/server/retention/fleet.js +66 -0
  196. package/dist/server/retention/index.d.ts +38 -0
  197. package/dist/server/retention/index.js +38 -0
  198. package/dist/server/retention/policy.d.ts +263 -0
  199. package/dist/server/retention/policy.js +264 -0
  200. package/dist/server/retention/store.d.ts +101 -0
  201. package/dist/server/retention/store.js +251 -0
  202. package/dist/server/retention/sweeper.d.ts +69 -0
  203. package/dist/server/retention/sweeper.js +289 -0
  204. package/dist/server/retention/target.d.ts +143 -0
  205. package/dist/server/retention/target.js +438 -0
  206. package/dist/server/search/index.d.ts +2 -0
  207. package/dist/server/search/index.js +6 -0
  208. package/dist/server/search/pgSearchProvider.d.ts +91 -0
  209. package/dist/server/search/pgSearchProvider.js +330 -0
  210. package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
  211. package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
  212. package/dist/server/search/pgSearchSchema.d.ts +81 -0
  213. package/dist/server/search/pgSearchSchema.js +89 -0
  214. package/dist/server/sharing/index.d.ts +18 -0
  215. package/dist/server/sharing/index.js +16 -0
  216. package/dist/server/sharing/migrations.d.ts +6 -0
  217. package/dist/server/sharing/migrations.js +79 -0
  218. package/dist/server/sharing/router.d.ts +111 -0
  219. package/dist/server/sharing/router.js +93 -0
  220. package/dist/server/sharing/sql.d.ts +67 -0
  221. package/dist/server/sharing/sql.js +88 -0
  222. package/dist/server/sharing/store.d.ts +173 -0
  223. package/dist/server/sharing/store.js +484 -0
  224. package/dist/server/sqlite/backup.d.ts +48 -0
  225. package/dist/server/sqlite/backup.js +97 -0
  226. package/dist/server/sqlite/browse.d.ts +42 -0
  227. package/dist/server/sqlite/browse.js +84 -0
  228. package/dist/server/sqlite/export.d.ts +32 -0
  229. package/dist/server/sqlite/export.js +42 -0
  230. package/dist/server/sqlite/fts5.d.ts +106 -0
  231. package/dist/server/sqlite/fts5.js +154 -0
  232. package/dist/server/sqlite/health.d.ts +44 -0
  233. package/dist/server/sqlite/health.js +52 -0
  234. package/dist/server/sqlite/import.d.ts +36 -0
  235. package/dist/server/sqlite/import.js +108 -0
  236. package/dist/server/sqlite/index.d.ts +9 -0
  237. package/dist/server/sqlite/index.js +15 -0
  238. package/dist/server/sqlite/readonlySql.d.ts +15 -0
  239. package/dist/server/sqlite/readonlySql.js +65 -0
  240. package/dist/server/sqlite/stats.d.ts +22 -0
  241. package/dist/server/sqlite/stats.js +32 -0
  242. package/dist/server/sqlite/viewer.d.ts +17 -0
  243. package/dist/server/sqlite/viewer.js +56 -0
  244. package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
  245. package/dist/server/storage/binaryServerProcessing.js +81 -0
  246. package/dist/server/storage/binaryServerStore.d.ts +3 -0
  247. package/dist/server/storage/binaryServerStore.js +95 -0
  248. package/dist/server/storage/diskStore.d.ts +2 -0
  249. package/dist/server/storage/diskStore.js +88 -0
  250. package/dist/server/storage/files/adapter.d.ts +33 -0
  251. package/dist/server/storage/files/adapter.js +203 -0
  252. package/dist/server/storage/files/catalogue.d.ts +72 -0
  253. package/dist/server/storage/files/catalogue.js +1033 -0
  254. package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
  255. package/dist/server/storage/files/chunkedUpload.js +70 -0
  256. package/dist/server/storage/files/fileRoutes.d.ts +51 -0
  257. package/dist/server/storage/files/fileRoutes.js +405 -0
  258. package/dist/server/storage/files/index.d.ts +9 -0
  259. package/dist/server/storage/files/index.js +11 -0
  260. package/dist/server/storage/files/migrations.d.ts +2 -0
  261. package/dist/server/storage/files/migrations.js +163 -0
  262. package/dist/server/storage/files/mimeByExt.d.ts +1 -0
  263. package/dist/server/storage/files/mimeByExt.js +13 -0
  264. package/dist/server/storage/files/stack.d.ts +136 -0
  265. package/dist/server/storage/files/stack.js +132 -0
  266. package/dist/server/storage/files/storageOps.d.ts +54 -0
  267. package/dist/server/storage/files/storageOps.js +110 -0
  268. package/dist/server/storage/files/types.d.ts +261 -0
  269. package/dist/server/storage/files/types.js +22 -0
  270. package/dist/server/storage/imageThumbnail.d.ts +3 -0
  271. package/dist/server/storage/imageThumbnail.js +50 -0
  272. package/dist/server/storage/index.d.ts +15 -0
  273. package/dist/server/storage/index.js +27 -0
  274. package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
  275. package/dist/server/storage/mountMediaRoutes.js +141 -0
  276. package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
  277. package/dist/server/storage/objectStoreMediaStore.js +57 -0
  278. package/dist/server/storage/s3ObjectStore.d.ts +63 -0
  279. package/dist/server/storage/s3ObjectStore.js +187 -0
  280. package/dist/server/storage/sources.d.ts +47 -0
  281. package/dist/server/storage/sources.js +148 -0
  282. package/dist/server/storage/streamRange.d.ts +19 -0
  283. package/dist/server/storage/streamRange.js +69 -0
  284. package/dist/server/storage/streamToken.d.ts +16 -0
  285. package/dist/server/storage/streamToken.js +54 -0
  286. package/dist/server/storage/transcodeJobs.d.ts +65 -0
  287. package/dist/server/storage/transcodeJobs.js +104 -0
  288. package/dist/server/storage/types.d.ts +193 -0
  289. package/dist/server/storage/types.js +41 -0
  290. package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
  291. package/dist/server/storage/videoDerivativeKeys.js +149 -0
  292. package/dist/server/storage/videoProcessor.d.ts +30 -0
  293. package/dist/server/storage/videoProcessor.js +148 -0
  294. package/dist/server/sync/alarm.d.ts +34 -0
  295. package/dist/server/sync/alarm.js +91 -0
  296. package/dist/server/sync/commands.d.ts +87 -0
  297. package/dist/server/sync/commands.js +241 -0
  298. package/dist/server/sync/engine.d.ts +62 -0
  299. package/dist/server/sync/engine.js +184 -0
  300. package/dist/server/sync/http.d.ts +106 -0
  301. package/dist/server/sync/http.js +243 -0
  302. package/dist/server/sync/index.d.ts +41 -0
  303. package/dist/server/sync/index.js +41 -0
  304. package/dist/server/sync/opLog.d.ts +61 -0
  305. package/dist/server/sync/opLog.js +96 -0
  306. package/dist/server/sync/planner.d.ts +31 -0
  307. package/dist/server/sync/planner.js +30 -0
  308. package/dist/server/sync/status.d.ts +63 -0
  309. package/dist/server/sync/status.js +49 -0
  310. package/dist/server/sync/timer.d.ts +52 -0
  311. package/dist/server/sync/timer.js +170 -0
  312. package/dist/server/sync/tokens.d.ts +25 -0
  313. package/dist/server/sync/tokens.js +51 -0
  314. package/dist/server/sync/types.d.ts +101 -0
  315. package/dist/server/sync/types.js +29 -0
  316. package/dist/server/ws/eventBus.d.ts +14 -0
  317. package/dist/server/ws/eventBus.js +42 -0
  318. package/dist/server/ws/hub.d.ts +20 -0
  319. package/dist/server/ws/hub.js +154 -0
  320. package/dist/server/ws/index.d.ts +5 -0
  321. package/dist/server/ws/index.js +7 -0
  322. package/dist/server/ws/presence.d.ts +11 -0
  323. package/dist/server/ws/presence.js +20 -0
  324. package/dist/server/ws/room.d.ts +17 -0
  325. package/dist/server/ws/room.js +23 -0
  326. package/dist/server/ws/types.d.ts +122 -0
  327. package/dist/server/ws/types.js +1 -0
  328. package/package.json +234 -0
  329. package/src/declaredDepsAreImported.spec.ts +87 -0
  330. package/src/leafSubpathsImportNothing.spec.ts +358 -0
  331. package/src/lockfileIsTracked.spec.ts +50 -0
  332. package/src/namedSubpathsResolve.spec.ts +122 -0
  333. package/src/noPathDeps.spec.ts +24 -0
  334. package/src/publishShape.spec.ts +89 -0
  335. package/src/server/activity/index.ts +59 -0
  336. package/src/server/activity/migrations.ts +67 -0
  337. package/src/server/activity/shareSource.ts +57 -0
  338. package/src/server/activity/store.spec.ts +255 -0
  339. package/src/server/activity/store.ts +368 -0
  340. package/src/server/app.spec.ts +243 -0
  341. package/src/server/app.ts +89 -0
  342. package/src/server/audit/index.ts +2 -0
  343. package/src/server/audit/migrations.ts +88 -0
  344. package/src/server/audit/sqliteSink.test.ts +200 -0
  345. package/src/server/audit/sqliteSink.ts +162 -0
  346. package/src/server/auth/cookie.spec.ts +46 -0
  347. package/src/server/auth/cookie.ts +42 -0
  348. package/src/server/auth/csrf.spec.ts +81 -0
  349. package/src/server/auth/csrf.ts +83 -0
  350. package/src/server/auth/fileTokenSigner.ts +40 -0
  351. package/src/server/auth/getOnly.spec.ts +53 -0
  352. package/src/server/auth/getOnly.ts +32 -0
  353. package/src/server/auth/index.ts +51 -0
  354. package/src/server/auth/jwt.spec.ts +106 -0
  355. package/src/server/auth/jwt.ts +75 -0
  356. package/src/server/auth/loginThrottle.spec.ts +213 -0
  357. package/src/server/auth/loginThrottle.ts +261 -0
  358. package/src/server/auth/middleware.spec.ts +148 -0
  359. package/src/server/auth/middleware.ts +56 -0
  360. package/src/server/auth/password.spec.ts +48 -0
  361. package/src/server/auth/password.ts +71 -0
  362. package/src/server/auth/sessionStore.spec.ts +189 -0
  363. package/src/server/auth/sessionStore.ts +261 -0
  364. package/src/server/auth/totp.spec.ts +73 -0
  365. package/src/server/auth/totp.ts +92 -0
  366. package/src/server/auth/types.ts +146 -0
  367. package/src/server/auth/vault.spec.ts +51 -0
  368. package/src/server/auth/vault.ts +71 -0
  369. package/src/server/context.ts +37 -0
  370. package/src/server/ctgr.spec.ts +138 -0
  371. package/src/server/ctgr.ts +108 -0
  372. package/src/server/db/kysely.spec.ts +62 -0
  373. package/src/server/db/kysely.ts +21 -0
  374. package/src/server/db/plumbingTypes.ts +126 -0
  375. package/src/server/dropzone/duplicates.ts +121 -0
  376. package/src/server/dropzone/index.ts +30 -0
  377. package/src/server/dropzone/scan.spec.ts +255 -0
  378. package/src/server/dropzone/scan.ts +342 -0
  379. package/src/server/errors.spec.ts +76 -0
  380. package/src/server/errors.ts +22 -0
  381. package/src/server/guard/argon2Binding.spec.ts +117 -0
  382. package/src/server/guard/argon2Binding.ts +195 -0
  383. package/src/server/guard/argon2Kat.spec.ts +250 -0
  384. package/src/server/guard/guard.spec.ts +390 -0
  385. package/src/server/guard/index.ts +6 -0
  386. package/src/server/guard/kdfWorker.spec.ts +283 -0
  387. package/src/server/guard/kdfWorker.ts +367 -0
  388. package/src/server/guard/kdfWorkerEntry.ts +16 -0
  389. package/src/server/guard/passwordVerifier.spec.ts +71 -0
  390. package/src/server/guard/passwordVerifier.ts +175 -0
  391. package/src/server/guard/revocationStore.spec.ts +135 -0
  392. package/src/server/guard/revocationStore.ts +291 -0
  393. package/src/server/guard/sessionSigner.ts +425 -0
  394. package/src/server/guard/verifier.ts +27 -0
  395. package/src/server/incidents/channels.ts +487 -0
  396. package/src/server/incidents/checks.ts +120 -0
  397. package/src/server/incidents/incidents.spec.ts +463 -0
  398. package/src/server/incidents/index.ts +96 -0
  399. package/src/server/incidents/migrations.ts +66 -0
  400. package/src/server/incidents/notifier.ts +215 -0
  401. package/src/server/incidents/reporter.ts +110 -0
  402. package/src/server/incidents/routes.ts +314 -0
  403. package/src/server/incidents/store.ts +329 -0
  404. package/src/server/incidents/types.ts +116 -0
  405. package/src/server/index.ts +248 -0
  406. package/src/server/jobs/fireQueue.spec.ts +85 -0
  407. package/src/server/jobs/fireQueue.ts +37 -0
  408. package/src/server/jobs/index.ts +26 -0
  409. package/src/server/jobs/logger.ts +16 -0
  410. package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
  411. package/src/server/jobs/plainjobAdapter.ts +54 -0
  412. package/src/server/jobs/polling.spec.ts +219 -0
  413. package/src/server/jobs/polling.ts +81 -0
  414. package/src/server/jobs/runner.spec.ts +213 -0
  415. package/src/server/jobs/runner.ts +213 -0
  416. package/src/server/jobs/timeout.spec.ts +35 -0
  417. package/src/server/jobs/timeout.ts +53 -0
  418. package/src/server/jobs/types.ts +188 -0
  419. package/src/server/master-lock/guard.spec.ts +347 -0
  420. package/src/server/master-lock/guard.ts +290 -0
  421. package/src/server/master-lock/index.ts +44 -0
  422. package/src/server/master-lock/lockPage.spec.ts +88 -0
  423. package/src/server/master-lock/lockPage.ts +313 -0
  424. package/src/server/master-lock/masterLock.spec.ts +338 -0
  425. package/src/server/master-lock/masterLock.ts +627 -0
  426. package/src/server/master-lock/principals.spec.ts +378 -0
  427. package/src/server/master-lock/principals.ts +175 -0
  428. package/src/server/master-lock/seed.spec.ts +69 -0
  429. package/src/server/master-lock/seed.ts +112 -0
  430. package/src/server/master-lock/store.ts +35 -0
  431. package/src/server/media-bun/image.spec.ts +115 -0
  432. package/src/server/media-bun/image.ts +134 -0
  433. package/src/server/media-bun/index.ts +52 -0
  434. package/src/server/media-bun/streaming.spec.ts +134 -0
  435. package/src/server/media-bun/streaming.ts +347 -0
  436. package/src/server/media-bun/video.spec.ts +115 -0
  437. package/src/server/media-bun/video.ts +232 -0
  438. package/src/server/metrics/metricsBuffer.ts +92 -0
  439. package/src/server/metrics/normalizeRoute.spec.ts +51 -0
  440. package/src/server/metrics/normalizeRoute.ts +63 -0
  441. package/src/server/middleware/correlationId.ts +24 -0
  442. package/src/server/middleware/corsAllowlist.spec.ts +118 -0
  443. package/src/server/middleware/corsAllowlist.ts +69 -0
  444. package/src/server/middleware/errorEnvelope.ts +38 -0
  445. package/src/server/middleware/index.ts +19 -0
  446. package/src/server/middleware/localOrigin.spec.ts +168 -0
  447. package/src/server/middleware/localOrigin.ts +139 -0
  448. package/src/server/middleware/rateLimit.spec.ts +72 -0
  449. package/src/server/middleware/rateLimit.ts +125 -0
  450. package/src/server/middleware/requestLogger.ts +95 -0
  451. package/src/server/middleware/securityHeaders.ts +78 -0
  452. package/src/server/middleware/zValidator.spec.ts +251 -0
  453. package/src/server/middleware/zValidator.ts +114 -0
  454. package/src/server/migration/runner.spec.ts +88 -0
  455. package/src/server/migration/runner.ts +87 -0
  456. package/src/server/migration/types.ts +17 -0
  457. package/src/server/net/outboundGuard.spec.ts +90 -0
  458. package/src/server/net/outboundGuard.ts +205 -0
  459. package/src/server/notifications/hub.ts +245 -0
  460. package/src/server/notifications/index.ts +77 -0
  461. package/src/server/notifications/migrations.ts +39 -0
  462. package/src/server/notifications/routes.ts +177 -0
  463. package/src/server/notifications/service.spec.ts +305 -0
  464. package/src/server/notifications/service.ts +112 -0
  465. package/src/server/paths/appDataHome.spec.ts +87 -0
  466. package/src/server/paths/appDataHome.ts +84 -0
  467. package/src/server/private-media/blobRoute.spec.ts +165 -0
  468. package/src/server/private-media/crypto.spec.ts +81 -0
  469. package/src/server/private-media/crypto.ts +155 -0
  470. package/src/server/private-media/gatePolicy.spec.ts +98 -0
  471. package/src/server/private-media/index-store.ts +83 -0
  472. package/src/server/private-media/mountPrivateRoutes.ts +265 -0
  473. package/src/server/private-media/pipeline.spec.ts +111 -0
  474. package/src/server/private-media/pipeline.ts +170 -0
  475. package/src/server/private-media/routes.spec.ts +265 -0
  476. package/src/server/private-media/unlock.ts +112 -0
  477. package/src/server/resume/index.ts +8 -0
  478. package/src/server/resume/resume.spec.ts +118 -0
  479. package/src/server/resume/routes.ts +92 -0
  480. package/src/server/resume/store.ts +82 -0
  481. package/src/server/retention/archive.spec.ts +181 -0
  482. package/src/server/retention/archive.ts +353 -0
  483. package/src/server/retention/fleet.ts +104 -0
  484. package/src/server/retention/index.ts +101 -0
  485. package/src/server/retention/policy.spec.ts +132 -0
  486. package/src/server/retention/policy.ts +392 -0
  487. package/src/server/retention/store.spec.ts +236 -0
  488. package/src/server/retention/store.ts +338 -0
  489. package/src/server/retention/sweeper.spec.ts +321 -0
  490. package/src/server/retention/sweeper.ts +367 -0
  491. package/src/server/retention/target.spec.ts +592 -0
  492. package/src/server/retention/target.ts +613 -0
  493. package/src/server/search/index.ts +12 -0
  494. package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
  495. package/src/server/search/pgSearchProvider.ts +421 -0
  496. package/src/server/search/pgSearchSchema.ts +147 -0
  497. package/src/server/sharing/index.ts +65 -0
  498. package/src/server/sharing/migrations.ts +128 -0
  499. package/src/server/sharing/router.spec.ts +167 -0
  500. package/src/server/sharing/router.ts +194 -0
  501. package/src/server/sharing/sql.ts +109 -0
  502. package/src/server/sharing/store.spec.ts +408 -0
  503. package/src/server/sharing/store.ts +737 -0
  504. package/src/server/sqlite/backup.spec.ts +114 -0
  505. package/src/server/sqlite/backup.ts +154 -0
  506. package/src/server/sqlite/browse.spec.ts +70 -0
  507. package/src/server/sqlite/browse.ts +120 -0
  508. package/src/server/sqlite/export.spec.ts +43 -0
  509. package/src/server/sqlite/export.ts +74 -0
  510. package/src/server/sqlite/fts5.spec.ts +175 -0
  511. package/src/server/sqlite/fts5.ts +255 -0
  512. package/src/server/sqlite/health.spec.ts +79 -0
  513. package/src/server/sqlite/health.ts +105 -0
  514. package/src/server/sqlite/import.spec.ts +75 -0
  515. package/src/server/sqlite/import.ts +141 -0
  516. package/src/server/sqlite/index.ts +64 -0
  517. package/src/server/sqlite/readonlySql.spec.ts +58 -0
  518. package/src/server/sqlite/readonlySql.ts +67 -0
  519. package/src/server/sqlite/stats.spec.ts +21 -0
  520. package/src/server/sqlite/stats.ts +61 -0
  521. package/src/server/sqlite/viewer.spec.ts +127 -0
  522. package/src/server/sqlite/viewer.ts +72 -0
  523. package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
  524. package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
  525. package/src/server/storage/binaryServerProcessing.ts +127 -0
  526. package/src/server/storage/binaryServerStore.ts +102 -0
  527. package/src/server/storage/chunked.integration.spec.ts +185 -0
  528. package/src/server/storage/diskStore.ts +83 -0
  529. package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
  530. package/src/server/storage/files/adapter.photo.spec.ts +128 -0
  531. package/src/server/storage/files/adapter.spec.ts +121 -0
  532. package/src/server/storage/files/adapter.ts +255 -0
  533. package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
  534. package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
  535. package/src/server/storage/files/catalogue.private.spec.ts +106 -0
  536. package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
  537. package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
  538. package/src/server/storage/files/catalogue.spec.ts +133 -0
  539. package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
  540. package/src/server/storage/files/catalogue.ts +1326 -0
  541. package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
  542. package/src/server/storage/files/chunkedUpload.ts +146 -0
  543. package/src/server/storage/files/collections.spec.ts +294 -0
  544. package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
  545. package/src/server/storage/files/fileRoutes.spec.ts +327 -0
  546. package/src/server/storage/files/fileRoutes.ts +462 -0
  547. package/src/server/storage/files/index.ts +66 -0
  548. package/src/server/storage/files/migrations.ts +187 -0
  549. package/src/server/storage/files/mimeByExt.ts +14 -0
  550. package/src/server/storage/files/stack.spec.ts +258 -0
  551. package/src/server/storage/files/stack.ts +284 -0
  552. package/src/server/storage/files/storageOps.spec.ts +126 -0
  553. package/src/server/storage/files/storageOps.ts +213 -0
  554. package/src/server/storage/files/types.ts +285 -0
  555. package/src/server/storage/imageThumbnail.spec.ts +81 -0
  556. package/src/server/storage/imageThumbnail.ts +60 -0
  557. package/src/server/storage/index.ts +122 -0
  558. package/src/server/storage/mountMediaRoutes.ts +231 -0
  559. package/src/server/storage/objectStore.spec.ts +272 -0
  560. package/src/server/storage/objectStoreMediaStore.ts +63 -0
  561. package/src/server/storage/s3ObjectStore.ts +239 -0
  562. package/src/server/storage/sources.ts +245 -0
  563. package/src/server/storage/storage.spec.ts +219 -0
  564. package/src/server/storage/streamRange.ts +92 -0
  565. package/src/server/storage/streamToken.ts +76 -0
  566. package/src/server/storage/transcode.integration.spec.ts +134 -0
  567. package/src/server/storage/transcodeJobs.spec.ts +152 -0
  568. package/src/server/storage/transcodeJobs.ts +171 -0
  569. package/src/server/storage/types.ts +230 -0
  570. package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
  571. package/src/server/storage/videoDerivativeKeys.ts +172 -0
  572. package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
  573. package/src/server/storage/videoProcessor.ts +229 -0
  574. package/src/server/sync/alarm.spec.ts +149 -0
  575. package/src/server/sync/alarm.ts +137 -0
  576. package/src/server/sync/commands.spec.ts +145 -0
  577. package/src/server/sync/commands.ts +361 -0
  578. package/src/server/sync/engine.spec.ts +496 -0
  579. package/src/server/sync/engine.ts +255 -0
  580. package/src/server/sync/http.ts +316 -0
  581. package/src/server/sync/httpRemote.spec.ts +158 -0
  582. package/src/server/sync/index.ts +90 -0
  583. package/src/server/sync/opLog.spec.ts +110 -0
  584. package/src/server/sync/opLog.ts +189 -0
  585. package/src/server/sync/planner.spec.ts +53 -0
  586. package/src/server/sync/planner.ts +62 -0
  587. package/src/server/sync/status.spec.ts +92 -0
  588. package/src/server/sync/status.ts +108 -0
  589. package/src/server/sync/timer.spec.ts +150 -0
  590. package/src/server/sync/timer.ts +207 -0
  591. package/src/server/sync/tokens.spec.ts +38 -0
  592. package/src/server/sync/tokens.ts +94 -0
  593. package/src/server/sync/types.ts +108 -0
  594. package/src/server/ws/eventBus.spec.ts +67 -0
  595. package/src/server/ws/eventBus.ts +46 -0
  596. package/src/server/ws/hub.integration.spec.ts +93 -0
  597. package/src/server/ws/hub.spec.ts +282 -0
  598. package/src/server/ws/hub.ts +168 -0
  599. package/src/server/ws/index.ts +20 -0
  600. package/src/server/ws/presence.spec.ts +27 -0
  601. package/src/server/ws/presence.ts +26 -0
  602. package/src/server/ws/room.spec.ts +36 -0
  603. package/src/server/ws/room.ts +29 -0
  604. package/src/server/ws/types.ts +134 -0
  605. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  606. package/src/testTmpRoot.spec.ts +102 -0
@@ -0,0 +1,37 @@
1
+ import { compileOriginMatcher, parseOriginList, siteOriginRules } from 'cwip/http';
2
+ import { cors } from 'hono/cors';
3
+ /** Build the Hono middleware from {@link CorsOpts}. Compiles the matcher once. */
4
+ export const corsAllowlist = (opts) => {
5
+ const rules = [
6
+ ...(opts.siteDomain ? siteOriginRules(opts.siteDomain) : []),
7
+ ...(opts.origins ?? []),
8
+ ];
9
+ const isAllowed = compileOriginMatcher(rules);
10
+ return cors({
11
+ origin: (origin) => {
12
+ if (isAllowed(origin)) {
13
+ return origin;
14
+ }
15
+ if (origin) {
16
+ opts.onReject?.(origin);
17
+ }
18
+ return null;
19
+ },
20
+ credentials: opts.credentials ?? true,
21
+ });
22
+ };
23
+ /**
24
+ * Resolve {@link CorsOpts} from the conventional env vars, `undefined` when
25
+ * neither is set (same-origin deploy — no CORS middleware needed):
26
+ * - `CORS_SITE_DOMAIN=example.com` — the systemic knob: apex + every subdomain.
27
+ * - `CORS_ORIGINS=https://a.com,*.b.com` — exact/wildcard extras (or the whole
28
+ * list, for apps that predate `CORS_SITE_DOMAIN`).
29
+ */
30
+ export const corsOptsFromEnv = (env = process.env) => {
31
+ const siteDomain = env.CORS_SITE_DOMAIN?.trim() || undefined;
32
+ const origins = parseOriginList(env.CORS_ORIGINS);
33
+ if (!siteDomain && origins.length === 0) {
34
+ return undefined;
35
+ }
36
+ return { siteDomain, origins };
37
+ };
@@ -0,0 +1,17 @@
1
+ import type { ErrorHandler } from 'hono';
2
+ import type { CursedbeltEnv } from '../context';
3
+ /**
4
+ * Hono `onError` handler. Converts ANY thrown value into the ONE canonical rich
5
+ * envelope — `{ error: { name, message, code?, status?, category?, context?,
6
+ * isOperational, timestamp }, correlationId? }` (the shared `toErrorEnvelope` from
7
+ * `cwip/error`, re-exported via cursedbelt-core/errors) — plus the right HTTP status,
8
+ * and echoes the correlation id header so a client can quote it in a bug report.
9
+ * This is the Hono twin of cwip's express `errorHandler`; both funnel through the
10
+ * same formatter, so their JSON is byte-identical for the same thrown error.
11
+ *
12
+ * - `ApiError` / any `AppError` → its `status` (else 500) + full summary.
13
+ * - `HTTPException` → normalized to an `ApiError` (its status + message).
14
+ * - anything else → 500 + a generic message; the real error is
15
+ * logged with the correlation id (never leaked).
16
+ */
17
+ export declare const errorEnvelope: ErrorHandler<CursedbeltEnv>;
@@ -0,0 +1,32 @@
1
+ import { HTTPException } from 'hono/http-exception';
2
+ import { ApiError, isAppError, toErrorEnvelope } from '../errors';
3
+ /**
4
+ * Hono `onError` handler. Converts ANY thrown value into the ONE canonical rich
5
+ * envelope — `{ error: { name, message, code?, status?, category?, context?,
6
+ * isOperational, timestamp }, correlationId? }` (the shared `toErrorEnvelope` from
7
+ * `cwip/error`, re-exported via cursedbelt-core/errors) — plus the right HTTP status,
8
+ * and echoes the correlation id header so a client can quote it in a bug report.
9
+ * This is the Hono twin of cwip's express `errorHandler`; both funnel through the
10
+ * same formatter, so their JSON is byte-identical for the same thrown error.
11
+ *
12
+ * - `ApiError` / any `AppError` → its `status` (else 500) + full summary.
13
+ * - `HTTPException` → normalized to an `ApiError` (its status + message).
14
+ * - anything else → 500 + a generic message; the real error is
15
+ * logged with the correlation id (never leaked).
16
+ */
17
+ export const errorEnvelope = (err, c) => {
18
+ const cid = c.get('correlationId');
19
+ if (cid)
20
+ c.header('x-correlation-id', cid);
21
+ // Hono's own thrown errors carry a status + safe message — normalize to an
22
+ // `ApiError` so they flow through the single rich-envelope path.
23
+ const normalized = err instanceof HTTPException ? new ApiError(err.status, err.message || 'Error') : err;
24
+ // Unexpected (non-`AppError`) errors: log the real thing (with the correlation
25
+ // id for cross-reference). The envelope stays sanitized — `toErrorEnvelope`
26
+ // returns a generic 500 message for non-`AppError`s, so internals never leak.
27
+ if (!isAppError(normalized)) {
28
+ console.error(`[error]${cid ? ` [${cid}]` : ''}`, err);
29
+ }
30
+ const envelope = toErrorEnvelope(normalized, { correlationId: cid });
31
+ return c.json(envelope, (envelope.error.status ?? 500));
32
+ };
@@ -0,0 +1,9 @@
1
+ export { correlationId } from './correlationId';
2
+ export { type CorsOpts, corsAllowlist, corsOptsFromEnv } from './corsAllowlist';
3
+ export { errorEnvelope } from './errorEnvelope';
4
+ export { crossOriginRefusal } from './localOrigin';
5
+ export { clientIp, createRateLimiter, type RateLimitOpts } from './rateLimit';
6
+ export { type RequestLoggerOpts, requestLogger } from './requestLogger';
7
+ export { type CspDirectives, type SecurityHeadersOpts, securityHeaders, } from './securityHeaders';
8
+ export { toValidationIssues, VALIDATION_ERROR_CODE, type ValidationIssue, validationEnvelope, } from '../errors';
9
+ export { type ZValidatorTarget, zValidator } from './zValidator';
@@ -0,0 +1,10 @@
1
+ // The cross-cutting cursedbelt server middleware set.
2
+ export { correlationId } from './correlationId';
3
+ export { corsAllowlist, corsOptsFromEnv } from './corsAllowlist';
4
+ export { errorEnvelope } from './errorEnvelope';
5
+ export { crossOriginRefusal } from './localOrigin';
6
+ export { clientIp, createRateLimiter } from './rateLimit';
7
+ export { requestLogger } from './requestLogger';
8
+ export { securityHeaders, } from './securityHeaders';
9
+ export { toValidationIssues, VALIDATION_ERROR_CODE, validationEnvelope, } from '../errors';
10
+ export { zValidator } from './zValidator';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The second lock on a LOOPBACK-ONLY HTTP daemon — the one binding to 127.0.0.1
3
+ * does not provide.
4
+ *
5
+ * ── The gap, measured 2026-08-06 against a live daemon ──────────────────────
6
+ * A machine-local daemon's whole trust model is "it only listens on 127.0.0.1",
7
+ * and for the NETWORK that is a real wall. For the owner's BROWSER it is not,
8
+ * for two reasons that have nothing to do with binding:
9
+ *
10
+ * 1. **CSRF.** Any page on any website can `POST http://127.0.0.1:<port>/...`.
11
+ * A cross-origin POST normally trips a CORS preflight the server would fail,
12
+ * but a preflight is only required for a NON-simple request — and
13
+ * `<form enctype="text/plain">` is simple, so it fires with no preflight at
14
+ * all. A body parser that calls `req.json()` parses it regardless of
15
+ * Content-Type, and the classic `text/plain` form trick smuggles valid JSON
16
+ * straight through. Verified by hand against the station daemon on :4747: a
17
+ * POST carrying `Content-Type: text/plain`, `Origin: https://evil.example.com`
18
+ * and `Sec-Fetch-Site: cross-site` was accepted, parsed, and reached field
19
+ * validation. The attacker cannot READ the reply (no CORS headers, so it
20
+ * stays opaque) — but destruction does not need a reply.
21
+ *
22
+ * 2. **DNS rebinding.** With no Host check, an attacker domain whose DNS flips
23
+ * to 127.0.0.1 becomes same-origin in the browser's eyes — which lifts the
24
+ * opaque-response limit above and turns every GET into an exfiltration
25
+ * channel.
26
+ *
27
+ * ── What this refuses, and why it costs nothing ─────────────────────────────
28
+ * A refusal needs a browser-only signal to be present and WRONG. Real local
29
+ * clients — curl, a bun `fetch` in an agent, a server-side proxy — send no
30
+ * `Origin` and no `Sec-Fetch-Site`, so they are untouched. A page served from
31
+ * the daemon itself is same-origin. A loopback SPA on ANOTHER port is
32
+ * `same-site` with a loopback `Origin`, and is allowed on purpose: the wall
33
+ * being defended is "not the local machine", not "not another port".
34
+ *
35
+ * This deliberately does NOT add a login. Loopback + owner-only remains the
36
+ * design for the daemons that use it; this only closes the hole where
37
+ * "loopback" silently included "every website the owner visits".
38
+ *
39
+ * ── Placement ───────────────────────────────────────────────────────────────
40
+ * Landed in station's own tree on 2026-08-06 and lifted here the same day, when
41
+ * orch (:3021) and cloud (:3022) turned out to need the identical guard —
42
+ * STANDARDS.md §1: three copies is one too many, and a request-header security
43
+ * check is not app-shaped. It sits beside `corsAllowlist` and `rateLimit`
44
+ * because it is the same kind of thing: a pure, header-only verdict about
45
+ * whether a request may be served.
46
+ */
47
+ export interface LocalOriginOpts {
48
+ /**
49
+ * Names the surface in the refusal text ("the machine API", "the orchestrator
50
+ * engine"). The owner reads these in a log line with no other context about
51
+ * which daemon spoke.
52
+ */
53
+ label?: string;
54
+ /**
55
+ * Extra EXACT hostnames that also mean this machine — a local host-header
56
+ * proxy's names (`cloud.localhost`). Matched whole, never as a suffix.
57
+ */
58
+ allowHosts?: readonly string[];
59
+ }
60
+ /**
61
+ * Why this request must be refused, or `null` to let it through.
62
+ *
63
+ * Pure and header-only so it is exhaustively testable without a socket. The
64
+ * caller decides the response; every current caller answers `403` with the
65
+ * reason as the body, and logs it — a silent refusal on a local daemon is
66
+ * indistinguishable from the daemon being down.
67
+ *
68
+ * @param opts see {@link LocalOriginOpts}. A bare string is accepted as the
69
+ * label, since that is the only thing most callers need.
70
+ */
71
+ export declare function crossOriginRefusal(req: Request, opts?: LocalOriginOpts | string): string | null;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The second lock on a LOOPBACK-ONLY HTTP daemon — the one binding to 127.0.0.1
3
+ * does not provide.
4
+ *
5
+ * ── The gap, measured 2026-08-06 against a live daemon ──────────────────────
6
+ * A machine-local daemon's whole trust model is "it only listens on 127.0.0.1",
7
+ * and for the NETWORK that is a real wall. For the owner's BROWSER it is not,
8
+ * for two reasons that have nothing to do with binding:
9
+ *
10
+ * 1. **CSRF.** Any page on any website can `POST http://127.0.0.1:<port>/...`.
11
+ * A cross-origin POST normally trips a CORS preflight the server would fail,
12
+ * but a preflight is only required for a NON-simple request — and
13
+ * `<form enctype="text/plain">` is simple, so it fires with no preflight at
14
+ * all. A body parser that calls `req.json()` parses it regardless of
15
+ * Content-Type, and the classic `text/plain` form trick smuggles valid JSON
16
+ * straight through. Verified by hand against the station daemon on :4747: a
17
+ * POST carrying `Content-Type: text/plain`, `Origin: https://evil.example.com`
18
+ * and `Sec-Fetch-Site: cross-site` was accepted, parsed, and reached field
19
+ * validation. The attacker cannot READ the reply (no CORS headers, so it
20
+ * stays opaque) — but destruction does not need a reply.
21
+ *
22
+ * 2. **DNS rebinding.** With no Host check, an attacker domain whose DNS flips
23
+ * to 127.0.0.1 becomes same-origin in the browser's eyes — which lifts the
24
+ * opaque-response limit above and turns every GET into an exfiltration
25
+ * channel.
26
+ *
27
+ * ── What this refuses, and why it costs nothing ─────────────────────────────
28
+ * A refusal needs a browser-only signal to be present and WRONG. Real local
29
+ * clients — curl, a bun `fetch` in an agent, a server-side proxy — send no
30
+ * `Origin` and no `Sec-Fetch-Site`, so they are untouched. A page served from
31
+ * the daemon itself is same-origin. A loopback SPA on ANOTHER port is
32
+ * `same-site` with a loopback `Origin`, and is allowed on purpose: the wall
33
+ * being defended is "not the local machine", not "not another port".
34
+ *
35
+ * This deliberately does NOT add a login. Loopback + owner-only remains the
36
+ * design for the daemons that use it; this only closes the hole where
37
+ * "loopback" silently included "every website the owner visits".
38
+ *
39
+ * ── Placement ───────────────────────────────────────────────────────────────
40
+ * Landed in station's own tree on 2026-08-06 and lifted here the same day, when
41
+ * orch (:3021) and cloud (:3022) turned out to need the identical guard —
42
+ * STANDARDS.md §1: three copies is one too many, and a request-header security
43
+ * check is not app-shaped. It sits beside `corsAllowlist` and `rateLimit`
44
+ * because it is the same kind of thing: a pure, header-only verdict about
45
+ * whether a request may be served.
46
+ */
47
+ /**
48
+ * Hosts that mean THIS machine. IPv6 arrives bracketed in a Host header.
49
+ *
50
+ * `extra` is for a daemon reached through a local host-header proxy under a
51
+ * name that is not itself a loopback literal — the dev gateway serves
52
+ * `cloud.localhost`, and note that the SUFFIX is deliberately not enough on its
53
+ * own (`localhost.evil.example.com` must stay refused), so each such name is
54
+ * listed exactly rather than pattern-matched.
55
+ */
56
+ function isLoopbackHost(hostname, extra) {
57
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, '');
58
+ return (h === 'localhost' ||
59
+ h === '::1' ||
60
+ h === '0:0:0:0:0:0:0:1' ||
61
+ // 127.0.0.0/8 — all of it is loopback, not just 127.0.0.1.
62
+ /^127(\.\d{1,3}){3}$/.test(h) ||
63
+ extra.has(h));
64
+ }
65
+ /** The hostname of a Host header value (`127.0.0.1:4747`, `[::1]:4747`). */
66
+ function hostnameOf(hostHeader) {
67
+ const trimmed = hostHeader.trim();
68
+ if (trimmed.startsWith('['))
69
+ return trimmed.slice(0, trimmed.indexOf(']') + 1);
70
+ const colon = trimmed.lastIndexOf(':');
71
+ return colon === -1 ? trimmed : trimmed.slice(0, colon);
72
+ }
73
+ /**
74
+ * Why this request must be refused, or `null` to let it through.
75
+ *
76
+ * Pure and header-only so it is exhaustively testable without a socket. The
77
+ * caller decides the response; every current caller answers `403` with the
78
+ * reason as the body, and logs it — a silent refusal on a local daemon is
79
+ * indistinguishable from the daemon being down.
80
+ *
81
+ * @param opts see {@link LocalOriginOpts}. A bare string is accepted as the
82
+ * label, since that is the only thing most callers need.
83
+ */
84
+ export function crossOriginRefusal(req, opts = {}) {
85
+ const { label = 'this daemon', allowHosts = [] } = typeof opts === 'string' ? { label: opts } : opts;
86
+ const extra = new Set(allowHosts.map((h) => h.toLowerCase()));
87
+ // ── DNS rebinding: the Host must name this machine ──────────────────────
88
+ // Checked first: a rebound Host is the case where the OTHER signals start
89
+ // looking legitimately same-origin, so it has to fail before they are read.
90
+ const host = req.headers.get('host');
91
+ if (host && !isLoopbackHost(hostnameOf(host), extra)) {
92
+ return `Host "${host}" is not a loopback name — ${label} answers only to 127.0.0.1/localhost`;
93
+ }
94
+ // ── CSRF: a browser told us where this came from; believe it ─────────────
95
+ const origin = req.headers.get('origin');
96
+ if (origin && origin !== 'null') {
97
+ let originHost;
98
+ try {
99
+ originHost = new URL(origin).hostname;
100
+ }
101
+ catch {
102
+ return `unparseable Origin "${origin}"`;
103
+ }
104
+ if (!isLoopbackHost(originHost, extra)) {
105
+ return `cross-origin request from "${origin}" — ${label} is local-only`;
106
+ }
107
+ }
108
+ // `same-site` is deliberately allowed: a loopback SPA on another port is
109
+ // cross-ORIGIN but same-SITE, and it is a legitimate local caller. Anything
110
+ // genuinely external reports `cross-site` and never reaches here anyway,
111
+ // because its Origin failed the loopback test above — this is the belt to
112
+ // that braces, for a browser that sends Sec-Fetch-Site but omits Origin
113
+ // (which is what a cross-site GET navigation does).
114
+ const site = req.headers.get('sec-fetch-site');
115
+ if (site && !['same-origin', 'same-site', 'none'].includes(site.toLowerCase())) {
116
+ return `Sec-Fetch-Site: ${site} — ${label} does not serve other sites`;
117
+ }
118
+ return null;
119
+ }
@@ -0,0 +1,56 @@
1
+ import type { Context, MiddlewareHandler } from 'hono';
2
+ /** How much of the forwarded-header chain, if any, this server may believe. */
3
+ export interface ClientIpOptions {
4
+ /**
5
+ * How many TRUSTED reverse proxies sit in front of this server.
6
+ *
7
+ * `0` (the default) means none: use the socket peer and ignore forwarded
8
+ * headers entirely. Set it to the real number of hops you control (nginx in
9
+ * front of Bun ⇒ `1`).
10
+ */
11
+ trustedProxyHops?: number;
12
+ }
13
+ /**
14
+ * The client IP for rate-limiting / logging.
15
+ *
16
+ * `X-Forwarded-For` is written by whoever is calling — a client can send any
17
+ * value it likes. Reading it unconditionally means an attacker gets a NEW
18
+ * rate-limit bucket on every request simply by varying the header, which
19
+ * silently removes the protection the limiter exists to provide. So forwarded
20
+ * headers are IGNORED unless you declare how many proxies you actually trust.
21
+ *
22
+ * When you do, the chain is read RIGHT-to-LEFT. A proxy appends the peer it saw
23
+ * to any existing header (nginx's `$proxy_add_x_forwarded_for`), so with one
24
+ * trusted hop the last entry is the address YOUR proxy observed, while
25
+ * everything to its left is caller-supplied and unverifiable. Taking the first
26
+ * entry — the previous behavior — is taking exactly the part an attacker
27
+ * controls.
28
+ *
29
+ * Falls back to `'unknown'` when no connection info is available (e.g.
30
+ * `app.fetch(new Request(...))` in tests).
31
+ */
32
+ export declare function clientIp(c: Context, options?: ClientIpOptions): string;
33
+ export interface RateLimitOpts {
34
+ /** Max requests allowed per window per key. */
35
+ limit: number;
36
+ /** Sliding window length in ms. */
37
+ windowMs: number;
38
+ /** 429 body message. */
39
+ message?: string;
40
+ /** Key function (default: client IP). Use to throttle per authenticated user. */
41
+ keyFn?: (c: Context) => string;
42
+ /**
43
+ * How many trusted reverse proxies sit in front of this server, for the
44
+ * default IP key. Default `0` — forwarded headers are ignored, because a
45
+ * caller can forge them to mint a fresh bucket per request. See
46
+ * {@link ClientIpOptions}.
47
+ */
48
+ trustedProxyHops?: number;
49
+ }
50
+ /**
51
+ * A sliding-window, in-memory rate limiter as Hono middleware. Keeps a timestamp log
52
+ * per key, prunes entries older than the window on access, and a cheap periodic
53
+ * sweep drops idle keys (no dangling timers). On limit: 429 + `Retry-After` + the
54
+ * canonical `{ error }` envelope. Used by auth routes (task 26) + vault unlock.
55
+ */
56
+ export declare function createRateLimiter(opts: RateLimitOpts): MiddlewareHandler;
@@ -0,0 +1,86 @@
1
+ import { getConnInfo } from 'hono/bun';
2
+ import { ApiError, toErrorEnvelope } from '../errors';
3
+ /**
4
+ * The client IP for rate-limiting / logging.
5
+ *
6
+ * `X-Forwarded-For` is written by whoever is calling — a client can send any
7
+ * value it likes. Reading it unconditionally means an attacker gets a NEW
8
+ * rate-limit bucket on every request simply by varying the header, which
9
+ * silently removes the protection the limiter exists to provide. So forwarded
10
+ * headers are IGNORED unless you declare how many proxies you actually trust.
11
+ *
12
+ * When you do, the chain is read RIGHT-to-LEFT. A proxy appends the peer it saw
13
+ * to any existing header (nginx's `$proxy_add_x_forwarded_for`), so with one
14
+ * trusted hop the last entry is the address YOUR proxy observed, while
15
+ * everything to its left is caller-supplied and unverifiable. Taking the first
16
+ * entry — the previous behavior — is taking exactly the part an attacker
17
+ * controls.
18
+ *
19
+ * Falls back to `'unknown'` when no connection info is available (e.g.
20
+ * `app.fetch(new Request(...))` in tests).
21
+ */
22
+ export function clientIp(c, options = {}) {
23
+ const hops = Math.max(0, Math.trunc(options.trustedProxyHops ?? 0));
24
+ if (hops > 0) {
25
+ const chain = (c.req.header('x-forwarded-for') ?? '')
26
+ .split(',')
27
+ .map((part) => part.trim())
28
+ .filter(Boolean);
29
+ // The entry your own trusted hop observed. If the chain is shorter than the
30
+ // configured hop count the client sent fewer entries than expected — fall
31
+ // through to the socket rather than believing the leftmost value.
32
+ const trusted = chain[chain.length - hops];
33
+ if (trusted)
34
+ return trusted;
35
+ // `x-real-ip` is single-valued and set by the same trusted proxy.
36
+ const xri = c.req.header('x-real-ip');
37
+ if (xri?.trim())
38
+ return xri.trim();
39
+ }
40
+ try {
41
+ return getConnInfo(c).remote.address ?? 'unknown';
42
+ }
43
+ catch {
44
+ return 'unknown';
45
+ }
46
+ }
47
+ /**
48
+ * A sliding-window, in-memory rate limiter as Hono middleware. Keeps a timestamp log
49
+ * per key, prunes entries older than the window on access, and a cheap periodic
50
+ * sweep drops idle keys (no dangling timers). On limit: 429 + `Retry-After` + the
51
+ * canonical `{ error }` envelope. Used by auth routes (task 26) + vault unlock.
52
+ */
53
+ export function createRateLimiter(opts) {
54
+ const { limit, windowMs, message = 'Too many requests. Please try again later.', keyFn, trustedProxyHops = 0, } = opts;
55
+ const hits = new Map();
56
+ let lastSweep = Date.now();
57
+ const sweep = (now) => {
58
+ const cutoff = now - windowMs;
59
+ for (const [key, times] of hits) {
60
+ const live = times.filter((t) => t > cutoff);
61
+ if (live.length === 0)
62
+ hits.delete(key);
63
+ else
64
+ hits.set(key, live);
65
+ }
66
+ lastSweep = now;
67
+ };
68
+ return async (c, next) => {
69
+ const key = keyFn ? keyFn(c) : clientIp(c, { trustedProxyHops });
70
+ const now = Date.now();
71
+ const cutoff = now - windowMs;
72
+ // Periodically evict idle keys so the map can't grow unbounded.
73
+ if (now - lastSweep > windowMs)
74
+ sweep(now);
75
+ const times = (hits.get(key) ?? []).filter((t) => t > cutoff);
76
+ if (times.length >= limit) {
77
+ hits.set(key, times);
78
+ const retryMs = (times[0] ?? now) + windowMs - now;
79
+ c.header('Retry-After', String(Math.max(1, Math.ceil(retryMs / 1000))));
80
+ return c.json(toErrorEnvelope(new ApiError(429, message, { code: 'rate_limited' })), 429);
81
+ }
82
+ times.push(now);
83
+ hits.set(key, times);
84
+ await next();
85
+ };
86
+ }
@@ -0,0 +1,29 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { MiddlewareHandler } from 'hono';
3
+ import type { CursedbeltEnv } from '../context';
4
+ import { type MetricsBuffer } from '../metrics/metricsBuffer';
5
+ /**
6
+ * Structured request/response logging. After each request it records one slim
7
+ * `request_metrics` row (via the batched {@link MetricsBuffer}, off the hot path)
8
+ * and, for errors (status ≥ 400) or slow requests, one `event_logs` row tagged with
9
+ * the correlation id. Attributes rows to `c.get('userId')` when the app's auth
10
+ * middleware has set it.
11
+ *
12
+ * It records metrics even when a downstream handler THROWS: the error is caught,
13
+ * the would-be status derived, the row written, then the error re-thrown so the
14
+ * error-envelope `onError` still produces the response.
15
+ */
16
+ export interface RequestLoggerOpts {
17
+ /**
18
+ * The metrics buffer to write through. Provide one (and own its lifecycle via
19
+ * `buffer.stop()`) to share it / flush deterministically; omit to create one from
20
+ * `db`. Its flush timer is `unref()`'d, so an internally-created buffer is fine for
21
+ * long-running servers.
22
+ */
23
+ buffer?: MetricsBuffer;
24
+ /** Duration (ms) at/above which a 2xx request also logs an `event_logs` row. Default: 2000. */
25
+ slowMs?: number;
26
+ /** Also write `event_logs` rows for status ≥ 400. Default: true. */
27
+ logErrors?: boolean;
28
+ }
29
+ export declare function requestLogger(db: Database, opts?: RequestLoggerOpts): MiddlewareHandler<CursedbeltEnv>;
@@ -0,0 +1,47 @@
1
+ import { HTTPException } from 'hono/http-exception';
2
+ import { isApiError } from '../errors';
3
+ import { createMetricsBuffer } from '../metrics/metricsBuffer';
4
+ import { normalizeRoute } from '../metrics/normalizeRoute';
5
+ function statusOfThrown(err) {
6
+ if (isApiError(err))
7
+ return err.status;
8
+ if (err instanceof HTTPException)
9
+ return err.status;
10
+ return 500;
11
+ }
12
+ export function requestLogger(db, opts = {}) {
13
+ const buffer = opts.buffer ?? createMetricsBuffer({ db });
14
+ const slowMs = opts.slowMs ?? 2000;
15
+ const logErrors = opts.logErrors ?? true;
16
+ const insertEvent = db.prepare(`INSERT INTO event_logs
17
+ (id, correlation_id, event_name, type, message, details, duration_ms, user_id, created_at)
18
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
19
+ return async (c, next) => {
20
+ const start = performance.now();
21
+ let thrown;
22
+ let didThrow = false;
23
+ try {
24
+ await next();
25
+ }
26
+ catch (err) {
27
+ thrown = err;
28
+ didThrow = true;
29
+ }
30
+ const durationMs = performance.now() - start;
31
+ const { method, route } = normalizeRoute(c);
32
+ const status = didThrow ? statusOfThrown(thrown) : c.res.status;
33
+ const lenHeader = didThrow ? null : c.res.headers.get('content-length');
34
+ const bytesOut = lenHeader ? Number(lenHeader) : null;
35
+ const userId = c.get('userId') ?? null;
36
+ buffer.record({ ts: Date.now(), method, route, status, durationMs, bytesOut, userId });
37
+ const isError = status >= 400;
38
+ const isSlow = durationMs >= slowMs;
39
+ if ((logErrors && isError) || isSlow) {
40
+ const type = status >= 500 ? 'error' : isError ? 'warn' : 'info';
41
+ insertEvent.run(crypto.randomUUID(), c.get('correlationId') ?? null, isError ? 'http_error' : 'http_slow', type, `${method} ${route} → ${status}`, JSON.stringify({ method, route, status, durationMs: Math.round(durationMs), bytesOut }), Math.round(durationMs), userId, new Date().toISOString());
42
+ }
43
+ // Re-throw so the error-envelope onError still builds the response.
44
+ if (didThrow)
45
+ throw thrown;
46
+ };
47
+ }
@@ -0,0 +1,29 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import { secureHeaders } from 'hono/secure-headers';
3
+ /**
4
+ * Security headers, built on Hono's `secureHeaders` (helmet-equivalent) rather than
5
+ * a hand-rolled header pass — robust + maintained, per the "prefer configured
6
+ * libraries" mandate. cursedbelt supplies sensible cross-app defaults (strict CSP,
7
+ * `X-Frame-Options: DENY`, COOP, HSTS in production, …); the consuming app extends
8
+ * just the directives it needs (CDN hosts, embed frames) via {@link SecurityHeadersOpts}.
9
+ */
10
+ type SecureHeadersOptions = NonNullable<Parameters<typeof secureHeaders>[0]>;
11
+ export type CspDirectives = NonNullable<SecureHeadersOptions['contentSecurityPolicy']>;
12
+ export interface SecurityHeadersOpts {
13
+ /**
14
+ * Extend/override the default CSP directives. Shallow-merged per directive, so a
15
+ * supplied directive REPLACES that default's value (pass the full list). Set to
16
+ * `false` to omit the CSP header entirely.
17
+ */
18
+ csp?: CspDirectives | false;
19
+ /**
20
+ * `Strict-Transport-Security` value. Default: enabled in production
21
+ * (`NODE_ENV==='production'`) as `max-age=31536000; includeSubDomains`, off
22
+ * otherwise. Pass a string to force a value, or `false` to disable.
23
+ */
24
+ hsts?: boolean | string;
25
+ /** Escape hatch: any other `secureHeaders` option, merged last (wins). */
26
+ overrides?: Partial<SecureHeadersOptions>;
27
+ }
28
+ export declare function securityHeaders(opts?: SecurityHeadersOpts): MiddlewareHandler;
29
+ export {};
@@ -0,0 +1,44 @@
1
+ import { secureHeaders } from 'hono/secure-headers';
2
+ // A strict baseline that works for a same-origin SPA + JSON API. Apps widen the
3
+ // few directives they need (script/img/connect hosts, frame embeds) via `opts.csp`.
4
+ const DEFAULT_CSP = {
5
+ defaultSrc: ["'self'"],
6
+ baseUri: ["'self'"],
7
+ objectSrc: ["'none'"],
8
+ frameAncestors: ["'none'"],
9
+ formAction: ["'self'"],
10
+ scriptSrc: ["'self'"],
11
+ scriptSrcAttr: ["'none'"],
12
+ // Inline styles are needed for Tailwind / chart / drag transforms; inline SCRIPTS
13
+ // stay blocked (the real XSS barrier).
14
+ styleSrc: ["'self'", "'unsafe-inline'"],
15
+ imgSrc: ["'self'", 'data:', 'blob:'],
16
+ fontSrc: ["'self'", 'data:'],
17
+ connectSrc: ["'self'"],
18
+ upgradeInsecureRequests: [],
19
+ };
20
+ export function securityHeaders(opts = {}) {
21
+ // A runtime escape hatch matching ca's ops convention: CSP_DISABLED=true drops CSP.
22
+ const cspOff = opts.csp === false || process.env.CSP_DISABLED === 'true';
23
+ const csp = cspOff ? undefined : { ...DEFAULT_CSP, ...(opts.csp ?? {}) };
24
+ const isProd = process.env.NODE_ENV === 'production';
25
+ const hsts = opts.hsts ?? (isProd ? 'max-age=31536000; includeSubDomains' : false);
26
+ return secureHeaders({
27
+ contentSecurityPolicy: csp,
28
+ xContentTypeOptions: 'nosniff',
29
+ // DENY (not SAMEORIGIN) to agree with CSP `frame-ancestors 'none'`.
30
+ xFrameOptions: 'DENY',
31
+ referrerPolicy: 'strict-origin-when-cross-origin',
32
+ crossOriginOpenerPolicy: 'same-origin',
33
+ // We serve cross-origin media/fonts, so keep CORP permissive.
34
+ crossOriginResourcePolicy: 'cross-origin',
35
+ // COEP breaks third-party embeds (YouTube, CDNs) — off by default.
36
+ crossOriginEmbedderPolicy: false,
37
+ originAgentCluster: true,
38
+ xDnsPrefetchControl: 'off',
39
+ xPermittedCrossDomainPolicies: 'none',
40
+ strictTransportSecurity: hsts,
41
+ removePoweredBy: true,
42
+ ...opts.overrides,
43
+ });
44
+ }
@@ -0,0 +1,29 @@
1
+ import type { Env, MiddlewareHandler } from 'hono';
2
+ import type { ZodType, z } from 'zod';
3
+ import type { CursedbeltEnv } from '../context';
4
+ /** The request locations a schema can validate. Mirrors Hono's body/non-body split. */
5
+ export type ZValidatorTarget = 'json' | 'form' | 'query' | 'param';
6
+ /**
7
+ * Validate a request `target` against a zod `schema`. Mount per route ahead of the
8
+ * handler:
9
+ *
10
+ * ```ts
11
+ * register('post', '/notes', zValidator(CreateNoteSchema), (c) => {
12
+ * const body = c.req.valid('json'); // typed z.infer<typeof CreateNoteSchema>
13
+ * // …
14
+ * });
15
+ * ```
16
+ *
17
+ * On failure responds `400` with the canonical envelope (field errors under
18
+ * `context.issues`); never throws, so it produces the same body whether or not the
19
+ * app mounts the {@link errorEnvelope} `onError` handler. On success the parsed value
20
+ * is available as both `c.req.valid(target)` and `c.get('validatedBody')`.
21
+ */
22
+ export declare function zValidator<Schema extends ZodType, Target extends ZValidatorTarget = 'json', E extends Env = CursedbeltEnv, P extends string = string>(schema: Schema, target?: Target): MiddlewareHandler<E, P, {
23
+ in: {
24
+ [K in Target]: z.input<Schema>;
25
+ };
26
+ out: {
27
+ [K in Target]: z.output<Schema>;
28
+ };
29
+ }>;