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
@@ -41,6 +41,7 @@ const memory_1 = require("../storage/memory");
41
41
  const calculator_1 = require("../trust/calculator");
42
42
  const engine_1 = require("../replay/engine");
43
43
  const plan_enforcer_1 = require("../billing/plan-enforcer");
44
+ const model_pricing_1 = require("../budget/model-pricing");
44
45
  /** Extract a route param as string (Express 5 returns string | string[]). */
45
46
  function param(req, name) {
46
47
  const val = req.params[name];
@@ -313,7 +314,7 @@ function createSaaSRouter(deps) {
313
314
  res.json({
314
315
  api_keys: keys.map(k => ({
315
316
  id: k.id,
316
- key_prefix: k.key_prefix,
317
+ prefix: k.key_prefix,
317
318
  name: k.name,
318
319
  roles: k.roles,
319
320
  tags: k.tags || [],
@@ -376,7 +377,7 @@ function createSaaSRouter(deps) {
376
377
  res.status(201).json({
377
378
  id: apiKey.id,
378
379
  key: rawKey,
379
- key_prefix: keyPrefix,
380
+ prefix: keyPrefix,
380
381
  name: apiKey.name,
381
382
  roles: apiKey.roles,
382
383
  tags: apiKey.tags,
@@ -495,6 +496,201 @@ function createSaaSRouter(deps) {
495
496
  offset,
496
497
  });
497
498
  });
499
+ router.delete('/workspaces/:id/traces', (req, res) => {
500
+ if (!requireSession(req, res))
501
+ return;
502
+ const user = req.sessionUser;
503
+ const workspaceId = param(req, 'id');
504
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
505
+ if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
506
+ res.status(403).json({ error: 'Admin or owner access required' });
507
+ return;
508
+ }
509
+ gateway.getAuditLogger().clear();
510
+ res.json({ status: 'ok', message: 'All traces cleared' });
511
+ });
512
+ router.delete('/workspaces/:id/traces/:taskId', (req, res) => {
513
+ if (!requireSession(req, res))
514
+ return;
515
+ const user = req.sessionUser;
516
+ const workspaceId = param(req, 'id');
517
+ const taskId = param(req, 'taskId');
518
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
519
+ if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
520
+ res.status(403).json({ error: 'Admin or owner access required' });
521
+ return;
522
+ }
523
+ gateway.getAuditLogger().deleteByTaskId(taskId);
524
+ res.json({ status: 'ok', message: `Trace ${taskId} deleted` });
525
+ });
526
+ router.delete('/workspaces/:id/sessions/:sessionId', (req, res) => {
527
+ if (!requireSession(req, res))
528
+ return;
529
+ const user = req.sessionUser;
530
+ const workspaceId = param(req, 'id');
531
+ const sessionId = param(req, 'sessionId');
532
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
533
+ if (!membership || (membership.role !== 'owner' && membership.role !== 'admin')) {
534
+ res.status(403).json({ error: 'Admin or owner access required' });
535
+ return;
536
+ }
537
+ gateway.getAuditLogger().deleteBySessionId(sessionId);
538
+ res.json({ status: 'ok', message: `Session ${sessionId} traces deleted` });
539
+ });
540
+ // ---------------------------------------------------------------------------
541
+ // Sessions (grouped traces)
542
+ // ---------------------------------------------------------------------------
543
+ router.get('/workspaces/:id/sessions', (req, res) => {
544
+ if (!requireSession(req, res))
545
+ return;
546
+ const user = req.sessionUser;
547
+ const workspaceId = param(req, 'id');
548
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
549
+ if (!membership) {
550
+ res.status(403).json({ error: 'Not a member of this workspace' });
551
+ return;
552
+ }
553
+ const limit = Math.min(parseInt(req.query.limit) || 50, 200);
554
+ const offset = parseInt(req.query.offset) || 0;
555
+ const allEvents = gateway.getAuditLogger().getAllEvents();
556
+ const wsForSessions = workspaceStore.getById(workspaceId);
557
+ const slugForSessions = wsForSessions?.slug;
558
+ const wsEvents = allEvents.filter(e => e.workspace_id === workspaceId || (slugForSessions && e.workspace_id === slugForSessions));
559
+ // Group events by session_id (falling back to tool_call_id for old events)
560
+ const sessionMap = new Map();
561
+ for (const e of wsEvents) {
562
+ const key = e.session_id || e.tool_call_id || e.task_id;
563
+ if (!sessionMap.has(key))
564
+ sessionMap.set(key, []);
565
+ sessionMap.get(key).push(e);
566
+ }
567
+ // Build session summaries
568
+ const sessions = [];
569
+ for (const [sessionId, events] of sessionMap) {
570
+ events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
571
+ const first = events[0];
572
+ const last = events[events.length - 1];
573
+ // Count unique tool_call_ids (each represents one tool call)
574
+ const toolCallIds = new Set(events.map(e => e.tool_call_id));
575
+ const receivedEvents = events.filter(e => e.event_type === 'TOOL_CALL_RECEIVED');
576
+ const toolCallCount = Math.max(receivedEvents.length, 1);
577
+ // Unique tools
578
+ const toolsUsed = [...new Set(events.map(e => e.tool_name).filter(Boolean))];
579
+ // Derive status counts per tool_call_id
580
+ const statusCounts = { ok: 0, blocked: 0, error: 0, approval: 0, redacted: 0 };
581
+ for (const tcId of toolCallIds) {
582
+ const tcEvents = events.filter(e => e.tool_call_id === tcId);
583
+ const hasDeny = tcEvents.some(e => e.event_type === 'POLICY_DECIDED' && e.metadata?.decision === 'deny');
584
+ const hasApproval = tcEvents.some(e => e.event_type === 'APPROVAL_REQUESTED');
585
+ const hasError = tcEvents.some(e => e.event_type === 'TOOL_EXECUTED' && e.metadata?.status === 'error');
586
+ const hasRedaction = tcEvents.some(e => e.event_type === 'DLP_SCANNED' && Array.isArray(e.metadata?.detected) && e.metadata.detected.length > 0);
587
+ if (hasDeny)
588
+ statusCounts.blocked++;
589
+ else if (hasError)
590
+ statusCounts.error++;
591
+ else if (hasApproval)
592
+ statusCounts.approval++;
593
+ else
594
+ statusCounts.ok++;
595
+ // Redacted is orthogonal — a call can be ok AND redacted
596
+ if (hasRedaction)
597
+ statusCounts.redacted++;
598
+ }
599
+ // Overall status: worst status across calls
600
+ let overallStatus = 'ok';
601
+ if (statusCounts.blocked > 0)
602
+ overallStatus = 'blocked';
603
+ else if (statusCounts.error > 0)
604
+ overallStatus = 'error';
605
+ else if (statusCounts.approval > 0)
606
+ overallStatus = 'approval';
607
+ sessions.push({
608
+ session_id: sessionId,
609
+ tool_call_count: toolCallCount,
610
+ first_timestamp: first.timestamp,
611
+ last_timestamp: last.timestamp,
612
+ duration_ms: new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime(),
613
+ actor_id: first.actor_id || '',
614
+ platform: first.metadata?.platform || 'unknown',
615
+ tools_used: toolsUsed,
616
+ overall_status: overallStatus,
617
+ status_counts: statusCounts,
618
+ });
619
+ }
620
+ // Sort newest first
621
+ sessions.sort((a, b) => b.first_timestamp.localeCompare(a.first_timestamp));
622
+ res.json({
623
+ sessions: sessions.slice(offset, offset + limit),
624
+ total: sessions.length,
625
+ });
626
+ });
627
+ router.get('/workspaces/:id/sessions/:sessionId', (req, res) => {
628
+ if (!requireSession(req, res))
629
+ return;
630
+ const user = req.sessionUser;
631
+ const workspaceId = param(req, 'id');
632
+ const sessionId = param(req, 'sessionId');
633
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
634
+ if (!membership) {
635
+ res.status(403).json({ error: 'Not a member of this workspace' });
636
+ return;
637
+ }
638
+ const allEvents = gateway.getAuditLogger().getAllEvents();
639
+ const workspace = workspaceStore.getById(workspaceId);
640
+ const wsSlug = workspace?.slug;
641
+ // Find events by session_id, filtered to this workspace
642
+ const sessionEvents = allEvents.filter(e => e.session_id === sessionId &&
643
+ (e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug)));
644
+ // If no events found by session_id, try treating sessionId as a tool_call_id (backward compat)
645
+ const events = sessionEvents.length > 0
646
+ ? sessionEvents
647
+ : allEvents.filter(e => e.tool_call_id === sessionId &&
648
+ (e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug)));
649
+ // Group by tool_call_id
650
+ const toolCallMap = new Map();
651
+ for (const e of events) {
652
+ const tcId = e.tool_call_id;
653
+ if (!toolCallMap.has(tcId))
654
+ toolCallMap.set(tcId, []);
655
+ toolCallMap.get(tcId).push(e);
656
+ }
657
+ const toolCalls = [];
658
+ for (const [tcId, tcEvents] of toolCallMap) {
659
+ tcEvents.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
660
+ const first = tcEvents[0];
661
+ const last = tcEvents[tcEvents.length - 1];
662
+ const resultEvent = tcEvents.find(e => e.event_type === 'TOOL_RESULT_RETURNED');
663
+ const durationMs = resultEvent?.metadata?.duration_ms ||
664
+ (new Date(last.timestamp).getTime() - new Date(first.timestamp).getTime());
665
+ // Derive status
666
+ const hasDeny = tcEvents.some(e => e.event_type === 'POLICY_DECIDED' && e.metadata?.decision === 'deny');
667
+ const hasApproval = tcEvents.some(e => e.event_type === 'APPROVAL_REQUESTED');
668
+ const hasError = tcEvents.some(e => e.event_type === 'TOOL_EXECUTED' && e.metadata?.status === 'error');
669
+ let status = 'ok';
670
+ if (hasDeny)
671
+ status = 'blocked';
672
+ else if (hasError)
673
+ status = 'error';
674
+ else if (hasApproval)
675
+ status = 'approval';
676
+ toolCalls.push({
677
+ tool_call_id: tcId,
678
+ task_id: first.task_id,
679
+ tool_name: first.tool_name || 'unknown',
680
+ timestamp: first.timestamp,
681
+ duration_ms: durationMs,
682
+ status,
683
+ events: tcEvents,
684
+ });
685
+ }
686
+ // Sort by timestamp
687
+ toolCalls.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
688
+ res.json({
689
+ session_id: sessionId,
690
+ tool_calls: toolCalls,
691
+ total_events: events.length,
692
+ });
693
+ });
498
694
  // ---------------------------------------------------------------------------
499
695
  // Budgets
500
696
  // ---------------------------------------------------------------------------
@@ -712,6 +908,23 @@ function createSaaSRouter(deps) {
712
908
  // ---------------------------------------------------------------------------
713
909
  // Policy Rule Generation (LLM-powered)
714
910
  // ---------------------------------------------------------------------------
911
+ // Per-user rate limiter for LLM rule generation: 10 requests per hour
912
+ const LLM_RATE_LIMIT_MAX = 10;
913
+ const LLM_RATE_LIMIT_WINDOW_MS = 60 * 60 * 1000; // 1 hour
914
+ const llmRateLimitHits = new Map();
915
+ // Periodic cleanup to prevent memory leaks
916
+ setInterval(() => {
917
+ const now = Date.now();
918
+ for (const [userId, timestamps] of llmRateLimitHits) {
919
+ const valid = timestamps.filter(t => now - t < LLM_RATE_LIMIT_WINDOW_MS);
920
+ if (valid.length === 0) {
921
+ llmRateLimitHits.delete(userId);
922
+ }
923
+ else {
924
+ llmRateLimitHits.set(userId, valid);
925
+ }
926
+ }
927
+ }, LLM_RATE_LIMIT_WINDOW_MS).unref();
715
928
  router.post('/workspaces/:id/policies/generate-rule', async (req, res) => {
716
929
  if (!requireSession(req, res))
717
930
  return;
@@ -733,6 +946,21 @@ function createSaaSRouter(deps) {
733
946
  return;
734
947
  }
735
948
  const trimmed = description.trim().slice(0, 500);
949
+ // Per-user rate limit check (after validation, before LLM call)
950
+ const now = Date.now();
951
+ const userTimestamps = llmRateLimitHits.get(user.id) || [];
952
+ const windowStart = now - LLM_RATE_LIMIT_WINDOW_MS;
953
+ const validTimestamps = userTimestamps.filter(t => t > windowStart);
954
+ if (validTimestamps.length >= LLM_RATE_LIMIT_MAX) {
955
+ const retryAfterMs = LLM_RATE_LIMIT_WINDOW_MS - (now - validTimestamps[0]);
956
+ res.status(429).json({
957
+ error: `You've reached the limit of ${LLM_RATE_LIMIT_MAX} AI generations per hour. Please try again later.`,
958
+ retry_after_ms: retryAfterMs,
959
+ });
960
+ return;
961
+ }
962
+ validTimestamps.push(now);
963
+ llmRateLimitHits.set(user.id, validTimestamps);
736
964
  const systemPrompt = `You are a policy rule generator for Palaryn, an AI agent gateway. Given a natural language description, generate a single JSON PolicyRule object.
737
965
 
738
966
  The PolicyRule schema:
@@ -784,19 +1012,33 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
784
1012
  try {
785
1013
  const controller = new AbortController();
786
1014
  const timeout = setTimeout(() => controller.abort(), 15000);
787
- const llmRes = await fetch('https://api.anthropic.com/v1/messages', {
788
- method: 'POST',
789
- headers: {
790
- 'Content-Type': 'application/json',
791
- 'x-api-key': apiKey,
792
- 'anthropic-version': '2023-06-01',
793
- },
794
- body: JSON.stringify({
795
- model: 'claude-sonnet-4-5-20250929',
1015
+ // Detect provider from API key prefix
1016
+ const isOpenAI = apiKey.startsWith('sk-proj-') || apiKey.startsWith('sk-');
1017
+ const llmUrl = isOpenAI
1018
+ ? 'https://api.openai.com/v1/chat/completions'
1019
+ : 'https://api.anthropic.com/v1/messages';
1020
+ const llmHeaders = isOpenAI
1021
+ ? { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }
1022
+ : { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' };
1023
+ const llmBody = isOpenAI
1024
+ ? JSON.stringify({
1025
+ model: 'gpt-4.1-mini',
1026
+ max_tokens: 1024,
1027
+ messages: [
1028
+ { role: 'system', content: systemPrompt },
1029
+ { role: 'user', content: trimmed },
1030
+ ],
1031
+ })
1032
+ : JSON.stringify({
1033
+ model: 'claude-sonnet-4-5-20241022',
796
1034
  max_tokens: 1024,
797
1035
  system: systemPrompt,
798
1036
  messages: [{ role: 'user', content: trimmed }],
799
- }),
1037
+ });
1038
+ const llmRes = await fetch(llmUrl, {
1039
+ method: 'POST',
1040
+ headers: llmHeaders,
1041
+ body: llmBody,
800
1042
  signal: controller.signal,
801
1043
  });
802
1044
  clearTimeout(timeout);
@@ -807,7 +1049,9 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
807
1049
  return;
808
1050
  }
809
1051
  const llmData = await llmRes.json();
810
- const text = llmData.content?.[0]?.text || '';
1052
+ const text = isOpenAI
1053
+ ? (llmData.choices?.[0]?.message?.content || '')
1054
+ : (llmData.content?.[0]?.text || '');
811
1055
  // Strip markdown code fences if present
812
1056
  const cleaned = text.replace(/^```(?:json)?\s*/i, '').replace(/\s*```\s*$/i, '').trim();
813
1057
  let rule;
@@ -862,20 +1106,38 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
862
1106
  }
863
1107
  const limit = Math.min(parseInt(req.query.limit) || 50, 200);
864
1108
  const offset = parseInt(req.query.offset) || 0;
1109
+ const severity = req.query.severity;
1110
+ const tool = req.query.tool;
1111
+ const actor = req.query.actor;
1112
+ const q = req.query.q;
865
1113
  const allDlpEvents = gateway.getAuditLogger().getEventsByType('DLP_SCANNED');
866
1114
  const workspace = workspaceStore.getById(workspaceId);
867
1115
  const wsSlug = workspace?.slug;
868
1116
  const wsEvents = allDlpEvents
869
1117
  .filter(e => e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug))
870
1118
  .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
871
- // Compute severity stats
1119
+ // Apply optional filters
1120
+ let filtered = wsEvents;
1121
+ if (severity)
1122
+ filtered = filtered.filter(e => e.metadata?.severity === severity);
1123
+ if (tool)
1124
+ filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(tool.toLowerCase()));
1125
+ if (actor)
1126
+ filtered = filtered.filter(e => e.actor_id.toLowerCase().includes(actor.toLowerCase()));
1127
+ if (q) {
1128
+ const ql = q.toLowerCase();
1129
+ filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(ql) ||
1130
+ e.actor_id.toLowerCase().includes(ql) ||
1131
+ (e.metadata?.detected || []).some(p => p.toLowerCase().includes(ql)));
1132
+ }
1133
+ // Compute severity stats over filtered results
872
1134
  let high = 0, medium = 0, low = 0;
873
1135
  const patternSet = new Set();
874
- for (const e of wsEvents) {
875
- const severity = e.metadata?.severity;
876
- if (severity === 'high')
1136
+ for (const e of filtered) {
1137
+ const sev = e.metadata?.severity;
1138
+ if (sev === 'high')
877
1139
  high++;
878
- else if (severity === 'medium')
1140
+ else if (sev === 'medium')
879
1141
  medium++;
880
1142
  else
881
1143
  low++;
@@ -886,12 +1148,12 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
886
1148
  }
887
1149
  }
888
1150
  res.json({
889
- events: wsEvents.slice(offset, offset + limit),
890
- total: wsEvents.length,
1151
+ events: filtered.slice(offset, offset + limit),
1152
+ total: filtered.length,
891
1153
  limit,
892
1154
  offset,
893
1155
  stats: {
894
- total: wsEvents.length,
1156
+ total: filtered.length,
895
1157
  high,
896
1158
  medium,
897
1159
  low,
@@ -1140,11 +1402,16 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
1140
1402
  const denyCount = allStats.reduce((acc, s) => acc + (s.policy_breakdown['deny'] || 0), 0);
1141
1403
  const shield_score = {
1142
1404
  score: totalDecisions > 0 ? Math.round(((allowCount + transformCount) / totalDecisions) * 100) : 100,
1405
+ total: totalDecisions,
1143
1406
  breakdown: {
1144
1407
  allowed_percent: totalDecisions > 0 ? Math.round((allowCount / totalDecisions) * 100) : 100,
1145
1408
  transformed_percent: totalDecisions > 0 ? Math.round((transformCount / totalDecisions) * 100) : 0,
1146
1409
  approval_percent: totalDecisions > 0 ? Math.round((approvalCount / totalDecisions) * 100) : 0,
1147
1410
  blocked_percent: totalDecisions > 0 ? Math.round((denyCount / totalDecisions) * 100) : 0,
1411
+ allowed_count: allowCount,
1412
+ transformed_count: transformCount,
1413
+ approval_count: approvalCount,
1414
+ blocked_count: denyCount,
1148
1415
  },
1149
1416
  };
1150
1417
  // Pipeline throughput from stats (merged across all matching workspace IDs)
@@ -1177,7 +1444,7 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
1177
1444
  res.json({ current: {}, baseline: {}, alerts: [] });
1178
1445
  return;
1179
1446
  }
1180
- const report = detector.getBaselineReport();
1447
+ const report = detector.getBaselineReport(workspaceId);
1181
1448
  res.json(report);
1182
1449
  });
1183
1450
  // ---------------------------------------------------------------------------
@@ -1561,6 +1828,72 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
1561
1828
  });
1562
1829
  res.json({ status: 'reset', config: config.budget, is_custom: false });
1563
1830
  });
1831
+ // ---------------------------------------------------------------------------
1832
+ // Model Pricing: Get merged pricing (built-in + workspace overrides)
1833
+ // ---------------------------------------------------------------------------
1834
+ router.get('/workspaces/:id/model-pricing', (req, res) => {
1835
+ if (!requireSession(req, res))
1836
+ return;
1837
+ const user = req.sessionUser;
1838
+ const workspaceId = param(req, 'id');
1839
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
1840
+ if (!membership) {
1841
+ res.status(403).json({ error: 'Not a member of this workspace' });
1842
+ return;
1843
+ }
1844
+ // Get workspace-level budget config overrides (if any)
1845
+ const wsConfig = budgetConfigStore.getByWorkspaceId(workspaceId);
1846
+ const workspaceOverrides = wsConfig?.token_pricing;
1847
+ // Merge: built-in pricing as base, workspace overrides on top
1848
+ const merged = {};
1849
+ for (const [model, pricing] of Object.entries(model_pricing_1.MODEL_PRICING)) {
1850
+ merged[model] = {
1851
+ input_per_token: pricing.input_per_token,
1852
+ output_per_token: pricing.output_per_token,
1853
+ source: 'built-in',
1854
+ };
1855
+ }
1856
+ if (workspaceOverrides) {
1857
+ for (const [model, pricing] of Object.entries(workspaceOverrides)) {
1858
+ merged[model] = {
1859
+ input_per_token: pricing.input_per_token,
1860
+ output_per_token: pricing.output_per_token,
1861
+ source: 'workspace',
1862
+ };
1863
+ }
1864
+ }
1865
+ res.json({
1866
+ models: merged,
1867
+ has_workspace_overrides: !!workspaceOverrides,
1868
+ });
1869
+ });
1870
+ // ---------------------------------------------------------------------------
1871
+ // Admin: Update Workspace Plan (platform admin only)
1872
+ // ---------------------------------------------------------------------------
1873
+ const VALID_PLANS = ['free', 'pro', 'business', 'enterprise'];
1874
+ router.put('/workspaces/:id/plan', (req, res) => {
1875
+ if (!requireSession(req, res))
1876
+ return;
1877
+ const user = req.sessionUser;
1878
+ const adminEmail = process.env.SEED_ADMIN_EMAIL;
1879
+ if (!adminEmail || user.email !== adminEmail) {
1880
+ res.status(403).json({ error: 'Platform admin access required' });
1881
+ return;
1882
+ }
1883
+ const { plan } = req.body || {};
1884
+ if (!plan || !VALID_PLANS.includes(plan)) {
1885
+ res.status(400).json({ error: `Invalid plan. Must be one of: ${VALID_PLANS.join(', ')}` });
1886
+ return;
1887
+ }
1888
+ const workspaceId = param(req, 'id');
1889
+ const workspace = workspaceStore.getById(workspaceId);
1890
+ if (!workspace) {
1891
+ res.status(404).json({ error: 'Workspace not found' });
1892
+ return;
1893
+ }
1894
+ const updated = workspaceStore.update(workspaceId, { plan, updated_at: new Date().toISOString() });
1895
+ res.json(updated);
1896
+ });
1564
1897
  return router;
1565
1898
  }
1566
1899
  //# sourceMappingURL=routes.js.map