palaryn 0.1.0 → 0.3.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 (328) 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/audit/logger.d.ts +10 -0
  5. package/dist/src/audit/logger.d.ts.map +1 -1
  6. package/dist/src/audit/logger.js +52 -38
  7. package/dist/src/audit/logger.js.map +1 -1
  8. package/dist/src/auth/routes.js.map +1 -1
  9. package/dist/src/budget/manager.d.ts +5 -0
  10. package/dist/src/budget/manager.d.ts.map +1 -1
  11. package/dist/src/budget/manager.js +32 -0
  12. package/dist/src/budget/manager.js.map +1 -1
  13. package/dist/src/budget/model-pricing.d.ts +20 -0
  14. package/dist/src/budget/model-pricing.d.ts.map +1 -0
  15. package/dist/src/budget/model-pricing.js +107 -0
  16. package/dist/src/budget/model-pricing.js.map +1 -0
  17. package/dist/src/budget/usage-extractor.d.ts +3 -1
  18. package/dist/src/budget/usage-extractor.d.ts.map +1 -1
  19. package/dist/src/budget/usage-extractor.js +47 -3
  20. package/dist/src/budget/usage-extractor.js.map +1 -1
  21. package/dist/src/config/defaults.d.ts.map +1 -1
  22. package/dist/src/config/defaults.js +65 -13
  23. package/dist/src/config/defaults.js.map +1 -1
  24. package/dist/src/dlp/tool-patterns.d.ts +7 -0
  25. package/dist/src/dlp/tool-patterns.d.ts.map +1 -0
  26. package/dist/src/dlp/tool-patterns.js +34 -0
  27. package/dist/src/dlp/tool-patterns.js.map +1 -0
  28. package/dist/src/executor/filesystem-executor.d.ts +28 -0
  29. package/dist/src/executor/filesystem-executor.d.ts.map +1 -0
  30. package/dist/src/executor/filesystem-executor.js +192 -0
  31. package/dist/src/executor/filesystem-executor.js.map +1 -0
  32. package/dist/src/executor/http-executor.d.ts.map +1 -1
  33. package/dist/src/executor/http-executor.js +4 -0
  34. package/dist/src/executor/http-executor.js.map +1 -1
  35. package/dist/src/executor/index.d.ts +4 -0
  36. package/dist/src/executor/index.d.ts.map +1 -1
  37. package/dist/src/executor/index.js +9 -1
  38. package/dist/src/executor/index.js.map +1 -1
  39. package/dist/src/executor/shell-executor.d.ts +22 -0
  40. package/dist/src/executor/shell-executor.d.ts.map +1 -0
  41. package/dist/src/executor/shell-executor.js +119 -0
  42. package/dist/src/executor/shell-executor.js.map +1 -0
  43. package/dist/src/executor/sql-executor.d.ts +29 -0
  44. package/dist/src/executor/sql-executor.d.ts.map +1 -0
  45. package/dist/src/executor/sql-executor.js +114 -0
  46. package/dist/src/executor/sql-executor.js.map +1 -0
  47. package/dist/src/executor/websocket-executor.d.ts +26 -0
  48. package/dist/src/executor/websocket-executor.d.ts.map +1 -0
  49. package/dist/src/executor/websocket-executor.js +205 -0
  50. package/dist/src/executor/websocket-executor.js.map +1 -0
  51. package/dist/src/interceptor/index.d.ts +2 -0
  52. package/dist/src/interceptor/index.d.ts.map +1 -0
  53. package/dist/src/interceptor/index.js +6 -0
  54. package/dist/src/interceptor/index.js.map +1 -0
  55. package/dist/src/interceptor/provider-interceptor.d.ts +36 -0
  56. package/dist/src/interceptor/provider-interceptor.d.ts.map +1 -0
  57. package/dist/src/interceptor/provider-interceptor.js +302 -0
  58. package/dist/src/interceptor/provider-interceptor.js.map +1 -0
  59. package/dist/src/mcp/auth-verifier.d.ts.map +1 -1
  60. package/dist/src/mcp/auth-verifier.js +3 -2
  61. package/dist/src/mcp/auth-verifier.js.map +1 -1
  62. package/dist/src/mcp/bridge.d.ts +14 -10
  63. package/dist/src/mcp/bridge.d.ts.map +1 -1
  64. package/dist/src/mcp/bridge.js +51 -227
  65. package/dist/src/mcp/bridge.js.map +1 -1
  66. package/dist/src/mcp/http-transport.d.ts.map +1 -1
  67. package/dist/src/mcp/http-transport.js +101 -65
  68. package/dist/src/mcp/http-transport.js.map +1 -1
  69. package/dist/src/mcp/tool-definitions.d.ts +41 -0
  70. package/dist/src/mcp/tool-definitions.d.ts.map +1 -0
  71. package/dist/src/mcp/tool-definitions.js +491 -0
  72. package/dist/src/mcp/tool-definitions.js.map +1 -0
  73. package/dist/src/middleware/auth.js.map +1 -1
  74. package/dist/src/middleware/session.js.map +1 -1
  75. package/dist/src/middleware/validate.d.ts +8 -0
  76. package/dist/src/middleware/validate.d.ts.map +1 -1
  77. package/dist/src/middleware/validate.js +45 -0
  78. package/dist/src/middleware/validate.js.map +1 -1
  79. package/dist/src/policy/engine.d.ts +4 -0
  80. package/dist/src/policy/engine.d.ts.map +1 -1
  81. package/dist/src/policy/engine.js +117 -0
  82. package/dist/src/policy/engine.js.map +1 -1
  83. package/dist/src/saas/routes.d.ts.map +1 -1
  84. package/dist/src/saas/routes.js +327 -10
  85. package/dist/src/saas/routes.js.map +1 -1
  86. package/dist/src/server/app.d.ts.map +1 -1
  87. package/dist/src/server/app.js +19 -2
  88. package/dist/src/server/app.js.map +1 -1
  89. package/dist/src/server/gateway.d.ts.map +1 -1
  90. package/dist/src/server/gateway.js +17 -0
  91. package/dist/src/server/gateway.js.map +1 -1
  92. package/dist/src/server/index.d.ts.map +1 -1
  93. package/dist/src/server/index.js +18 -0
  94. package/dist/src/server/index.js.map +1 -1
  95. package/dist/src/storage/interfaces.d.ts +14 -3
  96. package/dist/src/storage/interfaces.d.ts.map +1 -1
  97. package/dist/src/storage/memory.d.ts +2 -0
  98. package/dist/src/storage/memory.d.ts.map +1 -1
  99. package/dist/src/storage/memory.js +6 -0
  100. package/dist/src/storage/memory.js.map +1 -1
  101. package/dist/src/storage/postgres.d.ts +5 -0
  102. package/dist/src/storage/postgres.d.ts.map +1 -1
  103. package/dist/src/storage/postgres.js +16 -0
  104. package/dist/src/storage/postgres.js.map +1 -1
  105. package/dist/src/storage/redis.d.ts +10 -0
  106. package/dist/src/storage/redis.d.ts.map +1 -1
  107. package/dist/src/storage/redis.js +65 -0
  108. package/dist/src/storage/redis.js.map +1 -1
  109. package/dist/src/types/budget.d.ts +4 -0
  110. package/dist/src/types/budget.d.ts.map +1 -1
  111. package/dist/src/types/config.d.ts +58 -0
  112. package/dist/src/types/config.d.ts.map +1 -1
  113. package/dist/src/types/events.d.ts +1 -0
  114. package/dist/src/types/events.d.ts.map +1 -1
  115. package/dist/src/types/policy.d.ts +11 -1
  116. package/dist/src/types/policy.d.ts.map +1 -1
  117. package/dist/src/types/tool-result.d.ts +11 -0
  118. package/dist/src/types/tool-result.d.ts.map +1 -1
  119. package/dist/tests/unit/app-routes.test.d.ts +2 -0
  120. package/dist/tests/unit/app-routes.test.d.ts.map +1 -0
  121. package/dist/tests/unit/app-routes.test.js +715 -0
  122. package/dist/tests/unit/app-routes.test.js.map +1 -0
  123. package/dist/tests/unit/audit-logger.test.js +105 -0
  124. package/dist/tests/unit/audit-logger.test.js.map +1 -1
  125. package/dist/tests/unit/auth-providers.test.d.ts +2 -0
  126. package/dist/tests/unit/auth-providers.test.d.ts.map +1 -0
  127. package/dist/tests/unit/auth-providers.test.js +279 -0
  128. package/dist/tests/unit/auth-providers.test.js.map +1 -0
  129. package/dist/tests/unit/auth-routes-extended.test.d.ts +2 -0
  130. package/dist/tests/unit/auth-routes-extended.test.d.ts.map +1 -0
  131. package/dist/tests/unit/auth-routes-extended.test.js +993 -0
  132. package/dist/tests/unit/auth-routes-extended.test.js.map +1 -0
  133. package/dist/tests/unit/auth-verifier.test.d.ts +2 -0
  134. package/dist/tests/unit/auth-verifier.test.d.ts.map +1 -0
  135. package/dist/tests/unit/auth-verifier.test.js +505 -0
  136. package/dist/tests/unit/auth-verifier.test.js.map +1 -0
  137. package/dist/tests/unit/billing-routes.test.d.ts +2 -0
  138. package/dist/tests/unit/billing-routes.test.d.ts.map +1 -0
  139. package/dist/tests/unit/billing-routes.test.js +432 -0
  140. package/dist/tests/unit/billing-routes.test.js.map +1 -0
  141. package/dist/tests/unit/config-defaults.test.d.ts +2 -0
  142. package/dist/tests/unit/config-defaults.test.d.ts.map +1 -0
  143. package/dist/tests/unit/config-defaults.test.js +119 -0
  144. package/dist/tests/unit/config-defaults.test.js.map +1 -0
  145. package/dist/tests/unit/defaults.test.js +0 -10
  146. package/dist/tests/unit/defaults.test.js.map +1 -1
  147. package/dist/tests/unit/filesystem-executor.test.d.ts +2 -0
  148. package/dist/tests/unit/filesystem-executor.test.d.ts.map +1 -0
  149. package/dist/tests/unit/filesystem-executor.test.js +280 -0
  150. package/dist/tests/unit/filesystem-executor.test.js.map +1 -0
  151. package/dist/tests/unit/gateway-branches.test.d.ts +2 -0
  152. package/dist/tests/unit/gateway-branches.test.d.ts.map +1 -0
  153. package/dist/tests/unit/gateway-branches.test.js +1039 -0
  154. package/dist/tests/unit/gateway-branches.test.js.map +1 -0
  155. package/dist/tests/unit/http-executor-branches.test.d.ts +2 -0
  156. package/dist/tests/unit/http-executor-branches.test.d.ts.map +1 -0
  157. package/dist/tests/unit/http-executor-branches.test.js +495 -0
  158. package/dist/tests/unit/http-executor-branches.test.js.map +1 -0
  159. package/dist/tests/unit/logger.test.d.ts +2 -0
  160. package/dist/tests/unit/logger.test.d.ts.map +1 -0
  161. package/dist/tests/unit/logger.test.js +97 -0
  162. package/dist/tests/unit/logger.test.js.map +1 -0
  163. package/dist/tests/unit/metrics.test.js +102 -0
  164. package/dist/tests/unit/metrics.test.js.map +1 -1
  165. package/dist/tests/unit/model-pricing.test.d.ts +2 -0
  166. package/dist/tests/unit/model-pricing.test.d.ts.map +1 -0
  167. package/dist/tests/unit/model-pricing.test.js +87 -0
  168. package/dist/tests/unit/model-pricing.test.js.map +1 -0
  169. package/dist/tests/unit/oauth-stores.test.d.ts +2 -0
  170. package/dist/tests/unit/oauth-stores.test.d.ts.map +1 -0
  171. package/dist/tests/unit/oauth-stores.test.js +260 -0
  172. package/dist/tests/unit/oauth-stores.test.js.map +1 -0
  173. package/dist/tests/unit/policy-engine.test.js +466 -0
  174. package/dist/tests/unit/policy-engine.test.js.map +1 -1
  175. package/dist/tests/unit/provider-interceptor.test.d.ts +2 -0
  176. package/dist/tests/unit/provider-interceptor.test.d.ts.map +1 -0
  177. package/dist/tests/unit/provider-interceptor.test.js +472 -0
  178. package/dist/tests/unit/provider-interceptor.test.js.map +1 -0
  179. package/dist/tests/unit/saas-routes-branches.test.d.ts +2 -0
  180. package/dist/tests/unit/saas-routes-branches.test.d.ts.map +1 -0
  181. package/dist/tests/unit/saas-routes-branches.test.js +2040 -0
  182. package/dist/tests/unit/saas-routes-branches.test.js.map +1 -0
  183. package/dist/tests/unit/saas-routes-crud.test.d.ts +2 -0
  184. package/dist/tests/unit/saas-routes-crud.test.d.ts.map +1 -0
  185. package/dist/tests/unit/saas-routes-crud.test.js +332 -0
  186. package/dist/tests/unit/saas-routes-crud.test.js.map +1 -0
  187. package/dist/tests/unit/saas-routes-data.test.d.ts +2 -0
  188. package/dist/tests/unit/saas-routes-data.test.d.ts.map +1 -0
  189. package/dist/tests/unit/saas-routes-data.test.js +405 -0
  190. package/dist/tests/unit/saas-routes-data.test.js.map +1 -0
  191. package/dist/tests/unit/saas-routes.test.js +3 -3
  192. package/dist/tests/unit/saas-routes.test.js.map +1 -1
  193. package/dist/tests/unit/shell-executor.test.d.ts +2 -0
  194. package/dist/tests/unit/shell-executor.test.d.ts.map +1 -0
  195. package/dist/tests/unit/shell-executor.test.js +145 -0
  196. package/dist/tests/unit/shell-executor.test.js.map +1 -0
  197. package/dist/tests/unit/sql-executor.test.d.ts +2 -0
  198. package/dist/tests/unit/sql-executor.test.d.ts.map +1 -0
  199. package/dist/tests/unit/sql-executor.test.js +177 -0
  200. package/dist/tests/unit/sql-executor.test.js.map +1 -0
  201. package/dist/tests/unit/stream-proxy.test.d.ts +2 -0
  202. package/dist/tests/unit/stream-proxy.test.d.ts.map +1 -0
  203. package/dist/tests/unit/stream-proxy.test.js +147 -0
  204. package/dist/tests/unit/stream-proxy.test.js.map +1 -0
  205. package/dist/tests/unit/tool-definitions.test.d.ts +2 -0
  206. package/dist/tests/unit/tool-definitions.test.d.ts.map +1 -0
  207. package/dist/tests/unit/tool-definitions.test.js +184 -0
  208. package/dist/tests/unit/tool-definitions.test.js.map +1 -0
  209. package/dist/tests/unit/usage-extractor.test.js +140 -0
  210. package/dist/tests/unit/usage-extractor.test.js.map +1 -1
  211. package/dist/tests/unit/webhook-handler.test.d.ts +2 -0
  212. package/dist/tests/unit/webhook-handler.test.d.ts.map +1 -0
  213. package/dist/tests/unit/webhook-handler.test.js +453 -0
  214. package/dist/tests/unit/webhook-handler.test.js.map +1 -0
  215. package/dist/tests/unit/webhook-routes.test.d.ts +2 -0
  216. package/dist/tests/unit/webhook-routes.test.d.ts.map +1 -0
  217. package/dist/tests/unit/webhook-routes.test.js +69 -0
  218. package/dist/tests/unit/webhook-routes.test.js.map +1 -0
  219. package/dist/tests/unit/websocket-executor.test.d.ts +2 -0
  220. package/dist/tests/unit/websocket-executor.test.d.ts.map +1 -0
  221. package/dist/tests/unit/websocket-executor.test.js +121 -0
  222. package/dist/tests/unit/websocket-executor.test.js.map +1 -0
  223. package/package.json +8 -2
  224. package/policy-packs/demo_fail.yaml +41 -0
  225. package/policy-packs/full_tools.yaml +136 -0
  226. package/src/admin/index.ts +1 -0
  227. package/src/admin/routes.ts +509 -0
  228. package/src/admin/templates.ts +572 -0
  229. package/src/anomaly/detector.ts +717 -0
  230. package/src/anomaly/index.ts +1 -0
  231. package/src/approval/manager.ts +569 -0
  232. package/src/approval/webhook.ts +133 -0
  233. package/src/audit/logger.ts +490 -0
  234. package/src/auth/index.ts +5 -0
  235. package/src/auth/password.ts +21 -0
  236. package/src/auth/pkce.ts +22 -0
  237. package/src/auth/providers.ts +208 -0
  238. package/src/auth/routes.ts +521 -0
  239. package/src/auth/session.ts +84 -0
  240. package/src/billing/index.ts +6 -0
  241. package/src/billing/plan-enforcer.ts +135 -0
  242. package/src/billing/routes.ts +229 -0
  243. package/src/billing/stripe-client.ts +58 -0
  244. package/src/billing/webhook-handler.ts +182 -0
  245. package/src/billing/webhook-routes.ts +28 -0
  246. package/src/budget/manager.ts +679 -0
  247. package/src/budget/model-pricing.ts +119 -0
  248. package/src/budget/usage-extractor.ts +214 -0
  249. package/src/cli.ts +91 -0
  250. package/src/config/defaults.ts +261 -0
  251. package/src/config/validate.ts +88 -0
  252. package/src/dlp/composite-scanner.ts +213 -0
  253. package/src/dlp/index.ts +9 -0
  254. package/src/dlp/interfaces.ts +34 -0
  255. package/src/dlp/patterns.ts +30 -0
  256. package/src/dlp/prompt-injection-backend.ts +181 -0
  257. package/src/dlp/prompt-injection-patterns.ts +302 -0
  258. package/src/dlp/regex-backend.ts +181 -0
  259. package/src/dlp/scanner.ts +502 -0
  260. package/src/dlp/text-normalizer.ts +225 -0
  261. package/src/dlp/tool-patterns.ts +35 -0
  262. package/src/dlp/trufflehog-backend.ts +190 -0
  263. package/src/executor/filesystem-executor.ts +196 -0
  264. package/src/executor/http-executor.ts +330 -0
  265. package/src/executor/index.ts +9 -0
  266. package/src/executor/interfaces.ts +11 -0
  267. package/src/executor/noop-executor.ts +23 -0
  268. package/src/executor/registry.ts +64 -0
  269. package/src/executor/shell-executor.ts +148 -0
  270. package/src/executor/slack-executor.ts +176 -0
  271. package/src/executor/sql-executor.ts +146 -0
  272. package/src/executor/websocket-executor.ts +211 -0
  273. package/src/index.ts +24 -0
  274. package/src/interceptor/index.ts +1 -0
  275. package/src/interceptor/provider-interceptor.ts +315 -0
  276. package/src/mcp/auth-verifier.ts +152 -0
  277. package/src/mcp/bridge.ts +703 -0
  278. package/src/mcp/http-transport.ts +672 -0
  279. package/src/mcp/index.ts +9 -0
  280. package/src/mcp/oauth-pages.ts +139 -0
  281. package/src/mcp/oauth-postgres-stores.ts +278 -0
  282. package/src/mcp/oauth-provider.ts +536 -0
  283. package/src/mcp/oauth-stores.ts +202 -0
  284. package/src/mcp/server.ts +55 -0
  285. package/src/mcp/tool-definitions.ts +562 -0
  286. package/src/metrics/collector.ts +357 -0
  287. package/src/metrics/index.ts +1 -0
  288. package/src/middleware/auth.ts +814 -0
  289. package/src/middleware/session.ts +85 -0
  290. package/src/middleware/validate.ts +130 -0
  291. package/src/policy/engine.ts +815 -0
  292. package/src/policy/index.ts +2 -0
  293. package/src/policy/opa-engine.ts +829 -0
  294. package/src/proxy/forward-proxy.ts +649 -0
  295. package/src/proxy/index.ts +1 -0
  296. package/src/ratelimit/limiter.ts +196 -0
  297. package/src/replay/engine.ts +142 -0
  298. package/src/replay/index.ts +1 -0
  299. package/src/saas/index.ts +1 -0
  300. package/src/saas/routes.ts +2161 -0
  301. package/src/server/app.ts +981 -0
  302. package/src/server/errors.ts +49 -0
  303. package/src/server/gateway.ts +1130 -0
  304. package/src/server/index.ts +307 -0
  305. package/src/server/logger.ts +255 -0
  306. package/src/server/stream-proxy.ts +202 -0
  307. package/src/storage/file-persistence.ts +315 -0
  308. package/src/storage/index.ts +4 -0
  309. package/src/storage/interfaces.ts +287 -0
  310. package/src/storage/memory.ts +686 -0
  311. package/src/storage/postgres.ts +1831 -0
  312. package/src/storage/redis.ts +835 -0
  313. package/src/tracing/index.ts +1 -0
  314. package/src/tracing/provider.ts +100 -0
  315. package/src/trust/calculator.ts +141 -0
  316. package/src/trust/index.ts +7 -0
  317. package/src/types/budget.ts +36 -0
  318. package/src/types/config.ts +278 -0
  319. package/src/types/events.ts +41 -0
  320. package/src/types/express.d.ts +14 -0
  321. package/src/types/index.ts +7 -0
  322. package/src/types/policy.ts +83 -0
  323. package/src/types/stripe-config.ts +11 -0
  324. package/src/types/subscription.ts +59 -0
  325. package/src/types/tool-call.ts +47 -0
  326. package/src/types/tool-result.ts +82 -0
  327. package/src/types/user.ts +125 -0
  328. 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:
