palaryn 0.1.0 → 0.3.2

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 (344) hide show
  1. package/README.md +243 -588
  2. package/dist/sdk/typescript/src/client.js +2 -2
  3. package/dist/sdk/typescript/src/client.js.map +1 -1
  4. package/dist/src/anomaly/detector.d.ts +7 -4
  5. package/dist/src/anomaly/detector.d.ts.map +1 -1
  6. package/dist/src/anomaly/detector.js +22 -12
  7. package/dist/src/anomaly/detector.js.map +1 -1
  8. package/dist/src/audit/logger.d.ts +10 -0
  9. package/dist/src/audit/logger.d.ts.map +1 -1
  10. package/dist/src/audit/logger.js +52 -38
  11. package/dist/src/audit/logger.js.map +1 -1
  12. package/dist/src/auth/routes.d.ts.map +1 -1
  13. package/dist/src/auth/routes.js +35 -0
  14. package/dist/src/auth/routes.js.map +1 -1
  15. package/dist/src/budget/manager.d.ts +5 -0
  16. package/dist/src/budget/manager.d.ts.map +1 -1
  17. package/dist/src/budget/manager.js +32 -0
  18. package/dist/src/budget/manager.js.map +1 -1
  19. package/dist/src/budget/model-pricing.d.ts +20 -0
  20. package/dist/src/budget/model-pricing.d.ts.map +1 -0
  21. package/dist/src/budget/model-pricing.js +107 -0
  22. package/dist/src/budget/model-pricing.js.map +1 -0
  23. package/dist/src/budget/usage-extractor.d.ts +3 -1
  24. package/dist/src/budget/usage-extractor.d.ts.map +1 -1
  25. package/dist/src/budget/usage-extractor.js +47 -3
  26. package/dist/src/budget/usage-extractor.js.map +1 -1
  27. package/dist/src/config/defaults.d.ts.map +1 -1
  28. package/dist/src/config/defaults.js +65 -13
  29. package/dist/src/config/defaults.js.map +1 -1
  30. package/dist/src/dlp/tool-patterns.d.ts +7 -0
  31. package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
  32. package/dist/src/dlp/tool-patterns.js +34 -0
  33. package/dist/src/dlp/tool-patterns.js.map +1 -0
  34. package/dist/src/executor/filesystem-executor.d.ts +28 -0
  35. package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
  36. package/dist/src/executor/filesystem-executor.js +192 -0
  37. package/dist/src/executor/filesystem-executor.js.map +1 -0
  38. package/dist/src/executor/http-executor.d.ts.map +1 -1
  39. package/dist/src/executor/http-executor.js +22 -2
  40. package/dist/src/executor/http-executor.js.map +1 -1
  41. package/dist/src/executor/index.d.ts +4 -0
  42. package/dist/src/executor/index.d.ts.map +1 -1
  43. package/dist/src/executor/index.js +9 -1
  44. package/dist/src/executor/index.js.map +1 -1
  45. package/dist/src/executor/shell-executor.d.ts +22 -0
  46. package/dist/src/executor/shell-executor.d.ts.map +1 -0
  47. package/dist/src/executor/shell-executor.js +119 -0
  48. package/dist/src/executor/shell-executor.js.map +1 -0
  49. package/dist/src/executor/sql-executor.d.ts +29 -0
  50. package/dist/src/executor/sql-executor.d.ts.map +1 -0
  51. package/dist/src/executor/sql-executor.js +114 -0
  52. package/dist/src/executor/sql-executor.js.map +1 -0
  53. package/dist/src/executor/websocket-executor.d.ts +26 -0
  54. package/dist/src/executor/websocket-executor.d.ts.map +1 -0
  55. package/dist/src/executor/websocket-executor.js +205 -0
  56. package/dist/src/executor/websocket-executor.js.map +1 -0
  57. package/dist/src/interceptor/index.d.ts +2 -0
  58. package/dist/src/interceptor/index.d.ts.map +1 -0
  59. package/dist/src/interceptor/index.js +6 -0
  60. package/dist/src/interceptor/index.js.map +1 -0
  61. package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
  62. package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
  63. package/dist/src/interceptor/provider-interceptor.js +302 -0
  64. package/dist/src/interceptor/provider-interceptor.js.map +1 -0
  65. package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
  66. package/dist/src/mcp/auth-verifier.js +3 -2
  67. package/dist/src/mcp/auth-verifier.js.map +1 -1
  68. package/dist/src/mcp/bridge.d.ts +14 -10
  69. package/dist/src/mcp/bridge.d.ts.map +1 -1
  70. package/dist/src/mcp/bridge.js +51 -227
  71. package/dist/src/mcp/bridge.js.map +1 -1
  72. package/dist/src/mcp/http-transport.d.ts +2 -0
  73. package/dist/src/mcp/http-transport.d.ts.map +1 -1
  74. package/dist/src/mcp/http-transport.js +117 -66
  75. package/dist/src/mcp/http-transport.js.map +1 -1
  76. package/dist/src/mcp/internal-auth.d.ts +13 -0
  77. package/dist/src/mcp/internal-auth.d.ts.map +1 -0
  78. package/dist/src/mcp/internal-auth.js +12 -0
  79. package/dist/src/mcp/internal-auth.js.map +1 -0
  80. package/dist/src/mcp/tool-definitions.d.ts +41 -0
  81. package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
  82. package/dist/src/mcp/tool-definitions.js +491 -0
  83. package/dist/src/mcp/tool-definitions.js.map +1 -0
  84. package/dist/src/middleware/auth.js.map +1 -1
  85. package/dist/src/middleware/session.js.map +1 -1
  86. package/dist/src/middleware/validate.d.ts +8 -0
  87. package/dist/src/middleware/validate.d.ts.map +1 -1
  88. package/dist/src/middleware/validate.js +45 -0
  89. package/dist/src/middleware/validate.js.map +1 -1
  90. package/dist/src/policy/engine.d.ts +4 -0
  91. package/dist/src/policy/engine.d.ts.map +1 -1
  92. package/dist/src/policy/engine.js +117 -0
  93. package/dist/src/policy/engine.js.map +1 -1
  94. package/dist/src/saas/routes.d.ts.map +1 -1
  95. package/dist/src/saas/routes.js +355 -22
  96. package/dist/src/saas/routes.js.map +1 -1
  97. package/dist/src/server/app.d.ts.map +1 -1
  98. package/dist/src/server/app.js +24 -3
  99. package/dist/src/server/app.js.map +1 -1
  100. package/dist/src/server/gateway.d.ts.map +1 -1
  101. package/dist/src/server/gateway.js +17 -0
  102. package/dist/src/server/gateway.js.map +1 -1
  103. package/dist/src/server/index.d.ts.map +1 -1
  104. package/dist/src/server/index.js +18 -0
  105. package/dist/src/server/index.js.map +1 -1
  106. package/dist/src/storage/interfaces.d.ts +14 -3
  107. package/dist/src/storage/interfaces.d.ts.map +1 -1
  108. package/dist/src/storage/memory.d.ts +2 -0
  109. package/dist/src/storage/memory.d.ts.map +1 -1
  110. package/dist/src/storage/memory.js +6 -0
  111. package/dist/src/storage/memory.js.map +1 -1
  112. package/dist/src/storage/postgres.d.ts +5 -0
  113. package/dist/src/storage/postgres.d.ts.map +1 -1
  114. package/dist/src/storage/postgres.js +16 -0
  115. package/dist/src/storage/postgres.js.map +1 -1
  116. package/dist/src/storage/redis.d.ts +10 -0
  117. package/dist/src/storage/redis.d.ts.map +1 -1
  118. package/dist/src/storage/redis.js +65 -0
  119. package/dist/src/storage/redis.js.map +1 -1
  120. package/dist/src/types/budget.d.ts +4 -0
  121. package/dist/src/types/budget.d.ts.map +1 -1
  122. package/dist/src/types/config.d.ts +58 -0
  123. package/dist/src/types/config.d.ts.map +1 -1
  124. package/dist/src/types/events.d.ts +1 -0
  125. package/dist/src/types/events.d.ts.map +1 -1
  126. package/dist/src/types/policy.d.ts +11 -1
  127. package/dist/src/types/policy.d.ts.map +1 -1
  128. package/dist/src/types/tool-result.d.ts +11 -0
  129. package/dist/src/types/tool-result.d.ts.map +1 -1
  130. package/dist/tests/unit/app-routes.test.d.ts +2 -0
  131. package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
  132. package/dist/tests/unit/app-routes.test.js +715 -0
  133. package/dist/tests/unit/app-routes.test.js.map +1 -0
  134. package/dist/tests/unit/audit-logger.test.js +105 -0
  135. package/dist/tests/unit/audit-logger.test.js.map +1 -1
  136. package/dist/tests/unit/auth-providers.test.d.ts +2 -0
  137. package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
  138. package/dist/tests/unit/auth-providers.test.js +279 -0
  139. package/dist/tests/unit/auth-providers.test.js.map +1 -0
  140. package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
  141. package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
  142. package/dist/tests/unit/auth-routes-extended.test.js +993 -0
  143. package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
  144. package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
  145. package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
  146. package/dist/tests/unit/auth-verifier.test.js +505 -0
  147. package/dist/tests/unit/auth-verifier.test.js.map +1 -0
  148. package/dist/tests/unit/billing-routes.test.d.ts +2 -0
  149. package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
  150. package/dist/tests/unit/billing-routes.test.js +432 -0
  151. package/dist/tests/unit/billing-routes.test.js.map +1 -0
  152. package/dist/tests/unit/config-defaults.test.d.ts +2 -0
  153. package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
  154. package/dist/tests/unit/config-defaults.test.js +119 -0
  155. package/dist/tests/unit/config-defaults.test.js.map +1 -0
  156. package/dist/tests/unit/defaults.test.js +0 -10
  157. package/dist/tests/unit/defaults.test.js.map +1 -1
  158. package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
  159. package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
  160. package/dist/tests/unit/filesystem-executor.test.js +280 -0
  161. package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
  162. package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
  163. package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
  164. package/dist/tests/unit/gateway-branches.test.js +1039 -0
  165. package/dist/tests/unit/gateway-branches.test.js.map +1 -0
  166. package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
  167. package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
  168. package/dist/tests/unit/http-executor-branches.test.js +495 -0
  169. package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
  170. package/dist/tests/unit/logger.test.d.ts +2 -0
  171. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  172. package/dist/tests/unit/logger.test.js +97 -0
  173. package/dist/tests/unit/logger.test.js.map +1 -0
  174. package/dist/tests/unit/mcp-internal-auth.test.d.ts +2 -0
  175. package/dist/tests/unit/mcp-internal-auth.test.d.ts.map +1 -0
  176. package/dist/tests/unit/mcp-internal-auth.test.js +445 -0
  177. package/dist/tests/unit/mcp-internal-auth.test.js.map +1 -0
  178. package/dist/tests/unit/metrics.test.js +102 -0
  179. package/dist/tests/unit/metrics.test.js.map +1 -1
  180. package/dist/tests/unit/model-pricing.test.d.ts +2 -0
  181. package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
  182. package/dist/tests/unit/model-pricing.test.js +87 -0
  183. package/dist/tests/unit/model-pricing.test.js.map +1 -0
  184. package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
  185. package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
  186. package/dist/tests/unit/oauth-stores.test.js +260 -0
  187. package/dist/tests/unit/oauth-stores.test.js.map +1 -0
  188. package/dist/tests/unit/policy-engine.test.js +466 -0
  189. package/dist/tests/unit/policy-engine.test.js.map +1 -1
  190. package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
  191. package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
  192. package/dist/tests/unit/provider-interceptor.test.js +472 -0
  193. package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
  194. package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
  195. package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
  196. package/dist/tests/unit/saas-routes-branches.test.js +2165 -0
  197. package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
  198. package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
  199. package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
  200. package/dist/tests/unit/saas-routes-crud.test.js +332 -0
  201. package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
  202. package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
  203. package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
  204. package/dist/tests/unit/saas-routes-data.test.js +405 -0
  205. package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
  206. package/dist/tests/unit/saas-routes.test.js +3 -3
  207. package/dist/tests/unit/saas-routes.test.js.map +1 -1
  208. package/dist/tests/unit/shell-executor.test.d.ts +2 -0
  209. package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
  210. package/dist/tests/unit/shell-executor.test.js +145 -0
  211. package/dist/tests/unit/shell-executor.test.js.map +1 -0
  212. package/dist/tests/unit/sql-executor.test.d.ts +2 -0
  213. package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
  214. package/dist/tests/unit/sql-executor.test.js +177 -0
  215. package/dist/tests/unit/sql-executor.test.js.map +1 -0
  216. package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
  217. package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
  218. package/dist/tests/unit/stream-proxy.test.js +147 -0
  219. package/dist/tests/unit/stream-proxy.test.js.map +1 -0
  220. package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
  221. package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
  222. package/dist/tests/unit/tool-definitions.test.js +184 -0
  223. package/dist/tests/unit/tool-definitions.test.js.map +1 -0
  224. package/dist/tests/unit/usage-extractor.test.js +140 -0
  225. package/dist/tests/unit/usage-extractor.test.js.map +1 -1
  226. package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
  227. package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
  228. package/dist/tests/unit/webhook-handler.test.js +453 -0
  229. package/dist/tests/unit/webhook-handler.test.js.map +1 -0
  230. package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
  231. package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
  232. package/dist/tests/unit/webhook-routes.test.js +69 -0
  233. package/dist/tests/unit/webhook-routes.test.js.map +1 -0
  234. package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
  235. package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
  236. package/dist/tests/unit/websocket-executor.test.js +121 -0
  237. package/dist/tests/unit/websocket-executor.test.js.map +1 -0
  238. package/package.json +8 -2
  239. package/policy-packs/demo_fail.yaml +41 -0
  240. package/policy-packs/full_tools.yaml +136 -0
  241. package/src/admin/index.ts +1 -0
  242. package/src/admin/routes.ts +509 -0
  243. package/src/admin/templates.ts +572 -0
  244. package/src/anomaly/detector.ts +730 -0
  245. package/src/anomaly/index.ts +1 -0
  246. package/src/approval/manager.ts +569 -0
  247. package/src/approval/webhook.ts +133 -0
  248. package/src/audit/logger.ts +490 -0
  249. package/src/auth/index.ts +5 -0
  250. package/src/auth/password.ts +21 -0
  251. package/src/auth/pkce.ts +22 -0
  252. package/src/auth/providers.ts +208 -0
  253. package/src/auth/routes.ts +561 -0
  254. package/src/auth/session.ts +84 -0
  255. package/src/billing/index.ts +6 -0
  256. package/src/billing/plan-enforcer.ts +135 -0
  257. package/src/billing/routes.ts +229 -0
  258. package/src/billing/stripe-client.ts +58 -0
  259. package/src/billing/webhook-handler.ts +182 -0
  260. package/src/billing/webhook-routes.ts +28 -0
  261. package/src/budget/manager.ts +679 -0
  262. package/src/budget/model-pricing.ts +119 -0
  263. package/src/budget/usage-extractor.ts +214 -0
  264. package/src/cli.ts +91 -0
  265. package/src/config/defaults.ts +261 -0
  266. package/src/config/validate.ts +88 -0
  267. package/src/dlp/composite-scanner.ts +213 -0
  268. package/src/dlp/index.ts +9 -0
  269. package/src/dlp/interfaces.ts +34 -0
  270. package/src/dlp/patterns.ts +30 -0
  271. package/src/dlp/prompt-injection-backend.ts +181 -0
  272. package/src/dlp/prompt-injection-patterns.ts +302 -0
  273. package/src/dlp/regex-backend.ts +181 -0
  274. package/src/dlp/scanner.ts +502 -0
  275. package/src/dlp/text-normalizer.ts +225 -0
  276. package/src/dlp/tool-patterns.ts +35 -0
  277. package/src/dlp/trufflehog-backend.ts +190 -0
  278. package/src/executor/filesystem-executor.ts +196 -0
  279. package/src/executor/http-executor.ts +349 -0
  280. package/src/executor/index.ts +9 -0
  281. package/src/executor/interfaces.ts +11 -0
  282. package/src/executor/noop-executor.ts +23 -0
  283. package/src/executor/registry.ts +64 -0
  284. package/src/executor/shell-executor.ts +148 -0
  285. package/src/executor/slack-executor.ts +176 -0
  286. package/src/executor/sql-executor.ts +146 -0
  287. package/src/executor/websocket-executor.ts +211 -0
  288. package/src/index.ts +24 -0
  289. package/src/interceptor/index.ts +1 -0
  290. package/src/interceptor/provider-interceptor.ts +315 -0
  291. package/src/mcp/auth-verifier.ts +152 -0
  292. package/src/mcp/bridge.ts +703 -0
  293. package/src/mcp/http-transport.ts +698 -0
  294. package/src/mcp/index.ts +9 -0
  295. package/src/mcp/internal-auth.ts +14 -0
  296. package/src/mcp/oauth-pages.ts +139 -0
  297. package/src/mcp/oauth-postgres-stores.ts +278 -0
  298. package/src/mcp/oauth-provider.ts +536 -0
  299. package/src/mcp/oauth-stores.ts +202 -0
  300. package/src/mcp/server.ts +55 -0
  301. package/src/mcp/tool-definitions.ts +562 -0
  302. package/src/metrics/collector.ts +357 -0
  303. package/src/metrics/index.ts +1 -0
  304. package/src/middleware/auth.ts +814 -0
  305. package/src/middleware/session.ts +85 -0
  306. package/src/middleware/validate.ts +130 -0
  307. package/src/policy/engine.ts +815 -0
  308. package/src/policy/index.ts +2 -0
  309. package/src/policy/opa-engine.ts +829 -0
  310. package/src/proxy/forward-proxy.ts +649 -0
  311. package/src/proxy/index.ts +1 -0
  312. package/src/ratelimit/limiter.ts +196 -0
  313. package/src/replay/engine.ts +142 -0
  314. package/src/replay/index.ts +1 -0
  315. package/src/saas/index.ts +1 -0
  316. package/src/saas/routes.ts +2178 -0
  317. package/src/server/app.ts +985 -0
  318. package/src/server/errors.ts +49 -0
  319. package/src/server/gateway.ts +1130 -0
  320. package/src/server/index.ts +307 -0
  321. package/src/server/logger.ts +255 -0
  322. package/src/server/stream-proxy.ts +202 -0
  323. package/src/storage/file-persistence.ts +315 -0
  324. package/src/storage/index.ts +4 -0
  325. package/src/storage/interfaces.ts +287 -0
  326. package/src/storage/memory.ts +686 -0
  327. package/src/storage/postgres.ts +1831 -0
  328. package/src/storage/redis.ts +835 -0
  329. package/src/tracing/index.ts +1 -0
  330. package/src/tracing/provider.ts +100 -0
  331. package/src/trust/calculator.ts +141 -0
  332. package/src/trust/index.ts +7 -0
  333. package/src/types/budget.ts +36 -0
  334. package/src/types/config.ts +278 -0
  335. package/src/types/events.ts +41 -0
  336. package/src/types/express.d.ts +14 -0
  337. package/src/types/index.ts +7 -0
  338. package/src/types/policy.ts +83 -0
  339. package/src/types/stripe-config.ts +11 -0
  340. package/src/types/subscription.ts +59 -0
  341. package/src/types/tool-call.ts +47 -0
  342. package/src/types/tool-result.ts +82 -0
  343. package/src/types/user.ts +125 -0
  344. package/tsconfig.json +24 -0
