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,490 @@
1
+ import { randomUUID } from 'crypto';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { AuditEvent, EventType, IncidentEvent, IncidentSeverity } from '../types/events';
5
+ import { AuditConfig } from '../types/config';
6
+ import { AuditStore } from '../storage/interfaces';
7
+ import { ToolCall } from '../types/tool-call';
8
+ import { DLPRedaction } from '../types/tool-result';
9
+
10
+ /** Default maximum in-memory events before oldest are evicted */
11
+ const DEFAULT_MAX_BUFFER_SIZE = 50000;
12
+
13
+ export class AuditLogger {
14
+ private config: AuditConfig;
15
+ private events: AuditEvent[];
16
+ private logStream: fs.WriteStream | null;
17
+ private externalStore?: AuditStore;
18
+ private maxBufferSize: number;
19
+
20
+ constructor(config: AuditConfig, maxBufferSize?: number) {
21
+ this.config = config;
22
+ this.events = [];
23
+ this.maxBufferSize = maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
24
+ this.logStream = null;
25
+
26
+ if (config.enabled && config.log_dir) {
27
+ if (!fs.existsSync(config.log_dir)) {
28
+ fs.mkdirSync(config.log_dir, { recursive: true });
29
+ }
30
+
31
+ const today = new Date().toISOString().split('T')[0];
32
+ const logFile = path.join(config.log_dir, `audit-${today}.jsonl`);
33
+ this.logStream = fs.createWriteStream(logFile, { flags: 'a' });
34
+ this.logStream.on('error', () => {
35
+ // Silently handle write errors (e.g. directory deleted during cleanup)
36
+ this.logStream = null;
37
+ });
38
+ }
39
+ }
40
+
41
+ /** Attach an external audit store (e.g. Redis, Postgres) for durable event persistence */
42
+ setStore(store: AuditStore): void {
43
+ this.externalStore = store;
44
+ }
45
+
46
+ /**
47
+ * Populate the in-memory event buffer from the external store.
48
+ * Call this after `setStore()` and after the store has been hydrated
49
+ * so that `getAllEvents()` returns persisted events across restarts.
50
+ */
51
+ hydrateFromStore(): void {
52
+ if (!this.externalStore) return;
53
+ const stored = this.externalStore.getAll();
54
+ if (stored.length > 0) {
55
+ // Merge: keep any events already in memory (logged before hydration)
56
+ const existingIds = new Set(this.events.map(e => e.event_id));
57
+ const newEvents = stored.filter(e => !existingIds.has(e.event_id));
58
+ this.events = [...newEvents, ...this.events];
59
+ // Enforce buffer limit
60
+ if (this.events.length > this.maxBufferSize) {
61
+ this.events = this.events.slice(-this.maxBufferSize);
62
+ }
63
+ }
64
+ }
65
+
66
+ log(event: Omit<AuditEvent, 'event_id' | 'timestamp'>): AuditEvent {
67
+ const fullEvent: AuditEvent = {
68
+ ...event,
69
+ event_id: randomUUID(),
70
+ timestamp: new Date().toISOString(),
71
+ };
72
+
73
+ // Write-ahead pattern: persist to file stream BEFORE in-memory push.
74
+ // This ensures the event is durably written before the caller gets
75
+ // acknowledgment. If the stream write fails, we still add to memory
76
+ // for query purposes but log a warning about the persistence failure.
77
+ if (this.logStream) {
78
+ try {
79
+ this.logStream.write(JSON.stringify(fullEvent) + '\n');
80
+ } catch (err) {
81
+ console.warn('Audit log stream write failed, event added to memory only:', (err as Error).message);
82
+ }
83
+ }
84
+
85
+ this.events.push(fullEvent);
86
+
87
+ // Evict oldest events if in-memory buffer exceeds capacity
88
+ if (this.events.length > this.maxBufferSize) {
89
+ this.events = this.events.slice(-this.maxBufferSize);
90
+ }
91
+
92
+ // Persist to external store (fire-and-forget write-through)
93
+ if (this.externalStore) {
94
+ this.externalStore.append(fullEvent);
95
+ }
96
+
97
+ if (this.config.console_output) {
98
+ console.log(JSON.stringify({
99
+ level: 'info',
100
+ event_type: fullEvent.event_type,
101
+ event_id: fullEvent.event_id,
102
+ tool_call_id: fullEvent.tool_call_id,
103
+ task_id: fullEvent.task_id,
104
+ actor_id: fullEvent.actor_id,
105
+ tool_name: fullEvent.tool_name,
106
+ timestamp: fullEvent.timestamp,
107
+ metadata: fullEvent.metadata,
108
+ }));
109
+ }
110
+
111
+ return fullEvent;
112
+ }
113
+
114
+ /** Common fields shared by all tool-call audit events. */
115
+ private baseFields(toolCall: ToolCall) {
116
+ return {
117
+ tool_call_id: toolCall.tool_call_id,
118
+ session_id: toolCall.source?.session_id,
119
+ task_id: toolCall.task_id,
120
+ workspace_id: toolCall.workspace_id,
121
+ actor_id: toolCall.actor.id,
122
+ tool_name: toolCall.tool.name,
123
+ };
124
+ }
125
+
126
+ logToolCallReceived(toolCall: ToolCall): AuditEvent {
127
+ return this.log({
128
+ event_type: 'TOOL_CALL_RECEIVED',
129
+ ...this.baseFields(toolCall),
130
+ metadata: {
131
+ capability: toolCall.tool.capability,
132
+ platform: toolCall.source.platform,
133
+ has_constraints: !!toolCall.constraints,
134
+ labels: toolCall.context?.labels || [],
135
+ args_snapshot: this.redactArgs(toolCall.args),
136
+ },
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Retrieve the stored args snapshot for a specific tool_call_id.
142
+ * Returns undefined if no TOOL_CALL_RECEIVED event exists for the given id.
143
+ */
144
+ getToolCallArgs(toolCallId: string): Record<string, unknown> | undefined {
145
+ const event = this.events.find(
146
+ e => e.event_type === 'TOOL_CALL_RECEIVED' && e.tool_call_id === toolCallId
147
+ );
148
+ return event?.metadata?.args_snapshot as Record<string, unknown> | undefined;
149
+ }
150
+
151
+ /**
152
+ * Deep-clone args and strip sensitive fields (headers and top-level keys).
153
+ */
154
+ private redactArgs(args: Record<string, unknown>): Record<string, unknown> {
155
+ const SENSITIVE_HEADERS = ['authorization', 'cookie', 'x-api-key', 'x-secret'];
156
+ const SENSITIVE_KEYS = ['password', 'secret', 'token', 'api_key'];
157
+
158
+ const cloned = JSON.parse(JSON.stringify(args)) as Record<string, unknown>;
159
+
160
+ // Strip sensitive top-level keys
161
+ for (const key of Object.keys(cloned)) {
162
+ if (SENSITIVE_KEYS.includes(key.toLowerCase())) {
163
+ cloned[key] = '[REDACTED]';
164
+ }
165
+ }
166
+
167
+ // Strip sensitive headers
168
+ if (cloned.headers && typeof cloned.headers === 'object' && !Array.isArray(cloned.headers)) {
169
+ const headers = cloned.headers as Record<string, unknown>;
170
+ for (const key of Object.keys(headers)) {
171
+ if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
172
+ headers[key] = '[REDACTED]';
173
+ }
174
+ }
175
+ }
176
+
177
+ return cloned;
178
+ }
179
+
180
+ logPolicyDecided(toolCall: ToolCall, decision: string, ruleId: string, reasons: string[]): AuditEvent {
181
+ return this.log({
182
+ event_type: 'POLICY_DECIDED',
183
+ ...this.baseFields(toolCall),
184
+ metadata: {
185
+ decision,
186
+ rule_id: ruleId,
187
+ reasons,
188
+ },
189
+ });
190
+ }
191
+
192
+ logDLPScanned(toolCall: ToolCall, detected: string[], severity: string, redactionCount: number, redactions?: DLPRedaction[]): AuditEvent {
193
+ return this.log({
194
+ event_type: 'DLP_SCANNED',
195
+ ...this.baseFields(toolCall),
196
+ metadata: {
197
+ detected,
198
+ severity,
199
+ redaction_count: redactionCount,
200
+ findings_count: detected.length,
201
+ redactions: redactions?.map(r => ({
202
+ path: r.path,
203
+ type: r.original_type,
204
+ method: r.method,
205
+ masked_preview: r.masked_preview,
206
+ })),
207
+ target_url: toolCall.args?.url,
208
+ actor_display: toolCall.actor.display || toolCall.actor.id,
209
+ },
210
+ });
211
+ }
212
+
213
+ logBudgetChecked(toolCall: ToolCall, estimatedCost: number, spentToDate: number, remaining: number): AuditEvent {
214
+ return this.log({
215
+ event_type: 'BUDGET_CHECKED',
216
+ ...this.baseFields(toolCall),
217
+ metadata: {
218
+ estimated_cost: estimatedCost,
219
+ spent_to_date: spentToDate,
220
+ remaining,
221
+ would_exceed: estimatedCost > remaining,
222
+ },
223
+ });
224
+ }
225
+
226
+ logToolExecuted(toolCall: ToolCall, status: string, durationMs: number, httpStatus?: number): AuditEvent {
227
+ return this.log({
228
+ event_type: 'TOOL_EXECUTED',
229
+ ...this.baseFields(toolCall),
230
+ metadata: {
231
+ status,
232
+ duration_ms: durationMs,
233
+ ...(httpStatus !== undefined && { http_status: httpStatus }),
234
+ },
235
+ });
236
+ }
237
+
238
+ logToolResultReturned(toolCall: ToolCall, status: string, durationMs: number, extraMetadata?: Record<string, unknown>): AuditEvent {
239
+ return this.log({
240
+ event_type: 'TOOL_RESULT_RETURNED',
241
+ ...this.baseFields(toolCall),
242
+ metadata: {
243
+ status,
244
+ duration_ms: durationMs,
245
+ ...extraMetadata,
246
+ },
247
+ });
248
+ }
249
+
250
+ logApprovalRequested(toolCall: ToolCall, scope: string, reason: string, ttlSeconds: number): AuditEvent {
251
+ return this.log({
252
+ event_type: 'APPROVAL_REQUESTED',
253
+ ...this.baseFields(toolCall),
254
+ metadata: {
255
+ scope,
256
+ reason,
257
+ ttl_seconds: ttlSeconds,
258
+ expires_at: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
259
+ },
260
+ });
261
+ }
262
+
263
+ logApprovalApproved(toolCall: ToolCall, approverId: string): AuditEvent {
264
+ return this.log({
265
+ event_type: 'APPROVAL_APPROVED',
266
+ tool_call_id: toolCall.tool_call_id,
267
+ task_id: toolCall.task_id,
268
+ workspace_id: toolCall.workspace_id,
269
+ actor_id: toolCall.actor.id,
270
+ tool_name: toolCall.tool.name,
271
+ metadata: {
272
+ approver_id: approverId,
273
+ },
274
+ });
275
+ }
276
+
277
+ logApprovalDenied(toolCall: ToolCall, approverId: string, reason: string): AuditEvent {
278
+ return this.log({
279
+ event_type: 'APPROVAL_DENIED',
280
+ tool_call_id: toolCall.tool_call_id,
281
+ task_id: toolCall.task_id,
282
+ workspace_id: toolCall.workspace_id,
283
+ actor_id: toolCall.actor.id,
284
+ tool_name: toolCall.tool.name,
285
+ metadata: {
286
+ approver_id: approverId,
287
+ reason,
288
+ },
289
+ });
290
+ }
291
+
292
+ logApprovalExpired(toolCall: ToolCall): AuditEvent {
293
+ return this.log({
294
+ event_type: 'APPROVAL_EXPIRED',
295
+ tool_call_id: toolCall.tool_call_id,
296
+ task_id: toolCall.task_id,
297
+ workspace_id: toolCall.workspace_id,
298
+ actor_id: toolCall.actor.id,
299
+ tool_name: toolCall.tool.name,
300
+ metadata: {},
301
+ });
302
+ }
303
+
304
+ logIncident(toolCall: ToolCall, severity: IncidentSeverity, incidentType: string, description: string, recommendedAction: string): AuditEvent {
305
+ return this.log({
306
+ event_type: 'INCIDENT_RAISED',
307
+ ...this.baseFields(toolCall),
308
+ metadata: {
309
+ severity,
310
+ incident_type: incidentType,
311
+ description,
312
+ recommended_action: recommendedAction,
313
+ },
314
+ });
315
+ }
316
+
317
+ getTaskTrace(taskId: string): AuditEvent[] {
318
+ return this.events
319
+ .filter(e => e.task_id === taskId)
320
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
321
+ }
322
+
323
+ getToolCallEvents(toolCallId: string): AuditEvent[] {
324
+ return this.events
325
+ .filter(e => e.tool_call_id === toolCallId)
326
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
327
+ }
328
+
329
+ getEventsByType(eventType: EventType): AuditEvent[] {
330
+ return this.events.filter(e => e.event_type === eventType);
331
+ }
332
+
333
+ getAllEvents(): AuditEvent[] {
334
+ return [...this.events];
335
+ }
336
+
337
+ /**
338
+ * Batch-flush helper: corks the stream (buffers writes), executes the
339
+ * callback, then uncorks to flush all buffered writes in a single I/O
340
+ * operation. Useful when logging many events in a tight loop.
341
+ *
342
+ * When called with no arguments, drains the write stream and returns
343
+ * a Promise that resolves when all buffered data has been flushed to
344
+ * the underlying resource. Use this before close() to ensure no
345
+ * in-flight audit events are lost.
346
+ */
347
+ flush(fn?: () => void): void | Promise<void> {
348
+ if (fn) {
349
+ this.logStream?.cork();
350
+ try {
351
+ fn();
352
+ } finally {
353
+ this.logStream?.uncork();
354
+ }
355
+ return;
356
+ }
357
+
358
+ // No-arg: drain the write stream so pending writes complete
359
+ if (!this.logStream) return Promise.resolve();
360
+ return new Promise<void>((resolve) => {
361
+ // If the stream buffer is already empty, resolve immediately
362
+ if (this.logStream!.writableLength === 0) {
363
+ resolve();
364
+ return;
365
+ }
366
+ this.logStream!.once('drain', () => resolve());
367
+ // Cork and uncork to trigger a drain
368
+ this.logStream!.cork();
369
+ this.logStream!.uncork();
370
+ });
371
+ }
372
+
373
+ close(): void {
374
+ if (this.logStream) {
375
+ this.logStream.end();
376
+ this.logStream = null;
377
+ }
378
+ }
379
+
380
+ /** Get aggregated event statistics for a workspace within a time window */
381
+ getEventStats(workspaceId: string, hours: number = 24): {
382
+ total_requests: number;
383
+ blocked_count: number;
384
+ approval_count: number;
385
+ error_count: number;
386
+ avg_duration_ms: number;
387
+ requests_per_minute: number;
388
+ active_agents: number;
389
+ policy_breakdown: Record<string, number>;
390
+ pipeline_throughput: { stage: string; passed: number; failed: number }[];
391
+ } {
392
+ const cutoff = new Date(Date.now() - hours * 3600000).toISOString();
393
+ const windowEvents = this.events.filter(
394
+ e => e.workspace_id === workspaceId && e.timestamp >= cutoff
395
+ );
396
+
397
+ // Count event types
398
+ const total_requests = windowEvents.filter(e => e.event_type === 'TOOL_CALL_RECEIVED').length;
399
+ const policyEvents = windowEvents.filter(e => e.event_type === 'POLICY_DECIDED');
400
+ const blocked_count = policyEvents.filter(e => e.metadata?.decision === 'deny').length;
401
+ const approval_count = windowEvents.filter(e => e.event_type === 'APPROVAL_REQUESTED').length;
402
+ const executedEvents = windowEvents.filter(e => e.event_type === 'TOOL_EXECUTED');
403
+ const error_count = executedEvents.filter(e => e.metadata?.status === 'error').length;
404
+
405
+ // Average duration from TOOL_RESULT_RETURNED
406
+ const resultEvents = windowEvents.filter(e => e.event_type === 'TOOL_RESULT_RETURNED');
407
+ const durations = resultEvents
408
+ .map(e => e.metadata?.duration_ms)
409
+ .filter((d): d is number => typeof d === 'number');
410
+ const avg_duration_ms = durations.length > 0
411
+ ? durations.reduce((a, b) => a + b, 0) / durations.length
412
+ : 0;
413
+
414
+ // Requests per minute — use actual event timespan (first→last), min 1 minute
415
+ const receivedEvents = windowEvents.filter(e => e.event_type === 'TOOL_CALL_RECEIVED');
416
+ let requests_per_minute = 0;
417
+ if (receivedEvents.length >= 2) {
418
+ const timestamps = receivedEvents.map(e => new Date(e.timestamp).getTime()).sort((a, b) => a - b);
419
+ const spanMinutes = Math.max((timestamps[timestamps.length - 1] - timestamps[0]) / 60000, 1);
420
+ requests_per_minute = receivedEvents.length / spanMinutes;
421
+ } else if (receivedEvents.length === 1) {
422
+ // Single request in window — report as 1 req/min (it happened)
423
+ requests_per_minute = 1;
424
+ }
425
+
426
+ // Distinct actor IDs
427
+ const actorIds = new Set(windowEvents.map(e => e.actor_id));
428
+ const active_agents = actorIds.size;
429
+
430
+ // Policy breakdown: group POLICY_DECIDED by decision
431
+ const policy_breakdown: Record<string, number> = {};
432
+ for (const pe of policyEvents) {
433
+ const decision = String(pe.metadata?.decision || 'unknown');
434
+ policy_breakdown[decision] = (policy_breakdown[decision] || 0) + 1;
435
+ }
436
+
437
+ // Pipeline throughput
438
+ const policyAllow = policyEvents.filter(
439
+ e => e.metadata?.decision === 'allow' || e.metadata?.decision === 'transform'
440
+ ).length;
441
+ const policyFail = policyEvents.filter(
442
+ e => e.metadata?.decision === 'deny' || e.metadata?.decision === 'require_approval'
443
+ ).length;
444
+ const dlpEvents = windowEvents.filter(e => e.event_type === 'DLP_SCANNED');
445
+ const budgetEvents = windowEvents.filter(e => e.event_type === 'BUDGET_CHECKED');
446
+ const execOk = executedEvents.filter(e => e.metadata?.status === 'ok').length;
447
+ const execFail = executedEvents.filter(e => e.metadata?.status !== 'ok').length;
448
+
449
+ const pipeline_throughput = [
450
+ { stage: 'auth', passed: total_requests, failed: 0 },
451
+ { stage: 'policy', passed: policyAllow, failed: policyFail },
452
+ { stage: 'dlp', passed: dlpEvents.length, failed: 0 },
453
+ { stage: 'budget', passed: budgetEvents.length, failed: 0 },
454
+ { stage: 'execute', passed: execOk, failed: execFail },
455
+ ];
456
+
457
+ return {
458
+ total_requests,
459
+ blocked_count,
460
+ approval_count,
461
+ error_count,
462
+ avg_duration_ms,
463
+ requests_per_minute,
464
+ active_agents,
465
+ policy_breakdown,
466
+ pipeline_throughput,
467
+ };
468
+ }
469
+
470
+ deleteByTaskId(taskId: string): void {
471
+ this.events = this.events.filter(e => e.task_id !== taskId);
472
+ if (this.externalStore) {
473
+ this.externalStore.deleteByTaskId(taskId);
474
+ }
475
+ }
476
+
477
+ deleteBySessionId(sessionId: string): void {
478
+ this.events = this.events.filter(e => e.session_id !== sessionId);
479
+ if (this.externalStore) {
480
+ this.externalStore.deleteBySessionId(sessionId);
481
+ }
482
+ }
483
+
484
+ clear(): void {
485
+ this.events = [];
486
+ if (this.externalStore) {
487
+ this.externalStore.clear();
488
+ }
489
+ }
490
+ }
@@ -0,0 +1,5 @@
1
+ export * from './pkce';
2
+ export * from './password';
3
+ export * from './providers';
4
+ export * from './session';
5
+ export * from './routes';
@@ -0,0 +1,21 @@
1
+ import { scrypt, randomBytes, timingSafeEqual } from 'crypto';
2
+ import { promisify } from 'util';
3
+
4
+ const scryptAsync = promisify(scrypt);
5
+ const SALT_LENGTH = 16;
6
+ const KEY_LENGTH = 64;
7
+
8
+ export async function hashPassword(password: string): Promise<string> {
9
+ const salt = randomBytes(SALT_LENGTH).toString('hex');
10
+ const hash = (await scryptAsync(password, salt, KEY_LENGTH) as Buffer).toString('hex');
11
+ return `${salt}:${hash}`;
12
+ }
13
+
14
+ export async function verifyPassword(password: string, stored: string): Promise<boolean> {
15
+ const [salt, hash] = stored.split(':');
16
+ if (!salt || !hash) return false;
17
+ const derived = await scryptAsync(password, salt, KEY_LENGTH) as Buffer;
18
+ const storedBuf = Buffer.from(hash, 'hex');
19
+ if (derived.length !== storedBuf.length) return false;
20
+ return timingSafeEqual(derived, storedBuf);
21
+ }
@@ -0,0 +1,22 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ /**
4
+ * Generate a PKCE code_verifier (43-128 character random string).
5
+ */
6
+ export function generateCodeVerifier(): string {
7
+ return crypto.randomBytes(32).toString('base64url');
8
+ }
9
+
10
+ /**
11
+ * Generate a PKCE code_challenge from a code_verifier (S256 method).
12
+ */
13
+ export function generateCodeChallenge(verifier: string): string {
14
+ return crypto.createHash('sha256').update(verifier).digest('base64url');
15
+ }
16
+
17
+ /**
18
+ * Generate a cryptographically random nonce for OAuth state.
19
+ */
20
+ export function generateNonce(): string {
21
+ return crypto.randomBytes(32).toString('hex');
22
+ }