@@ -862,20 +1090,38 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
862
1090
  }
863
1091
  const limit = Math.min(parseInt(req.query.limit) || 50, 200);
864
1092
  const offset = parseInt(req.query.offset) || 0;
1093
+ const severity = req.query.severity;
1094
+ const tool = req.query.tool;
1095
+ const actor = req.query.actor;
1096
+ const q = req.query.q;
865
1097
  const allDlpEvents = gateway.getAuditLogger().getEventsByType('DLP_SCANNED');
866
1098
  const workspace = workspaceStore.getById(workspaceId);
867
1099
  const wsSlug = workspace?.slug;
868
1100
  const wsEvents = allDlpEvents
869
1101
  .filter(e => e.workspace_id === workspaceId || (wsSlug && e.workspace_id === wsSlug))
870
1102
  .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
871
- // Compute severity stats
1103
+ // Apply optional filters
1104
+ let filtered = wsEvents;
1105
+ if (severity)
1106
+ filtered = filtered.filter(e => e.metadata?.severity === severity);
1107
+ if (tool)
1108
+ filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(tool.toLowerCase()));
1109
+ if (actor)
1110
+ filtered = filtered.filter(e => e.actor_id.toLowerCase().includes(actor.toLowerCase()));
1111
+ if (q) {
1112
+ const ql = q.toLowerCase();
1113
+ filtered = filtered.filter(e => e.tool_name.toLowerCase().includes(ql) ||
1114
+ e.actor_id.toLowerCase().includes(ql) ||
1115
+ (e.metadata?.detected || []).some(p => p.toLowerCase().includes(ql)));
1116
+ }
1117
+ // Compute severity stats over filtered results
872
1118
  let high = 0, medium = 0, low = 0;