@@ -0,0 +1,202 @@
1
+ import * as http from 'http';
2
+ import * as https from 'https';
3
+ import { URL } from 'url';
4
+ import type { Response as ExpressResponse } from 'express';
5
+ import { isPrivateIP } from '../executor/http-executor';
6
+ import * as dns from 'dns';
7
+ import * as net from 'net';
8
+
9
+ /** Maximum accumulated response size for post-DLP scan (50 MB) */
10
+ const MAX_ACCUMULATED_BYTES = 50 * 1024 * 1024;
11
+
12
+ /**
13
+ * Resolve a hostname and verify it's not private/internal (SSRF protection).
14
+ * Reuses the same logic as HttpExecutor.
15
+ */
16
+ function validateResolvedIP(hostname: string): Promise<void> {
17
+ if (net.isIP(hostname)) {
18
+ if (isPrivateIP(hostname)) {
19
+ return Promise.reject(new Error(`SSRF blocked: resolved IP ${hostname} is a private/reserved address`));
20
+ }
21
+ return Promise.resolve();
22
+ }
23
+
24
+ return new Promise((resolve, reject) => {
25
+ dns.lookup(hostname, { all: true }, (err, addresses) => {
26
+ if (err) return reject(err);
27
+ for (const addr of addresses) {
28
+ if (isPrivateIP(addr.address)) {
29
+ return reject(
30
+ new Error(`SSRF blocked: hostname "${hostname}" resolved to private IP ${addr.address}`)
31
+ );
32
+ }
33
+ }
34
+ resolve();
35
+ });
36
+ });
37
+ }
38
+
39
+ export interface StreamProxyOptions {
40
+ /** Target URL to proxy to */
41
+ url: string;
42
+ /** HTTP method (default: POST) */
43
+ method?: string;
44
+ /** Request headers to forward */
45
+ headers?: Record<string, string>;
46
+ /** Request body */
47
+ body?: unknown;
48
+ /** Timeout in milliseconds (default: 120000) */
49
+ timeoutMs?: number;
50
+ /** Whether to enable SSRF protection (default: true) */
51
+ ssrfProtection?: boolean;
52
+ }
53
+
54
+ export interface StreamProxyResult {
55
+ /** Accumulated response body (all SSE chunks concatenated) */
56
+ accumulatedBody: string;
57
+ /** Upstream HTTP status code */
58
+ httpStatus: number;
59
+ /** Upstream response headers */
60
+ headers: Record<string, string>;
61
+ }
62
+
63
+ /**
64
+ * Opens an HTTP(S) connection to the target URL and pipes the response
65
+ * as SSE events to an Express response. Accumulates the full response
66
+ * in a buffer for post-stream DLP scanning.
67
+ *
68
+ * @returns The accumulated response body and upstream metadata on stream end.
69
+ */
70
+ export function streamProxy(
71
+ options: StreamProxyOptions,
72
+ clientRes: ExpressResponse,
73
+ ): Promise<StreamProxyResult> {
74
+ const {
75
+ url,
76
+ method = 'POST',
77
+ headers = {},
78
+ body,
79
+ timeoutMs = 120000,
80
+ ssrfProtection = true,
81
+ } = options;
82
+
83
+ return new Promise(async (resolve, reject) => {
84
+ // SSRF protection
85
+ const parsedUrl = new URL(url);
86
+ if (ssrfProtection) {
87
+ try {
88
+ await validateResolvedIP(parsedUrl.hostname);
89
+ } catch (err) {
90
+ reject(err);
91
+ return;
92
+ }
93
+ }
94
+
95
+ const isHttps = parsedUrl.protocol === 'https:';
96
+ const transport = isHttps ? https : http;
97
+ const bodyStr = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined;
98
+
99
+ const requestHeaders: Record<string, string> = { ...headers };
100
+ if (bodyStr && !requestHeaders['content-type'] && !requestHeaders['Content-Type']) {
101
+ requestHeaders['Content-Type'] = 'application/json';
102
+ }
103
+ if (bodyStr) {
104
+ requestHeaders['Content-Length'] = Buffer.byteLength(bodyStr).toString();
105
+ }
106
+
107
+ const requestOptions = {
108
+ hostname: parsedUrl.hostname,
109
+ port: parsedUrl.port || (isHttps ? 443 : 80),
110
+ path: parsedUrl.pathname + parsedUrl.search,
111
+ method: method.toUpperCase(),
112
+ headers: requestHeaders,
113
+ timeout: timeoutMs,
114
+ };
115
+
116
+ const req = transport.request(requestOptions, (upstreamRes) => {
117
+ const httpStatus = upstreamRes.statusCode || 502;
118
+
119
+ // Collect upstream response headers
120
+ const responseHeaders: Record<string, string> = {};
121
+ for (const [key, value] of Object.entries(upstreamRes.headers)) {
122
+ if (value) responseHeaders[key] = Array.isArray(value) ? value.join(', ') : value;
123
+ }
124
+
125
+ // If upstream returns non-2xx, read the body and reject
126
+ if (httpStatus >= 400) {
127
+ let errBody = '';
128
+ upstreamRes.on('data', (chunk: Buffer) => { errBody += chunk.toString(); });
129
+ upstreamRes.on('end', () => {
130
+ reject(new Error(`Upstream returned HTTP ${httpStatus}: ${errBody.slice(0, 500)}`));
131
+ });
132
+ return;
133
+ }
134
+
135
+ // Set SSE response headers on the client response
136
+ clientRes.writeHead(200, {
137
+ 'Content-Type': 'text/event-stream',
138
+ 'Cache-Control': 'no-cache',
139
+ 'Connection': 'keep-alive',
140
+ 'X-Accel-Buffering': 'no',
141
+ });
142
+
143
+ // Accumulation buffer for post-DLP
144
+ let accumulated = '';
145
+ let accumulatedBytes = 0;
146
+
147
+ upstreamRes.on('data', (chunk: Buffer) => {
148
+ const str = chunk.toString();
149
+
150
+ // Pipe directly to client (zero-buffer streaming)
151
+ clientRes.write(chunk);
152
+
153
+ // Accumulate for post-stream DLP scan
154
+ accumulatedBytes += chunk.length;
155
+ if (accumulatedBytes <= MAX_ACCUMULATED_BYTES) {
156
+ accumulated += str;
157
+ }
158
+ });
159
+
160
+ upstreamRes.on('end', () => {
161
+ clientRes.end();
162
+ resolve({
163
+ accumulatedBody: accumulated,
164
+ httpStatus,
165
+ headers: responseHeaders,
166
+ });
167
+ });
168
+
169
+ upstreamRes.on('error', (err) => {
170
+ // Send SSE error event to client before closing
171
+ try {
172
+ clientRes.write(`event: error\ndata: ${JSON.stringify({ error: err.message })}\n\n`);
173
+ clientRes.end();
174
+ } catch {
175
+ // Client may already be disconnected
176
+ }
177
+ reject(err);
178
+ });
179
+ });
180
+
181
+ req.on('error', (err) => {
182
+ reject(err);
183
+ });
184
+
185
+ req.on('timeout', () => {
186
+ req.destroy();
187
+ reject(new Error(`Upstream request timeout after ${timeoutMs}ms`));
188
+ });
189
+
190
+ // Handle client disconnect — abort the upstream request
191
+ clientRes.on('close', () => {
192
+ if (!req.destroyed) {
193
+ req.destroy();
194
+ }
195
+ });
196
+
197
+ if (bodyStr) {
198
+ req.write(bodyStr);
199
+ }
200
+ req.end();
201
+ });
202
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * File-based persistence for SaaS in-memory stores.
3
+ * Saves all store data to a JSON file on every write operation.
4
+ * Loads data from disk on startup.
5
+ *
6
+ * This is for development only -- production uses PostgreSQL.
7
+ */
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import {
11
+ User,
12
+ OAuthAccount,
13
+ OAuthProvider,
14
+ Workspace,
15
+ WorkspaceMember,
16
+ Session,
17
+ UserApiKey,
18
+ } from '../types/user';
19
+ import { PolicyPack } from '../types/policy';
20
+ import {
21
+ UserStore,
22
+ OAuthAccountStore,
23
+ WorkspaceStore,
24
+ WorkspaceMemberStore,
25
+ SessionStore,
26
+ UserApiKeyStore,
27
+ PolicyStore,
28
+ } from './interfaces';
29
+ import {
30
+ InMemoryUserStore,
31
+ InMemoryOAuthAccountStore,
32
+ InMemoryWorkspaceStore,
33
+ InMemoryWorkspaceMemberStore,
34
+ InMemorySessionStore,
35
+ InMemoryUserApiKeyStore,
36
+ InMemoryPolicyStore,
37
+ } from './memory';
38
+ import { OAuthClientsStore } from '../mcp/oauth-stores';
39
+ import { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
40
+
41
+ interface PersistedData {
42
+ users: [string, User][];
43
+ oauthAccounts: [string, OAuthAccount][];
44
+ workspaces: [string, Workspace][];
45
+ members: [string, WorkspaceMember][];
46
+ sessions: [string, Session][];
47
+ apiKeys: [string, UserApiKey][];
48
+ policies: [string, PolicyPack][];
49
+ /** MCP OAuth clients (dynamic registration) */
50
+ mcpOAuthClients?: [string, OAuthClientInformationFull][];
51
+ }
52
+
53
+ /**
54
+ * Wraps an in-memory store with file persistence.
55
+ * Every mutation triggers a save of ALL stores to disk.
56
+ */
57
+ export class FilePersistedStores {
58
+ private filePath: string;
59
+ private saveTimer: ReturnType<typeof setTimeout> | null = null;
60
+
61
+ readonly userStore: UserStore;
62
+ readonly oauthAccountStore: OAuthAccountStore;
63
+ readonly workspaceStore: WorkspaceStore;
64
+ readonly workspaceMemberStore: WorkspaceMemberStore;
65
+ readonly sessionStore: SessionStore;
66
+ readonly userApiKeyStore: UserApiKeyStore;
67
+ readonly policyStore: PolicyStore;
68
+ readonly oauthClientsStore: OAuthClientsStore;
69
+
70
+ // Keep references to the underlying in-memory stores for serialization
71
+ private _users: InMemoryUserStore;
72
+ private _oauth: InMemoryOAuthAccountStore;
73
+ private _workspaces: InMemoryWorkspaceStore;
74
+ private _members: InMemoryWorkspaceMemberStore;
75
+ private _sessions: InMemorySessionStore;
76
+ private _apiKeys: InMemoryUserApiKeyStore;
77
+ private _policies: InMemoryPolicyStore;
78
+ private _oauthClients: OAuthClientsStore;
79
+
80
+ constructor(dataDir: string) {
81
+ fs.mkdirSync(dataDir, { recursive: true });
82
+ this.filePath = path.join(dataDir, 'saas-state.json');
83
+
84
+ this._users = new InMemoryUserStore();
85
+ this._oauth = new InMemoryOAuthAccountStore();
86
+ this._workspaces = new InMemoryWorkspaceStore();
87
+ this._members = new InMemoryWorkspaceMemberStore();
88
+ this._sessions = new InMemorySessionStore();
89
+ this._apiKeys = new InMemoryUserApiKeyStore();
90
+ this._policies = new InMemoryPolicyStore();
91
+ this._oauthClients = new OAuthClientsStore();
92
+
93
+ // Load existing data from disk
94
+ this.load();
95
+
96
+ // Create proxied stores that auto-save on mutations
97
+ const save = () => this.scheduleSave();
98
+ const saveNow = () => this.scheduleSave(true);
99
+
100
+ this.userStore = this.wrapUserStore(this._users, save, saveNow);
101
+ this.oauthAccountStore = this.wrapOAuthStore(this._oauth, save);
102
+ this.workspaceStore = this.wrapWorkspaceStore(this._workspaces, save);
103
+ this.workspaceMemberStore = this.wrapMemberStore(this._members, save);
104
+ this.sessionStore = this.wrapSessionStore(this._sessions, save);
105
+ this.userApiKeyStore = this.wrapApiKeyStore(this._apiKeys, save, saveNow);
106
+ this.policyStore = this.wrapPolicyStore(this._policies, save);
107
+ this.oauthClientsStore = this._oauthClients; // Direct reference; persisted via saveNow on load
108
+ }
109
+
110
+ private load(): void {
111
+ try {
112
+ if (!fs.existsSync(this.filePath)) return;
113
+ const raw = fs.readFileSync(this.filePath, 'utf-8');
114
+ const data: PersistedData = JSON.parse(raw);
115
+
116
+ for (const [, u] of data.users || []) this._users.create(u);
117
+ for (const [, a] of data.oauthAccounts || []) this._oauth.create(a);
118
+ for (const [, w] of data.workspaces || []) this._workspaces.create(w);
119
+ for (const [, m] of data.members || []) this._members.create(m);
120
+ for (const [, s] of data.sessions || []) this._sessions.create(s);
121
+ for (const [, k] of data.apiKeys || []) this._apiKeys.create(k);
122
+ for (const [wsId, p] of data.policies || []) this._policies.set(wsId, p);
123
+ if (data.mcpOAuthClients) {
124
+ this._oauthClients.load(data.mcpOAuthClients);
125
+ }
126
+
127
+ const counts = [
128
+ data.users?.length || 0,
129
+ data.workspaces?.length || 0,
130
+ data.apiKeys?.length || 0,
131
+ ];
132
+ console.log(`Loaded persisted state: ${counts[0]} users, ${counts[1]} workspaces, ${counts[2]} API keys`);
133
+ } catch (err) {
134
+ console.warn('Failed to load persisted state:', (err as Error).message);
135
+ }
136
+ }
137
+
138
+ private scheduleSave(immediate = false): void {
139
+ if (immediate) {
140
+ // Cancel any pending debounced save and write synchronously now
141
+ if (this.saveTimer) {
142
+ clearTimeout(this.saveTimer);
143
+ this.saveTimer = null;
144
+ }
145
+ this.saveNow();
146
+ return;
147
+ }
148
+ // Debounce saves to avoid excessive disk writes
149
+ if (this.saveTimer) return;
150
+ this.saveTimer = setTimeout(() => {
151
+ this.saveTimer = null;
152
+ this.saveNow();
153
+ }, 100);
154
+ }
155
+
156
+ /**
157
+ * Write to disk synchronously. Use for critical operations where
158
+ * data loss from a crash within 100ms would be unacceptable
159
+ * (e.g. user creation, API key creation).
160
+ */
161
+ saveImmediate(): void {
162
+ if (this.saveTimer) {
163
+ clearTimeout(this.saveTimer);
164
+ this.saveTimer = null;
165
+ }
166
+ this.saveNow();
167
+ }
168
+
169
+ private saveNow(): void {
170
+ try {
171
+ const data: PersistedData = {
172
+ users: this._users.list().map(u => [u.id, u]),
173
+ oauthAccounts: [], // OAuth accounts are ephemeral
174
+ workspaces: this._workspaces.list().map(w => [w.id, w]),
175
+ members: this.serializeMembers(),
176
+ sessions: [], // Sessions are ephemeral
177
+ apiKeys: this.serializeApiKeys(),
178
+ policies: this.serializePolicies(),
179
+ mcpOAuthClients: this._oauthClients.entries(),
180
+ };
181
+ const tmp = this.filePath + '.tmp';
182
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
183
+ fs.renameSync(tmp, this.filePath);
184
+ } catch (err) {
185
+ console.error('Failed to persist state:', (err as Error).message);
186
+ }
187
+ }
188
+
189
+ private serializeMembers(): [string, WorkspaceMember][] {
190
+ // Access members via store's list-like queries (workspace-based)
191
+ const workspaces = this._workspaces.list();
192
+ const seen = new Set<string>();
193
+ const result: [string, WorkspaceMember][] = [];
194
+ for (const w of workspaces) {
195
+ for (const m of this._members.getByWorkspace(w.id)) {
196
+ if (!seen.has(m.id)) {
197
+ seen.add(m.id);
198
+ result.push([m.id, m]);
199
+ }
200
+ }
201
+ }
202
+ return result;
203
+ }
204
+
205
+ private serializeApiKeys(): [string, UserApiKey][] {
206
+ const workspaces = this._workspaces.list();
207
+ const seen = new Set<string>();
208
+ const result: [string, UserApiKey][] = [];
209
+ for (const w of workspaces) {
210
+ for (const k of this._apiKeys.getByWorkspace(w.id)) {
211
+ if (!seen.has(k.id)) {
212
+ seen.add(k.id);
213
+ result.push([k.id, k]);
214
+ }
215
+ }
216
+ }
217
+ return result;
218
+ }
219
+
220
+ private serializePolicies(): [string, PolicyPack][] {
221
+ const m = this._policies.list();
222
+ return Array.from(m.entries());
223
+ }
224
+
225
+ flush(): void {
226
+ if (this.saveTimer) {
227
+ clearTimeout(this.saveTimer);
228
+ this.saveTimer = null;
229
+ }
230
+ this.saveNow();
231
+ }
232
+
233
+ // --- Wrapper factories that add save-on-mutate ---
234
+
235
+ private wrapUserStore(inner: InMemoryUserStore, save: () => void, saveNow: () => void): UserStore {
236
+ return {
237
+ create(user: User) { inner.create(user); saveNow(); },
238
+ getById(id: string) { return inner.getById(id); },
239
+ getByEmail(email: string) { return inner.getByEmail(email); },
240
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
241
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
242
+ list() { return inner.list(); },
243
+ };
244
+ }
245
+
246
+ private wrapOAuthStore(inner: InMemoryOAuthAccountStore, save: () => void): OAuthAccountStore {
247
+ return {
248
+ create(account: OAuthAccount) { inner.create(account); save(); },
249
+ getById(id: string) { return inner.getById(id); },
250
+ getByProvider(provider: OAuthProvider, providerUserId: string) { return inner.getByProvider(provider, providerUserId); },
251
+ getByUserId(userId: string) { return inner.getByUserId(userId); },
252
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
253
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
254
+ };
255
+ }
256
+
257
+ private wrapWorkspaceStore(inner: InMemoryWorkspaceStore, save: () => void): WorkspaceStore {
258
+ return {
259
+ create(workspace: Workspace) { inner.create(workspace); save(); },
260
+ getById(id: string) { return inner.getById(id); },
261
+ getBySlug(slug: string) { return inner.getBySlug(slug); },
262
+ getByOwner(userId: string) { return inner.getByOwner(userId); },
263
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
264
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
265
+ list() { return inner.list(); },
266
+ };
267
+ }
268
+
269
+ private wrapMemberStore(inner: InMemoryWorkspaceMemberStore, save: () => void): WorkspaceMemberStore {
270
+ return {
271
+ create(member: WorkspaceMember) { inner.create(member); save(); },
272
+ getById(id: string) { return inner.getById(id); },
273
+ getByWorkspace(workspaceId: string) { return inner.getByWorkspace(workspaceId); },
274
+ getByUser(userId: string) { return inner.getByUser(userId); },
275
+ getByWorkspaceAndUser(workspaceId: string, userId: string) { return inner.getByWorkspaceAndUser(workspaceId, userId); },
276
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
277
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
278
+ };
279
+ }
280
+
281
+ private wrapSessionStore(inner: InMemorySessionStore, save: () => void): SessionStore {
282
+ return {
283
+ create(session: Session) { inner.create(session); save(); },
284
+ getById(id: string) { return inner.getById(id); },
285
+ getByUserId(userId: string) { return inner.getByUserId(userId); },
286
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
287
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
288
+ deleteExpired() { const r = inner.deleteExpired(); save(); return r; },
289
+ deleteByUserId(userId: string) { const r = inner.deleteByUserId(userId); save(); return r; },
290
+ };
291
+ }
292
+
293
+ private wrapApiKeyStore(inner: InMemoryUserApiKeyStore, save: () => void, saveNow: () => void): UserApiKeyStore {
294
+ return {
295
+ create(apiKey: UserApiKey) { inner.create(apiKey); saveNow(); },
296
+ getById(id: string) { return inner.getById(id); },
297
+ getByKeyHash(keyHash: string) { return inner.getByKeyHash(keyHash); },
298
+ verifyToken(token: string) { return inner.verifyToken(token); },
299
+ getByWorkspace(workspaceId: string) { return inner.getByWorkspace(workspaceId); },
300
+ getByUser(userId: string) { return inner.getByUser(userId); },
301
+ update(id: string, updates: any) { const r = inner.update(id, updates); save(); return r; },
302
+ delete(id: string) { const r = inner.delete(id); save(); return r; },
303
+ };
304
+ }
305
+
306
+ private wrapPolicyStore(inner: InMemoryPolicyStore, save: () => void): PolicyStore {
307
+ return {
308
+ getByWorkspaceId(workspaceId: string) { return inner.getByWorkspaceId(workspaceId); },
309
+ set(workspaceId: string, policy: PolicyPack) { inner.set(workspaceId, policy); save(); },
310
+ delete(workspaceId: string) { const r = inner.delete(workspaceId); save(); return r; },
311
+ list() { return inner.list(); },
312
+ clear() { inner.clear(); save(); },
313
+ };
314
+ }
315
+ }
@@ -0,0 +1,4 @@
1
+ export * from './interfaces';
2
+ export * from './memory';
3
+ export * from './postgres';
4
+ export * from './redis';