873
1119
  const patternSet = new Set();
874
- for (const e of wsEvents) {
875
- const severity = e.metadata?.severity;
876
- if (severity === 'high')
1120
+ for (const e of filtered) {
1121
+ const sev = e.metadata?.severity;
1122
+ if (sev === 'high')
877
1123
  high++;
878
- else if (severity === 'medium')
1124
+ else if (sev === 'medium')
879
1125
  medium++;
880
1126
  else
881
1127
  low++;
@@ -886,12 +1132,12 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
886
1132
  }
887
1133
  }
888
1134
  res.json({
889
- events: wsEvents.slice(offset, offset + limit),
890
- total: wsEvents.length,
1135
+ events: filtered.slice(offset, offset + limit),
1136
+ total: filtered.length,
891
1137
  limit,
892
1138
  offset,
893
1139
  stats: {
894
- total: wsEvents.length,
1140
+ total: filtered.length,
895
1141
  high,
896
1142
  medium,
897
1143
  low,
@@ -1140,11 +1386,16 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
1140
1386
  const denyCount = allStats.reduce((acc, s) => acc + (s.policy_breakdown['deny'] || 0), 0);
1141
1387
  const shield_score = {
1142
1388
  score: totalDecisions > 0 ? Math.round(((allowCount + transformCount) / totalDecisions) * 100) : 100,
1389
+ total: totalDecisions,
1143
1390
  breakdown: {
1144
1391
  allowed_percent: totalDecisions > 0 ? Math.round((allowCount / totalDecisions) * 100) : 100,
1145
1392
  transformed_percent: totalDecisions > 0 ? Math.round((transformCount / totalDecisions) * 100) : 0,
1146
1393
  approval_percent: totalDecisions > 0 ? Math.round((approvalCount / totalDecisions) * 100) : 0,
1147
1394
  blocked_percent: totalDecisions > 0 ? Math.round((denyCount / totalDecisions) * 100) : 0,
1395
+ allowed_count: allowCount,
1396
+ transformed_count: transformCount,
1397
+ approval_count: approvalCount,
1398
+ blocked_count: denyCount,
1148
1399
  },
1149
1400
  };
1150
1401
  // Pipeline throughput from stats (merged across all matching workspace IDs)
@@ -1561,6 +1812,72 @@ Output: {"name":"approve-slack-writes","description":"Require approval for write
1561
1812
  });
1562
1813
  res.json({ status: 'reset', config: config.budget, is_custom: false });
1563
1814
  });
1815
+ // ---------------------------------------------------------------------------
1816
+ // Model Pricing: Get merged pricing (built-in + workspace overrides)
1817
+ // ---------------------------------------------------------------------------
1818
+ router.get('/workspaces/:id/model-pricing', (req, res) => {
1819
+ if (!requireSession(req, res))
1820
+ return;
1821
+ const user = req.sessionUser;
1822
+ const workspaceId = param(req, 'id');
1823
+ const membership = workspaceMemberStore.getByWorkspaceAndUser(workspaceId, user.id);
1824
+ if (!membership) {
1825
+ res.status(403).json({ error: 'Not a member of this workspace' });
1826
+ return;
1827
+ }
1828
+ // Get workspace-level budget config overrides (if any)
1829
+ const wsConfig = budgetConfigStore.getByWorkspaceId(workspaceId);
1830
+ const workspaceOverrides = wsConfig?.token_pricing;
1831
+ // Merge: built-in pricing as base, workspace overrides on top
1832
+ const merged = {};
1833
+ for (const [model, pricing] of Object.entries(model_pricing_1.MODEL_PRICING)) {
1834
+ merged[model] = {
1835
+ input_per_token: pricing.input_per_token,
1836
+ output_per_token: pricing.output_per_token,
1837
+ source: 'built-in',
1838
+ };
1839
+ }
1840
+ if (workspaceOverrides) {
1841
+ for (const [model, pricing] of Object.entries(workspaceOverrides)) {
1842
+ merged[model] = {
1843
+ input_per_token: pricing.input_per_token,
1844
+ output_per_token: pricing.output_per_token,
1845
+ source: 'workspace',
1846
+ };
1847
+ }
1848
+ }
1849
+ res.json({
1850
+ models: merged,
1851
+ has_workspace_overrides: !!workspaceOverrides,
1852
+ });
1853
+ });
1854
+ // ---------------------------------------------------------------------------
1855
+ // Admin: Update Workspace Plan (platform admin only)
1856
+ // ---------------------------------------------------------------------------
1857
+ const VALID_PLANS = ['free', 'pro', 'business', 'enterprise'];
1858
+ router.put('/workspaces/:id/plan', (req, res) => {
1859
+ if (!requireSession(req, res))
1860
+ return;
1861
+ const user = req.sessionUser;
1862
+ const adminEmail = process.env.SEED_ADMIN_EMAIL;
1863
+ if (!adminEmail || user.email !== adminEmail) {
1864
+ res.status(403).json({ error: 'Platform admin access required' });
1865
+ return;
1866
+ }
1867
+ const { plan } = req.body || {};
1868
+ if (!plan || !VALID_PLANS.includes(plan)) {
1869
+ res.status(400).json({ error: `Invalid plan. Must be one of: ${VALID_PLANS.join(', ')}` });
1870
+ return;
1871
+ }
1872
+ const workspaceId = param(req, 'id');
1873
+ const workspace = workspaceStore.getById(workspaceId);
1874
+ if (!workspace) {
1875
+ res.status(404).json({ error: 'Workspace not found' });
1876
+ return;
1877
+ }
1878
+ const updated = workspaceStore.update(workspaceId, { plan, updated_at: new Date().toISOString() });
1879
+ res.json(updated);
1880
+ });
1564
1881
  return router;
1565
1882
  }
1566
1883
  //# sourceMappingURL=routes.js.map