palaryn 0.1.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 (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/sdk/typescript/src/client.d.ts +71 -0
  4. package/dist/sdk/typescript/src/client.d.ts.map +1 -0
  5. package/dist/sdk/typescript/src/client.js +176 -0
  6. package/dist/sdk/typescript/src/client.js.map +1 -0
  7. package/dist/sdk/typescript/src/errors.d.ts +50 -0
  8. package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
  9. package/dist/sdk/typescript/src/errors.js +103 -0
  10. package/dist/sdk/typescript/src/errors.js.map +1 -0
  11. package/dist/sdk/typescript/src/index.d.ts +4 -0
  12. package/dist/sdk/typescript/src/index.d.ts.map +1 -0
  13. package/dist/sdk/typescript/src/index.js +15 -0
  14. package/dist/sdk/typescript/src/index.js.map +1 -0
  15. package/dist/sdk/typescript/src/types.d.ts +101 -0
  16. package/dist/sdk/typescript/src/types.d.ts.map +1 -0
  17. package/dist/sdk/typescript/src/types.js +6 -0
  18. package/dist/sdk/typescript/src/types.js.map +1 -0
  19. package/dist/src/admin/index.d.ts +2 -0
  20. package/dist/src/admin/index.d.ts.map +1 -0
  21. package/dist/src/admin/index.js +6 -0
  22. package/dist/src/admin/index.js.map +1 -0
  23. package/dist/src/admin/routes.d.ts +5 -0
  24. package/dist/src/admin/routes.d.ts.map +1 -0
  25. package/dist/src/admin/routes.js +471 -0
  26. package/dist/src/admin/routes.js.map +1 -0
  27. package/dist/src/admin/templates.d.ts +51 -0
  28. package/dist/src/admin/templates.d.ts.map +1 -0
  29. package/dist/src/admin/templates.js +500 -0
  30. package/dist/src/admin/templates.js.map +1 -0
  31. package/dist/src/anomaly/detector.d.ts +141 -0
  32. package/dist/src/anomaly/detector.d.ts.map +1 -0
  33. package/dist/src/anomaly/detector.js +554 -0
  34. package/dist/src/anomaly/detector.js.map +1 -0
  35. package/dist/src/anomaly/index.d.ts +2 -0
  36. package/dist/src/anomaly/index.d.ts.map +1 -0
  37. package/dist/src/anomaly/index.js +7 -0
  38. package/dist/src/anomaly/index.js.map +1 -0
  39. package/dist/src/approval/manager.d.ts +147 -0
  40. package/dist/src/approval/manager.d.ts.map +1 -0
  41. package/dist/src/approval/manager.js +511 -0
  42. package/dist/src/approval/manager.js.map +1 -0
  43. package/dist/src/approval/webhook.d.ts +36 -0
  44. package/dist/src/approval/webhook.d.ts.map +1 -0
  45. package/dist/src/approval/webhook.js +135 -0
  46. package/dist/src/approval/webhook.js.map +1 -0
  47. package/dist/src/audit/logger.d.ts +70 -0
  48. package/dist/src/audit/logger.d.ts.map +1 -0
  49. package/dist/src/audit/logger.js +440 -0
  50. package/dist/src/audit/logger.js.map +1 -0
  51. package/dist/src/auth/index.d.ts +6 -0
  52. package/dist/src/auth/index.d.ts.map +1 -0
  53. package/dist/src/auth/index.js +22 -0
  54. package/dist/src/auth/index.js.map +1 -0
  55. package/dist/src/auth/password.d.ts +3 -0
  56. package/dist/src/auth/password.d.ts.map +1 -0
  57. package/dist/src/auth/password.js +25 -0
  58. package/dist/src/auth/password.js.map +1 -0
  59. package/dist/src/auth/pkce.d.ts +13 -0
  60. package/dist/src/auth/pkce.d.ts.map +1 -0
  61. package/dist/src/auth/pkce.js +58 -0
  62. package/dist/src/auth/pkce.js.map +1 -0
  63. package/dist/src/auth/providers.d.ts +28 -0
  64. package/dist/src/auth/providers.d.ts.map +1 -0
  65. package/dist/src/auth/providers.js +198 -0
  66. package/dist/src/auth/providers.js.map +1 -0
  67. package/dist/src/auth/routes.d.ts +14 -0
  68. package/dist/src/auth/routes.d.ts.map +1 -0
  69. package/dist/src/auth/routes.js +431 -0
  70. package/dist/src/auth/routes.js.map +1 -0
  71. package/dist/src/auth/session.d.ts +24 -0
  72. package/dist/src/auth/session.d.ts.map +1 -0
  73. package/dist/src/auth/session.js +105 -0
  74. package/dist/src/auth/session.js.map +1 -0
  75. package/dist/src/billing/index.d.ts +7 -0
  76. package/dist/src/billing/index.d.ts.map +1 -0
  77. package/dist/src/billing/index.js +14 -0
  78. package/dist/src/billing/index.js.map +1 -0
  79. package/dist/src/billing/plan-enforcer.d.ts +44 -0
  80. package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
  81. package/dist/src/billing/plan-enforcer.js +110 -0
  82. package/dist/src/billing/plan-enforcer.js.map +1 -0
  83. package/dist/src/billing/routes.d.ts +15 -0
  84. package/dist/src/billing/routes.d.ts.map +1 -0
  85. package/dist/src/billing/routes.js +193 -0
  86. package/dist/src/billing/routes.js.map +1 -0
  87. package/dist/src/billing/stripe-client.d.ts +14 -0
  88. package/dist/src/billing/stripe-client.d.ts.map +1 -0
  89. package/dist/src/billing/stripe-client.js +51 -0
  90. package/dist/src/billing/stripe-client.js.map +1 -0
  91. package/dist/src/billing/webhook-handler.d.ts +19 -0
  92. package/dist/src/billing/webhook-handler.d.ts.map +1 -0
  93. package/dist/src/billing/webhook-handler.js +169 -0
  94. package/dist/src/billing/webhook-handler.js.map +1 -0
  95. package/dist/src/billing/webhook-routes.d.ts +5 -0
  96. package/dist/src/billing/webhook-routes.d.ts.map +1 -0
  97. package/dist/src/billing/webhook-routes.js +30 -0
  98. package/dist/src/billing/webhook-routes.js.map +1 -0
  99. package/dist/src/budget/manager.d.ts +95 -0
  100. package/dist/src/budget/manager.d.ts.map +1 -0
  101. package/dist/src/budget/manager.js +547 -0
  102. package/dist/src/budget/manager.js.map +1 -0
  103. package/dist/src/budget/usage-extractor.d.ts +38 -0
  104. package/dist/src/budget/usage-extractor.d.ts.map +1 -0
  105. package/dist/src/budget/usage-extractor.js +165 -0
  106. package/dist/src/budget/usage-extractor.js.map +1 -0
  107. package/dist/src/cli.d.ts +3 -0
  108. package/dist/src/cli.d.ts.map +1 -0
  109. package/dist/src/cli.js +115 -0
  110. package/dist/src/cli.js.map +1 -0
  111. package/dist/src/config/defaults.d.ts +3 -0
  112. package/dist/src/config/defaults.d.ts.map +1 -0
  113. package/dist/src/config/defaults.js +243 -0
  114. package/dist/src/config/defaults.js.map +1 -0
  115. package/dist/src/config/validate.d.ts +15 -0
  116. package/dist/src/config/validate.d.ts.map +1 -0
  117. package/dist/src/config/validate.js +105 -0
  118. package/dist/src/config/validate.js.map +1 -0
  119. package/dist/src/dlp/composite-scanner.d.ts +47 -0
  120. package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
  121. package/dist/src/dlp/composite-scanner.js +186 -0
  122. package/dist/src/dlp/composite-scanner.js.map +1 -0
  123. package/dist/src/dlp/index.d.ts +10 -0
  124. package/dist/src/dlp/index.d.ts.map +1 -0
  125. package/dist/src/dlp/index.js +26 -0
  126. package/dist/src/dlp/index.js.map +1 -0
  127. package/dist/src/dlp/interfaces.d.ts +33 -0
  128. package/dist/src/dlp/interfaces.d.ts.map +1 -0
  129. package/dist/src/dlp/interfaces.js +3 -0
  130. package/dist/src/dlp/interfaces.js.map +1 -0
  131. package/dist/src/dlp/patterns.d.ts +9 -0
  132. package/dist/src/dlp/patterns.d.ts.map +1 -0
  133. package/dist/src/dlp/patterns.js +25 -0
  134. package/dist/src/dlp/patterns.js.map +1 -0
  135. package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
  136. package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
  137. package/dist/src/dlp/prompt-injection-backend.js +148 -0
  138. package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
  139. package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
  140. package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
  141. package/dist/src/dlp/prompt-injection-patterns.js +290 -0
  142. package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
  143. package/dist/src/dlp/regex-backend.d.ts +32 -0
  144. package/dist/src/dlp/regex-backend.d.ts.map +1 -0
  145. package/dist/src/dlp/regex-backend.js +153 -0
  146. package/dist/src/dlp/regex-backend.js.map +1 -0
  147. package/dist/src/dlp/scanner.d.ts +122 -0
  148. package/dist/src/dlp/scanner.d.ts.map +1 -0
  149. package/dist/src/dlp/scanner.js +444 -0
  150. package/dist/src/dlp/scanner.js.map +1 -0
  151. package/dist/src/dlp/text-normalizer.d.ts +41 -0
  152. package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
  153. package/dist/src/dlp/text-normalizer.js +203 -0
  154. package/dist/src/dlp/text-normalizer.js.map +1 -0
  155. package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
  156. package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
  157. package/dist/src/dlp/trufflehog-backend.js +151 -0
  158. package/dist/src/dlp/trufflehog-backend.js.map +1 -0
  159. package/dist/src/executor/http-executor.d.ts +25 -0
  160. package/dist/src/executor/http-executor.d.ts.map +1 -0
  161. package/dist/src/executor/http-executor.js +333 -0
  162. package/dist/src/executor/http-executor.js.map +1 -0
  163. package/dist/src/executor/index.d.ts +6 -0
  164. package/dist/src/executor/index.d.ts.map +1 -0
  165. package/dist/src/executor/index.js +12 -0
  166. package/dist/src/executor/index.js.map +1 -0
  167. package/dist/src/executor/interfaces.d.ts +11 -0
  168. package/dist/src/executor/interfaces.d.ts.map +1 -0
  169. package/dist/src/executor/interfaces.js +3 -0
  170. package/dist/src/executor/interfaces.js.map +1 -0
  171. package/dist/src/executor/noop-executor.d.ts +13 -0
  172. package/dist/src/executor/noop-executor.d.ts.map +1 -0
  173. package/dist/src/executor/noop-executor.js +21 -0
  174. package/dist/src/executor/noop-executor.js.map +1 -0
  175. package/dist/src/executor/registry.d.ts +30 -0
  176. package/dist/src/executor/registry.d.ts.map +1 -0
  177. package/dist/src/executor/registry.js +62 -0
  178. package/dist/src/executor/registry.js.map +1 -0
  179. package/dist/src/executor/slack-executor.d.ts +24 -0
  180. package/dist/src/executor/slack-executor.d.ts.map +1 -0
  181. package/dist/src/executor/slack-executor.js +147 -0
  182. package/dist/src/executor/slack-executor.js.map +1 -0
  183. package/dist/src/index.d.ts +25 -0
  184. package/dist/src/index.d.ts.map +1 -0
  185. package/dist/src/index.js +74 -0
  186. package/dist/src/index.js.map +1 -0
  187. package/dist/src/mcp/auth-verifier.d.ts +23 -0
  188. package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
  189. package/dist/src/mcp/auth-verifier.js +162 -0
  190. package/dist/src/mcp/auth-verifier.js.map +1 -0
  191. package/dist/src/mcp/bridge.d.ts +132 -0
  192. package/dist/src/mcp/bridge.d.ts.map +1 -0
  193. package/dist/src/mcp/bridge.js +734 -0
  194. package/dist/src/mcp/bridge.js.map +1 -0
  195. package/dist/src/mcp/http-transport.d.ts +32 -0
  196. package/dist/src/mcp/http-transport.d.ts.map +1 -0
  197. package/dist/src/mcp/http-transport.js +538 -0
  198. package/dist/src/mcp/http-transport.js.map +1 -0
  199. package/dist/src/mcp/index.d.ts +10 -0
  200. package/dist/src/mcp/index.d.ts.map +1 -0
  201. package/dist/src/mcp/index.js +17 -0
  202. package/dist/src/mcp/index.js.map +1 -0
  203. package/dist/src/mcp/oauth-pages.d.ts +23 -0
  204. package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
  205. package/dist/src/mcp/oauth-pages.js +121 -0
  206. package/dist/src/mcp/oauth-pages.js.map +1 -0
  207. package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
  208. package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
  209. package/dist/src/mcp/oauth-postgres-stores.js +226 -0
  210. package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
  211. package/dist/src/mcp/oauth-provider.d.ts +95 -0
  212. package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
  213. package/dist/src/mcp/oauth-provider.js +360 -0
  214. package/dist/src/mcp/oauth-provider.js.map +1 -0
  215. package/dist/src/mcp/oauth-stores.d.ts +62 -0
  216. package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
  217. package/dist/src/mcp/oauth-stores.js +154 -0
  218. package/dist/src/mcp/oauth-stores.js.map +1 -0
  219. package/dist/src/mcp/server.d.ts +18 -0
  220. package/dist/src/mcp/server.d.ts.map +1 -0
  221. package/dist/src/mcp/server.js +51 -0
  222. package/dist/src/mcp/server.js.map +1 -0
  223. package/dist/src/metrics/collector.d.ts +106 -0
  224. package/dist/src/metrics/collector.d.ts.map +1 -0
  225. package/dist/src/metrics/collector.js +311 -0
  226. package/dist/src/metrics/collector.js.map +1 -0
  227. package/dist/src/metrics/index.d.ts +2 -0
  228. package/dist/src/metrics/index.d.ts.map +1 -0
  229. package/dist/src/metrics/index.js +6 -0
  230. package/dist/src/metrics/index.js.map +1 -0
  231. package/dist/src/middleware/auth.d.ts +77 -0
  232. package/dist/src/middleware/auth.d.ts.map +1 -0
  233. package/dist/src/middleware/auth.js +720 -0
  234. package/dist/src/middleware/auth.js.map +1 -0
  235. package/dist/src/middleware/session.d.ts +18 -0
  236. package/dist/src/middleware/session.d.ts.map +1 -0
  237. package/dist/src/middleware/session.js +67 -0
  238. package/dist/src/middleware/session.js.map +1 -0
  239. package/dist/src/middleware/validate.d.ts +3 -0
  240. package/dist/src/middleware/validate.d.ts.map +1 -0
  241. package/dist/src/middleware/validate.js +85 -0
  242. package/dist/src/middleware/validate.js.map +1 -0
  243. package/dist/src/policy/engine.d.ts +107 -0
  244. package/dist/src/policy/engine.d.ts.map +1 -0
  245. package/dist/src/policy/engine.js +646 -0
  246. package/dist/src/policy/engine.js.map +1 -0
  247. package/dist/src/policy/index.d.ts +3 -0
  248. package/dist/src/policy/index.d.ts.map +1 -0
  249. package/dist/src/policy/index.js +8 -0
  250. package/dist/src/policy/index.js.map +1 -0
  251. package/dist/src/policy/opa-engine.d.ts +176 -0
  252. package/dist/src/policy/opa-engine.d.ts.map +1 -0
  253. package/dist/src/policy/opa-engine.js +790 -0
  254. package/dist/src/policy/opa-engine.js.map +1 -0
  255. package/dist/src/proxy/forward-proxy.d.ts +30 -0
  256. package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
  257. package/dist/src/proxy/forward-proxy.js +580 -0
  258. package/dist/src/proxy/forward-proxy.js.map +1 -0
  259. package/dist/src/proxy/index.d.ts +2 -0
  260. package/dist/src/proxy/index.d.ts.map +1 -0
  261. package/dist/src/proxy/index.js +8 -0
  262. package/dist/src/proxy/index.js.map +1 -0
  263. package/dist/src/ratelimit/limiter.d.ts +45 -0
  264. package/dist/src/ratelimit/limiter.d.ts.map +1 -0
  265. package/dist/src/ratelimit/limiter.js +158 -0
  266. package/dist/src/ratelimit/limiter.js.map +1 -0
  267. package/dist/src/replay/engine.d.ts +40 -0
  268. package/dist/src/replay/engine.d.ts.map +1 -0
  269. package/dist/src/replay/engine.js +106 -0
  270. package/dist/src/replay/engine.js.map +1 -0
  271. package/dist/src/replay/index.d.ts +2 -0
  272. package/dist/src/replay/index.d.ts.map +1 -0
  273. package/dist/src/replay/index.js +6 -0
  274. package/dist/src/replay/index.js.map +1 -0
  275. package/dist/src/saas/index.d.ts +2 -0
  276. package/dist/src/saas/index.d.ts.map +1 -0
  277. package/dist/src/saas/index.js +18 -0
  278. package/dist/src/saas/index.js.map +1 -0
  279. package/dist/src/saas/routes.d.ts +18 -0
  280. package/dist/src/saas/routes.d.ts.map +1 -0
  281. package/dist/src/saas/routes.js +1566 -0
  282. package/dist/src/saas/routes.js.map +1 -0
  283. package/dist/src/server/app.d.ts +44 -0
  284. package/dist/src/server/app.d.ts.map +1 -0
  285. package/dist/src/server/app.js +854 -0
  286. package/dist/src/server/app.js.map +1 -0
  287. package/dist/src/server/errors.d.ts +32 -0
  288. package/dist/src/server/errors.d.ts.map +1 -0
  289. package/dist/src/server/errors.js +39 -0
  290. package/dist/src/server/errors.js.map +1 -0
  291. package/dist/src/server/gateway.d.ts +165 -0
  292. package/dist/src/server/gateway.d.ts.map +1 -0
  293. package/dist/src/server/gateway.js +964 -0
  294. package/dist/src/server/gateway.js.map +1 -0
  295. package/dist/src/server/index.d.ts +2 -0
  296. package/dist/src/server/index.d.ts.map +1 -0
  297. package/dist/src/server/index.js +295 -0
  298. package/dist/src/server/index.js.map +1 -0
  299. package/dist/src/server/logger.d.ts +33 -0
  300. package/dist/src/server/logger.d.ts.map +1 -0
  301. package/dist/src/server/logger.js +230 -0
  302. package/dist/src/server/logger.js.map +1 -0
  303. package/dist/src/server/stream-proxy.d.ts +32 -0
  304. package/dist/src/server/stream-proxy.d.ts.map +1 -0
  305. package/dist/src/server/stream-proxy.js +184 -0
  306. package/dist/src/server/stream-proxy.js.map +1 -0
  307. package/dist/src/storage/file-persistence.d.ts +48 -0
  308. package/dist/src/storage/file-persistence.d.ts.map +1 -0
  309. package/dist/src/storage/file-persistence.js +280 -0
  310. package/dist/src/storage/file-persistence.js.map +1 -0
  311. package/dist/src/storage/index.d.ts +5 -0
  312. package/dist/src/storage/index.d.ts.map +1 -0
  313. package/dist/src/storage/index.js +21 -0
  314. package/dist/src/storage/index.js.map +1 -0
  315. package/dist/src/storage/interfaces.d.ts +237 -0
  316. package/dist/src/storage/interfaces.d.ts.map +1 -0
  317. package/dist/src/storage/interfaces.js +3 -0
  318. package/dist/src/storage/interfaces.js.map +1 -0
  319. package/dist/src/storage/memory.d.ts +162 -0
  320. package/dist/src/storage/memory.d.ts.map +1 -0
  321. package/dist/src/storage/memory.js +603 -0
  322. package/dist/src/storage/memory.js.map +1 -0
  323. package/dist/src/storage/postgres.d.ts +267 -0
  324. package/dist/src/storage/postgres.d.ts.map +1 -0
  325. package/dist/src/storage/postgres.js +1555 -0
  326. package/dist/src/storage/postgres.js.map +1 -0
  327. package/dist/src/storage/redis.d.ts +202 -0
  328. package/dist/src/storage/redis.d.ts.map +1 -0
  329. package/dist/src/storage/redis.js +629 -0
  330. package/dist/src/storage/redis.js.map +1 -0
  331. package/dist/src/tracing/index.d.ts +2 -0
  332. package/dist/src/tracing/index.d.ts.map +1 -0
  333. package/dist/src/tracing/index.js +6 -0
  334. package/dist/src/tracing/index.js.map +1 -0
  335. package/dist/src/tracing/provider.d.ts +43 -0
  336. package/dist/src/tracing/provider.d.ts.map +1 -0
  337. package/dist/src/tracing/provider.js +74 -0
  338. package/dist/src/tracing/provider.js.map +1 -0
  339. package/dist/src/trust/calculator.d.ts +54 -0
  340. package/dist/src/trust/calculator.d.ts.map +1 -0
  341. package/dist/src/trust/calculator.js +102 -0
  342. package/dist/src/trust/calculator.js.map +1 -0
  343. package/dist/src/trust/index.d.ts +2 -0
  344. package/dist/src/trust/index.d.ts.map +1 -0
  345. package/dist/src/trust/index.js +7 -0
  346. package/dist/src/trust/index.js.map +1 -0
  347. package/dist/src/types/budget.d.ts +30 -0
  348. package/dist/src/types/budget.d.ts.map +1 -0
  349. package/dist/src/types/budget.js +3 -0
  350. package/dist/src/types/budget.js.map +1 -0
  351. package/dist/src/types/config.d.ts +176 -0
  352. package/dist/src/types/config.d.ts.map +1 -0
  353. package/dist/src/types/config.js +3 -0
  354. package/dist/src/types/config.js.map +1 -0
  355. package/dist/src/types/events.d.ts +24 -0
  356. package/dist/src/types/events.d.ts.map +1 -0
  357. package/dist/src/types/events.js +3 -0
  358. package/dist/src/types/events.js.map +1 -0
  359. package/dist/src/types/index.d.ts +8 -0
  360. package/dist/src/types/index.d.ts.map +1 -0
  361. package/dist/src/types/index.js +24 -0
  362. package/dist/src/types/index.js.map +1 -0
  363. package/dist/src/types/policy.d.ts +60 -0
  364. package/dist/src/types/policy.d.ts.map +1 -0
  365. package/dist/src/types/policy.js +3 -0
  366. package/dist/src/types/policy.js.map +1 -0
  367. package/dist/src/types/stripe-config.d.ts +12 -0
  368. package/dist/src/types/stripe-config.d.ts.map +1 -0
  369. package/dist/src/types/stripe-config.js +3 -0
  370. package/dist/src/types/stripe-config.js.map +1 -0
  371. package/dist/src/types/subscription.d.ts +24 -0
  372. package/dist/src/types/subscription.d.ts.map +1 -0
  373. package/dist/src/types/subscription.js +38 -0
  374. package/dist/src/types/subscription.js.map +1 -0
  375. package/dist/src/types/tool-call.d.ts +42 -0
  376. package/dist/src/types/tool-call.d.ts.map +1 -0
  377. package/dist/src/types/tool-call.js +3 -0
  378. package/dist/src/types/tool-call.js.map +1 -0
  379. package/dist/src/types/tool-result.d.ts +58 -0
  380. package/dist/src/types/tool-result.d.ts.map +1 -0
  381. package/dist/src/types/tool-result.js +3 -0
  382. package/dist/src/types/tool-result.js.map +1 -0
  383. package/dist/src/types/user.d.ts +101 -0
  384. package/dist/src/types/user.d.ts.map +1 -0
  385. package/dist/src/types/user.js +6 -0
  386. package/dist/src/types/user.js.map +1 -0
  387. package/dist/tests/integration/api.test.d.ts +2 -0
  388. package/dist/tests/integration/api.test.d.ts.map +1 -0
  389. package/dist/tests/integration/api.test.js +1199 -0
  390. package/dist/tests/integration/api.test.js.map +1 -0
  391. package/dist/tests/integration/proxy.test.d.ts +2 -0
  392. package/dist/tests/integration/proxy.test.d.ts.map +1 -0
  393. package/dist/tests/integration/proxy.test.js +251 -0
  394. package/dist/tests/integration/proxy.test.js.map +1 -0
  395. package/dist/tests/integration/storage.test.d.ts +16 -0
  396. package/dist/tests/integration/storage.test.d.ts.map +1 -0
  397. package/dist/tests/integration/storage.test.js +826 -0
  398. package/dist/tests/integration/storage.test.js.map +1 -0
  399. package/dist/tests/unit/admin.test.d.ts +2 -0
  400. package/dist/tests/unit/admin.test.d.ts.map +1 -0
  401. package/dist/tests/unit/admin.test.js +698 -0
  402. package/dist/tests/unit/admin.test.js.map +1 -0
  403. package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
  404. package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
  405. package/dist/tests/unit/anomaly-detector.test.js +903 -0
  406. package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
  407. package/dist/tests/unit/approval-manager.test.d.ts +2 -0
  408. package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
  409. package/dist/tests/unit/approval-manager.test.js +528 -0
  410. package/dist/tests/unit/approval-manager.test.js.map +1 -0
  411. package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
  412. package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
  413. package/dist/tests/unit/approval-webhook.test.js +355 -0
  414. package/dist/tests/unit/approval-webhook.test.js.map +1 -0
  415. package/dist/tests/unit/audit-logger.test.d.ts +2 -0
  416. package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
  417. package/dist/tests/unit/audit-logger.test.js +635 -0
  418. package/dist/tests/unit/audit-logger.test.js.map +1 -0
  419. package/dist/tests/unit/auth-routes.test.d.ts +2 -0
  420. package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
  421. package/dist/tests/unit/auth-routes.test.js +281 -0
  422. package/dist/tests/unit/auth-routes.test.js.map +1 -0
  423. package/dist/tests/unit/auth.test.d.ts +2 -0
  424. package/dist/tests/unit/auth.test.d.ts.map +1 -0
  425. package/dist/tests/unit/auth.test.js +1382 -0
  426. package/dist/tests/unit/auth.test.js.map +1 -0
  427. package/dist/tests/unit/billing.test.d.ts +2 -0
  428. package/dist/tests/unit/billing.test.d.ts.map +1 -0
  429. package/dist/tests/unit/billing.test.js +579 -0
  430. package/dist/tests/unit/billing.test.js.map +1 -0
  431. package/dist/tests/unit/budget-manager.test.d.ts +2 -0
  432. package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
  433. package/dist/tests/unit/budget-manager.test.js +778 -0
  434. package/dist/tests/unit/budget-manager.test.js.map +1 -0
  435. package/dist/tests/unit/budget-race.test.d.ts +2 -0
  436. package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
  437. package/dist/tests/unit/budget-race.test.js +58 -0
  438. package/dist/tests/unit/budget-race.test.js.map +1 -0
  439. package/dist/tests/unit/cli.test.d.ts +2 -0
  440. package/dist/tests/unit/cli.test.d.ts.map +1 -0
  441. package/dist/tests/unit/cli.test.js +93 -0
  442. package/dist/tests/unit/cli.test.js.map +1 -0
  443. package/dist/tests/unit/concurrency.test.d.ts +2 -0
  444. package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
  445. package/dist/tests/unit/concurrency.test.js +1270 -0
  446. package/dist/tests/unit/concurrency.test.js.map +1 -0
  447. package/dist/tests/unit/config-validate.test.d.ts +2 -0
  448. package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
  449. package/dist/tests/unit/config-validate.test.js +230 -0
  450. package/dist/tests/unit/config-validate.test.js.map +1 -0
  451. package/dist/tests/unit/defaults.test.d.ts +2 -0
  452. package/dist/tests/unit/defaults.test.d.ts.map +1 -0
  453. package/dist/tests/unit/defaults.test.js +364 -0
  454. package/dist/tests/unit/defaults.test.js.map +1 -0
  455. package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
  456. package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
  457. package/dist/tests/unit/dlp-backends.test.js +563 -0
  458. package/dist/tests/unit/dlp-backends.test.js.map +1 -0
  459. package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
  460. package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
  461. package/dist/tests/unit/dlp-scanner.test.js +739 -0
  462. package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
  463. package/dist/tests/unit/error-responses.test.d.ts +2 -0
  464. package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
  465. package/dist/tests/unit/error-responses.test.js +101 -0
  466. package/dist/tests/unit/error-responses.test.js.map +1 -0
  467. package/dist/tests/unit/executor-registry.test.d.ts +2 -0
  468. package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
  469. package/dist/tests/unit/executor-registry.test.js +390 -0
  470. package/dist/tests/unit/executor-registry.test.js.map +1 -0
  471. package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
  472. package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
  473. package/dist/tests/unit/forward-proxy.test.js +621 -0
  474. package/dist/tests/unit/forward-proxy.test.js.map +1 -0
  475. package/dist/tests/unit/gateway-features.test.d.ts +2 -0
  476. package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
  477. package/dist/tests/unit/gateway-features.test.js +753 -0
  478. package/dist/tests/unit/gateway-features.test.js.map +1 -0
  479. package/dist/tests/unit/http-executor.test.d.ts +2 -0
  480. package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
  481. package/dist/tests/unit/http-executor.test.js +310 -0
  482. package/dist/tests/unit/http-executor.test.js.map +1 -0
  483. package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
  484. package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
  485. package/dist/tests/unit/mcp-bridge.test.js +1136 -0
  486. package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
  487. package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
  488. package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
  489. package/dist/tests/unit/mcp-http-transport.test.js +899 -0
  490. package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
  491. package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
  492. package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
  493. package/dist/tests/unit/mcp-oauth.test.js +759 -0
  494. package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
  495. package/dist/tests/unit/mcp-server.test.d.ts +15 -0
  496. package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
  497. package/dist/tests/unit/mcp-server.test.js +158 -0
  498. package/dist/tests/unit/mcp-server.test.js.map +1 -0
  499. package/dist/tests/unit/metrics.test.d.ts +2 -0
  500. package/dist/tests/unit/metrics.test.d.ts.map +1 -0
  501. package/dist/tests/unit/metrics.test.js +208 -0
  502. package/dist/tests/unit/metrics.test.js.map +1 -0
  503. package/dist/tests/unit/oauth.test.d.ts +2 -0
  504. package/dist/tests/unit/oauth.test.d.ts.map +1 -0
  505. package/dist/tests/unit/oauth.test.js +281 -0
  506. package/dist/tests/unit/oauth.test.js.map +1 -0
  507. package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
  508. package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
  509. package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
  510. package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
  511. package/dist/tests/unit/opa-engine.test.d.ts +2 -0
  512. package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
  513. package/dist/tests/unit/opa-engine.test.js +1813 -0
  514. package/dist/tests/unit/opa-engine.test.js.map +1 -0
  515. package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
  516. package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
  517. package/dist/tests/unit/pipeline-timing.test.js +528 -0
  518. package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
  519. package/dist/tests/unit/policy-engine.test.d.ts +2 -0
  520. package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
  521. package/dist/tests/unit/policy-engine.test.js +1345 -0
  522. package/dist/tests/unit/policy-engine.test.js.map +1 -0
  523. package/dist/tests/unit/policy-store.test.d.ts +2 -0
  524. package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
  525. package/dist/tests/unit/policy-store.test.js +60 -0
  526. package/dist/tests/unit/policy-store.test.js.map +1 -0
  527. package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
  528. package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
  529. package/dist/tests/unit/postgres-storage.test.js +614 -0
  530. package/dist/tests/unit/postgres-storage.test.js.map +1 -0
  531. package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
  532. package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
  533. package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
  534. package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
  535. package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
  536. package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
  537. package/dist/tests/unit/proxy-hardening.test.js +166 -0
  538. package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
  539. package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
  540. package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
  541. package/dist/tests/unit/rate-limiter.test.js +443 -0
  542. package/dist/tests/unit/rate-limiter.test.js.map +1 -0
  543. package/dist/tests/unit/redis-storage.test.d.ts +2 -0
  544. package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
  545. package/dist/tests/unit/redis-storage.test.js +766 -0
  546. package/dist/tests/unit/redis-storage.test.js.map +1 -0
  547. package/dist/tests/unit/replay-engine.test.d.ts +2 -0
  548. package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
  549. package/dist/tests/unit/replay-engine.test.js +371 -0
  550. package/dist/tests/unit/replay-engine.test.js.map +1 -0
  551. package/dist/tests/unit/saas-routes.test.d.ts +2 -0
  552. package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
  553. package/dist/tests/unit/saas-routes.test.js +1399 -0
  554. package/dist/tests/unit/saas-routes.test.js.map +1 -0
  555. package/dist/tests/unit/session.test.d.ts +2 -0
  556. package/dist/tests/unit/session.test.d.ts.map +1 -0
  557. package/dist/tests/unit/session.test.js +532 -0
  558. package/dist/tests/unit/session.test.js.map +1 -0
  559. package/dist/tests/unit/slack-executor.test.d.ts +2 -0
  560. package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
  561. package/dist/tests/unit/slack-executor.test.js +209 -0
  562. package/dist/tests/unit/slack-executor.test.js.map +1 -0
  563. package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
  564. package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
  565. package/dist/tests/unit/storage-hardening.test.js +165 -0
  566. package/dist/tests/unit/storage-hardening.test.js.map +1 -0
  567. package/dist/tests/unit/storage.test.d.ts +2 -0
  568. package/dist/tests/unit/storage.test.d.ts.map +1 -0
  569. package/dist/tests/unit/storage.test.js +698 -0
  570. package/dist/tests/unit/storage.test.js.map +1 -0
  571. package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
  572. package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
  573. package/dist/tests/unit/text-normalizer.test.js +229 -0
  574. package/dist/tests/unit/text-normalizer.test.js.map +1 -0
  575. package/dist/tests/unit/tracing.test.d.ts +2 -0
  576. package/dist/tests/unit/tracing.test.d.ts.map +1 -0
  577. package/dist/tests/unit/tracing.test.js +611 -0
  578. package/dist/tests/unit/tracing.test.js.map +1 -0
  579. package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
  580. package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
  581. package/dist/tests/unit/trust-calculator.test.js +497 -0
  582. package/dist/tests/unit/trust-calculator.test.js.map +1 -0
  583. package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
  584. package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
  585. package/dist/tests/unit/ts-sdk.test.js +421 -0
  586. package/dist/tests/unit/ts-sdk.test.js.map +1 -0
  587. package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
  588. package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
  589. package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
  590. package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
  591. package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
  592. package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
  593. package/dist/tests/unit/usage-extractor.test.js +271 -0
  594. package/dist/tests/unit/usage-extractor.test.js.map +1 -0
  595. package/dist/tests/unit/user-stores.test.d.ts +2 -0
  596. package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
  597. package/dist/tests/unit/user-stores.test.js +687 -0
  598. package/dist/tests/unit/user-stores.test.js.map +1 -0
  599. package/dist/tests/unit/validate.test.d.ts +2 -0
  600. package/dist/tests/unit/validate.test.d.ts.map +1 -0
  601. package/dist/tests/unit/validate.test.js +545 -0
  602. package/dist/tests/unit/validate.test.js.map +1 -0
  603. package/package.json +86 -0
  604. package/policy-packs/README.md +42 -0
  605. package/policy-packs/default.yaml +46 -0
  606. package/policy-packs/dev_fast.yaml +54 -0
  607. package/policy-packs/prod_strict.yaml +83 -0
@@ -0,0 +1,903 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const anomaly_1 = require("../../src/anomaly");
4
+ // ---------------------------------------------------------------------------
5
+ // Helpers
6
+ // ---------------------------------------------------------------------------
7
+ /** Build a minimal valid ToolCall with configurable fields. */
8
+ function buildToolCall(overrides = {}) {
9
+ return {
10
+ tool_call_id: `tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
11
+ task_id: 'task-001',
12
+ workspace_id: overrides.workspaceId ?? 'ws-default',
13
+ actor: {
14
+ type: 'agent',
15
+ id: overrides.actorId ?? 'agent-1',
16
+ },
17
+ source: {
18
+ platform: 'test',
19
+ },
20
+ tool: {
21
+ name: overrides.toolName ?? 'http.request',
22
+ capability: overrides.capability ?? 'read',
23
+ },
24
+ args: {
25
+ method: 'GET',
26
+ url: 'https://api.example.com/data',
27
+ },
28
+ timestamp: new Date().toISOString(),
29
+ };
30
+ }
31
+ /** Build a default AnomalyConfig with optional overrides. */
32
+ function defaultConfig(overrides) {
33
+ return {
34
+ enabled: true,
35
+ window_ms: 3600000,
36
+ z_score_threshold: 3,
37
+ min_samples: 10,
38
+ action: 'flag',
39
+ track_actors: true,
40
+ track_tools: true,
41
+ track_workspaces: true,
42
+ ...overrides,
43
+ };
44
+ }
45
+ // ===========================================================================
46
+ // Tests
47
+ // ===========================================================================
48
+ describe('AnomalyDetector', () => {
49
+ // -----------------------------------------------------------------------
50
+ // Disabled
51
+ // -----------------------------------------------------------------------
52
+ describe('when anomaly detection is disabled', () => {
53
+ it('should return no alerts from analyze()', () => {
54
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ enabled: false }));
55
+ const tc = buildToolCall();
56
+ const alerts = detector.analyze(tc);
57
+ expect(alerts).toEqual([]);
58
+ });
59
+ it('should return no alerts from analyzeResult()', () => {
60
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ enabled: false }));
61
+ const tc = buildToolCall();
62
+ const alerts = detector.analyzeResult(tc, 100, false);
63
+ expect(alerts).toEqual([]);
64
+ });
65
+ it('should not record results when disabled', () => {
66
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ enabled: false }));
67
+ const tc = buildToolCall();
68
+ // Should not throw
69
+ detector.recordResult(tc, 100, false);
70
+ detector.recordResult(tc, 200, true);
71
+ // No baselines should exist
72
+ expect(detector.getBaseline('tool', 'http.request', 'latency')).toBeNull();
73
+ });
74
+ it('should return no alerts even with many calls', () => {
75
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ enabled: false }));
76
+ for (let i = 0; i < 50; i++) {
77
+ const alerts = detector.analyze(buildToolCall());
78
+ expect(alerts).toEqual([]);
79
+ }
80
+ });
81
+ });
82
+ // -----------------------------------------------------------------------
83
+ // New tool usage detection
84
+ // -----------------------------------------------------------------------
85
+ describe('new tool usage detection', () => {
86
+ it('should not alert on the first tool an actor uses', () => {
87
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
88
+ const tc = buildToolCall({ actorId: 'actor-new', toolName: 'http.get' });
89
+ const alerts = detector.analyze(tc);
90
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
91
+ expect(newToolAlerts).toHaveLength(0);
92
+ });
93
+ it('should alert when an actor uses a new tool after their first', () => {
94
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
95
+ // First tool -- no alert
96
+ detector.analyze(buildToolCall({ actorId: 'actor-a', toolName: 'http.get' }));
97
+ // Second tool -- should alert
98
+ const alerts = detector.analyze(buildToolCall({ actorId: 'actor-a', toolName: 'slack.post' }));
99
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
100
+ expect(newToolAlerts).toHaveLength(1);
101
+ expect(newToolAlerts[0].entity_type).toBe('actor');
102
+ expect(newToolAlerts[0].entity_id).toBe('actor-a');
103
+ expect(newToolAlerts[0].metric).toBe('new_tool');
104
+ expect(newToolAlerts[0].severity).toBe('low');
105
+ });
106
+ it('should not alert on repeated use of the same tool', () => {
107
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
108
+ // Use tool twice
109
+ detector.analyze(buildToolCall({ actorId: 'actor-b', toolName: 'http.get' }));
110
+ detector.analyze(buildToolCall({ actorId: 'actor-b', toolName: 'slack.post' })); // triggers new tool alert
111
+ // Use the same tool again -- should not trigger new_tool_usage
112
+ const alerts = detector.analyze(buildToolCall({ actorId: 'actor-b', toolName: 'slack.post' }));
113
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
114
+ expect(newToolAlerts).toHaveLength(0);
115
+ });
116
+ it('should track different actors independently', () => {
117
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
118
+ // Actor A uses two tools
119
+ detector.analyze(buildToolCall({ actorId: 'actor-x', toolName: 'http.get' }));
120
+ detector.analyze(buildToolCall({ actorId: 'actor-x', toolName: 'slack.post' }));
121
+ // Actor B using http.get should not alert (it's their first tool)
122
+ const alerts = detector.analyze(buildToolCall({ actorId: 'actor-y', toolName: 'http.get' }));
123
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
124
+ expect(newToolAlerts).toHaveLength(0);
125
+ });
126
+ it('should not track new tools when track_actors is false', () => {
127
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ track_actors: false }));
128
+ detector.analyze(buildToolCall({ actorId: 'actor-c', toolName: 'http.get' }));
129
+ const alerts = detector.analyze(buildToolCall({ actorId: 'actor-c', toolName: 'slack.post' }));
130
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
131
+ expect(newToolAlerts).toHaveLength(0);
132
+ });
133
+ });
134
+ // -----------------------------------------------------------------------
135
+ // Capability escalation detection
136
+ // -----------------------------------------------------------------------
137
+ describe('capability escalation detection', () => {
138
+ it('should not alert on the first capability an actor uses', () => {
139
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
140
+ const tc = buildToolCall({ actorId: 'cap-actor', capability: 'read' });
141
+ const alerts = detector.analyze(tc);
142
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
143
+ expect(capAlerts).toHaveLength(0);
144
+ });
145
+ it('should alert when actor escalates from read to write', () => {
146
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
147
+ // Start with read
148
+ detector.analyze(buildToolCall({ actorId: 'cap-1', capability: 'read' }));
149
+ // Escalate to write
150
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-1', capability: 'write' }));
151
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
152
+ expect(capAlerts).toHaveLength(1);
153
+ expect(capAlerts[0].entity_type).toBe('actor');
154
+ expect(capAlerts[0].entity_id).toBe('cap-1');
155
+ expect(capAlerts[0].severity).toBe('low');
156
+ });
157
+ it('should alert with medium severity when escalating to delete', () => {
158
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
159
+ detector.analyze(buildToolCall({ actorId: 'cap-2', capability: 'read' }));
160
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-2', capability: 'delete' }));
161
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
162
+ expect(capAlerts).toHaveLength(1);
163
+ expect(capAlerts[0].severity).toBe('medium');
164
+ });
165
+ it('should alert with high severity when escalating to admin', () => {
166
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
167
+ detector.analyze(buildToolCall({ actorId: 'cap-3', capability: 'read' }));
168
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-3', capability: 'admin' }));
169
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
170
+ expect(capAlerts).toHaveLength(1);
171
+ expect(capAlerts[0].severity).toBe('high');
172
+ });
173
+ it('should not alert when actor already used this capability before', () => {
174
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
175
+ detector.analyze(buildToolCall({ actorId: 'cap-4', capability: 'read' }));
176
+ detector.analyze(buildToolCall({ actorId: 'cap-4', capability: 'write' })); // alerts once
177
+ // Using write again -- should not alert
178
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-4', capability: 'write' }));
179
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
180
+ expect(capAlerts).toHaveLength(0);
181
+ });
182
+ it('should not alert when going from write down to read (not an escalation)', () => {
183
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
184
+ detector.analyze(buildToolCall({ actorId: 'cap-5', capability: 'write' }));
185
+ // Going to read is a de-escalation, not escalation
186
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-5', capability: 'read' }));
187
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
188
+ expect(capAlerts).toHaveLength(0);
189
+ });
190
+ it('should not track capability escalation when track_actors is false', () => {
191
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ track_actors: false }));
192
+ detector.analyze(buildToolCall({ actorId: 'cap-6', capability: 'read' }));
193
+ const alerts = detector.analyze(buildToolCall({ actorId: 'cap-6', capability: 'admin' }));
194
+ const capAlerts = alerts.filter(a => a.anomaly_type === 'capability_escalation');
195
+ expect(capAlerts).toHaveLength(0);
196
+ });
197
+ });
198
+ // -----------------------------------------------------------------------
199
+ // Off-hours activity detection
200
+ // -----------------------------------------------------------------------
201
+ describe('off-hours activity detection', () => {
202
+ beforeEach(() => {
203
+ jest.useFakeTimers();
204
+ });
205
+ afterEach(() => {
206
+ jest.useRealTimers();
207
+ });
208
+ it('should not alert during normal business hours (weekday)', () => {
209
+ // Set time to Wednesday 2pm UTC
210
+ const wednesday2pm = new Date('2025-01-15T14:00:00Z'); // Wednesday
211
+ jest.setSystemTime(wednesday2pm);
212
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
213
+ const alerts = detector.analyze(buildToolCall({ actorId: 'office-worker' }));
214
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
215
+ expect(offHoursAlerts).toHaveLength(0);
216
+ });
217
+ it('should alert for activity at 3am UTC', () => {
218
+ // Set time to Wednesday 3am UTC
219
+ const wednesday3am = new Date('2025-01-15T03:00:00Z');
220
+ jest.setSystemTime(wednesday3am);
221
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
222
+ const alerts = detector.analyze(buildToolCall({ actorId: 'night-owl' }));
223
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
224
+ expect(offHoursAlerts).toHaveLength(1);
225
+ expect(offHoursAlerts[0].entity_id).toBe('night-owl');
226
+ expect(offHoursAlerts[0].metric).toBe('hour_utc');
227
+ });
228
+ it('should alert for activity at 11pm UTC (hour 23)', () => {
229
+ const wednesday11pm = new Date('2025-01-15T23:00:00Z');
230
+ jest.setSystemTime(wednesday11pm);
231
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
232
+ const alerts = detector.analyze(buildToolCall({ actorId: 'late-worker' }));
233
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
234
+ expect(offHoursAlerts).toHaveLength(1);
235
+ });
236
+ it('should alert for weekend activity', () => {
237
+ // Set time to Saturday 10am UTC
238
+ const saturday10am = new Date('2025-01-18T10:00:00Z'); // Saturday
239
+ jest.setSystemTime(saturday10am);
240
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
241
+ const alerts = detector.analyze(buildToolCall({ actorId: 'weekend-worker' }));
242
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
243
+ expect(offHoursAlerts).toHaveLength(1);
244
+ expect(offHoursAlerts[0].severity).toBe('low');
245
+ });
246
+ it('should have medium severity for weekend + off-hours combined', () => {
247
+ // Set time to Saturday 3am UTC
248
+ const saturday3am = new Date('2025-01-18T03:00:00Z'); // Saturday
249
+ jest.setSystemTime(saturday3am);
250
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
251
+ const alerts = detector.analyze(buildToolCall({ actorId: 'deep-night-weekend' }));
252
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
253
+ expect(offHoursAlerts).toHaveLength(1);
254
+ expect(offHoursAlerts[0].severity).toBe('medium');
255
+ });
256
+ it('should throttle off-hours alerts to once per entity per hour', () => {
257
+ const wednesday3am = new Date('2025-01-15T03:00:00Z');
258
+ jest.setSystemTime(wednesday3am);
259
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
260
+ // First call should alert
261
+ const alerts1 = detector.analyze(buildToolCall({ actorId: 'throttle-test' }));
262
+ const offHours1 = alerts1.filter(a => a.anomaly_type === 'off_hours_activity');
263
+ expect(offHours1).toHaveLength(1);
264
+ // Second call within same hour should NOT alert
265
+ const alerts2 = detector.analyze(buildToolCall({ actorId: 'throttle-test' }));
266
+ const offHours2 = alerts2.filter(a => a.anomaly_type === 'off_hours_activity');
267
+ expect(offHours2).toHaveLength(0);
268
+ });
269
+ it('should alert again in the next hour', () => {
270
+ const wednesday3am = new Date('2025-01-15T03:00:00Z');
271
+ jest.setSystemTime(wednesday3am);
272
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
273
+ // First alert at 3am
274
+ const alerts1 = detector.analyze(buildToolCall({ actorId: 'next-hour' }));
275
+ expect(alerts1.filter(a => a.anomaly_type === 'off_hours_activity')).toHaveLength(1);
276
+ // Advance to 4am (still off-hours)
277
+ jest.setSystemTime(new Date('2025-01-15T04:00:00Z'));
278
+ // Should alert again since it's a new hour
279
+ const alerts2 = detector.analyze(buildToolCall({ actorId: 'next-hour' }));
280
+ expect(alerts2.filter(a => a.anomaly_type === 'off_hours_activity')).toHaveLength(1);
281
+ });
282
+ it('should not alert at 6am UTC (boundary - start of business)', () => {
283
+ const wednesday6am = new Date('2025-01-15T06:00:00Z');
284
+ jest.setSystemTime(wednesday6am);
285
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
286
+ const alerts = detector.analyze(buildToolCall({ actorId: 'boundary-test' }));
287
+ const offHoursAlerts = alerts.filter(a => a.anomaly_type === 'off_hours_activity');
288
+ expect(offHoursAlerts).toHaveLength(0);
289
+ });
290
+ });
291
+ // -----------------------------------------------------------------------
292
+ // Latency spike detection
293
+ // -----------------------------------------------------------------------
294
+ describe('latency spike detection', () => {
295
+ it('should not alert when there are fewer than min_samples', () => {
296
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
297
+ const tc = buildToolCall({ toolName: 'http.fast' });
298
+ // Record 5 normal latencies
299
+ for (let i = 0; i < 5; i++) {
300
+ detector.recordResult(tc, 100, false);
301
+ }
302
+ // Even a very high latency should not alert (not enough samples)
303
+ const alerts = detector.analyzeResult(tc, 10000, false);
304
+ expect(alerts.filter(a => a.anomaly_type === 'latency_spike')).toHaveLength(0);
305
+ });
306
+ it('should not alert for normal latency within baseline', () => {
307
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
308
+ const tc = buildToolCall({ toolName: 'http.normal' });
309
+ // Build baseline with significant variance (50-150ms range)
310
+ const values = [50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 100, 90, 110, 105];
311
+ for (const v of values) {
312
+ detector.recordResult(tc, v, false);
313
+ }
314
+ // A value within the normal range should not alert
315
+ const alerts = detector.analyzeResult(tc, 115, false);
316
+ expect(alerts.filter(a => a.anomaly_type === 'latency_spike')).toHaveLength(0);
317
+ });
318
+ it('should alert for a significant latency spike', () => {
319
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10, z_score_threshold: 3 }));
320
+ const tc = buildToolCall({ toolName: 'http.spike' });
321
+ // Build baseline of ~100ms latency with low variance
322
+ for (let i = 0; i < 20; i++) {
323
+ detector.recordResult(tc, 100, false); // constant 100ms
324
+ }
325
+ // Add a tiny bit of variance so stddev > 0
326
+ detector.recordResult(tc, 101, false);
327
+ detector.recordResult(tc, 99, false);
328
+ // Now a 10x spike
329
+ const alerts = detector.analyzeResult(tc, 1000, false);
330
+ const latencyAlerts = alerts.filter(a => a.anomaly_type === 'latency_spike');
331
+ expect(latencyAlerts).toHaveLength(1);
332
+ expect(latencyAlerts[0].entity_type).toBe('tool');
333
+ expect(latencyAlerts[0].entity_id).toBe('http.spike');
334
+ expect(latencyAlerts[0].metric).toBe('latency_ms');
335
+ expect(latencyAlerts[0].current_value).toBe(1000);
336
+ expect(latencyAlerts[0].z_score).toBeGreaterThan(3);
337
+ });
338
+ it('should classify latency spike severity based on z-score', () => {
339
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 5, z_score_threshold: 2 }));
340
+ const tc = buildToolCall({ toolName: 'http.severity' });
341
+ // Build tight baseline
342
+ for (let i = 0; i < 10; i++) {
343
+ detector.recordResult(tc, 100, false);
344
+ }
345
+ detector.recordResult(tc, 101, false);
346
+ detector.recordResult(tc, 99, false);
347
+ // Extreme spike
348
+ const alerts = detector.analyzeResult(tc, 5000, false);
349
+ const latencyAlerts = alerts.filter(a => a.anomaly_type === 'latency_spike');
350
+ if (latencyAlerts.length > 0) {
351
+ // z-score for 5000 with mean ~100 should be very high
352
+ expect(latencyAlerts[0].z_score).toBeGreaterThan(6);
353
+ expect(latencyAlerts[0].severity).toBe('high');
354
+ }
355
+ });
356
+ });
357
+ // -----------------------------------------------------------------------
358
+ // Error rate spike detection
359
+ // -----------------------------------------------------------------------
360
+ describe('error rate spike detection', () => {
361
+ it('should not alert for errors when there are fewer than min_samples', () => {
362
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
363
+ const tc = buildToolCall({ actorId: 'err-actor-1' });
364
+ // Record 5 successful calls
365
+ for (let i = 0; i < 5; i++) {
366
+ detector.recordResult(tc, 100, false);
367
+ }
368
+ // An error should not trigger an alert (not enough samples)
369
+ const alerts = detector.analyzeResult(tc, 100, true);
370
+ expect(alerts.filter(a => a.anomaly_type === 'error_rate_spike')).toHaveLength(0);
371
+ });
372
+ it('should alert when error rate spikes from a low baseline', () => {
373
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
374
+ const tc = buildToolCall({ actorId: 'err-actor-2' });
375
+ // Build baseline with all successes (error_rate near 0)
376
+ for (let i = 0; i < 20; i++) {
377
+ detector.recordResult(tc, 100, false);
378
+ }
379
+ // Record the error and analyze
380
+ detector.recordResult(tc, 100, true);
381
+ const alerts = detector.analyzeResult(tc, 100, true);
382
+ const errorAlerts = alerts.filter(a => a.anomaly_type === 'error_rate_spike');
383
+ expect(errorAlerts).toHaveLength(1);
384
+ expect(errorAlerts[0].entity_type).toBe('actor');
385
+ expect(errorAlerts[0].entity_id).toBe('err-actor-2');
386
+ expect(errorAlerts[0].metric).toBe('error_rate');
387
+ });
388
+ it('should not alert for errors when error rate was already high', () => {
389
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
390
+ const tc = buildToolCall({ actorId: 'err-actor-3' });
391
+ // Build baseline with high error rate (50% errors)
392
+ for (let i = 0; i < 20; i++) {
393
+ detector.recordResult(tc, 100, i % 2 === 0); // alternating
394
+ }
395
+ // Another error should not spike (error rate already ~0.5)
396
+ const alerts = detector.analyzeResult(tc, 100, true);
397
+ const errorAlerts = alerts.filter(a => a.anomaly_type === 'error_rate_spike');
398
+ expect(errorAlerts).toHaveLength(0);
399
+ });
400
+ it('should not alert for successful requests', () => {
401
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
402
+ const tc = buildToolCall({ actorId: 'err-actor-4' });
403
+ // Build baseline
404
+ for (let i = 0; i < 15; i++) {
405
+ detector.recordResult(tc, 100, false);
406
+ }
407
+ // Analyze a success -- should not trigger error alert
408
+ const alerts = detector.analyzeResult(tc, 100, false);
409
+ const errorAlerts = alerts.filter(a => a.anomaly_type === 'error_rate_spike');
410
+ expect(errorAlerts).toHaveLength(0);
411
+ });
412
+ });
413
+ // -----------------------------------------------------------------------
414
+ // Request rate spike detection
415
+ // -----------------------------------------------------------------------
416
+ describe('request rate spike detection', () => {
417
+ it('should not alert with fewer than min_samples requests', () => {
418
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 10 }));
419
+ // Send a few requests
420
+ for (let i = 0; i < 5; i++) {
421
+ const alerts = detector.analyze(buildToolCall({ actorId: 'rate-1' }));
422
+ const rateAlerts = alerts.filter(a => a.anomaly_type === 'request_rate_spike');
423
+ expect(rateAlerts).toHaveLength(0);
424
+ }
425
+ });
426
+ it('should build baselines without alerting during normal traffic', () => {
427
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 5 }));
428
+ // Send steady traffic -- should not alert during baseline building
429
+ let rateAlertCount = 0;
430
+ for (let i = 0; i < 20; i++) {
431
+ const alerts = detector.analyze(buildToolCall({ actorId: 'rate-2' }));
432
+ rateAlertCount += alerts.filter(a => a.anomaly_type === 'request_rate_spike').length;
433
+ }
434
+ // Some alerts might fire during the initial window as baselines form,
435
+ // but after steady traffic the system should stabilize
436
+ // We just verify the detector runs without error
437
+ expect(typeof rateAlertCount).toBe('number');
438
+ });
439
+ });
440
+ // -----------------------------------------------------------------------
441
+ // Config options
442
+ // -----------------------------------------------------------------------
443
+ describe('configuration options', () => {
444
+ it('should use custom z_score_threshold', () => {
445
+ // With a very high threshold, latency spikes should not alert
446
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 5, z_score_threshold: 100 }));
447
+ const tc = buildToolCall({ toolName: 'http.threshold' });
448
+ // Build baseline with significant variance so z-scores stay manageable
449
+ const values = [50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 100, 90, 110, 105];
450
+ for (const v of values) {
451
+ detector.recordResult(tc, v, false);
452
+ }
453
+ // A spike that would normally alert with z_score_threshold=3
454
+ // but shouldn't alert with z_score_threshold=100
455
+ const alerts = detector.analyzeResult(tc, 500, false);
456
+ const latencyAlerts = alerts.filter(a => a.anomaly_type === 'latency_spike');
457
+ expect(latencyAlerts).toHaveLength(0);
458
+ });
459
+ it('should use custom min_samples', () => {
460
+ // With min_samples = 3, we should start detecting earlier
461
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 3, z_score_threshold: 2 }));
462
+ const tc = buildToolCall({ toolName: 'http.min' });
463
+ // Build baseline with just 3 samples
464
+ detector.recordResult(tc, 100, false);
465
+ detector.recordResult(tc, 101, false);
466
+ detector.recordResult(tc, 99, false);
467
+ // Should have enough samples now
468
+ const baseline = detector.getBaseline('tool', 'http.min', 'latency');
469
+ expect(baseline).not.toBeNull();
470
+ expect(baseline.count).toBe(3);
471
+ });
472
+ it('should respect track_actors: false', () => {
473
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ track_actors: false }));
474
+ // New tool usage and capability escalation should not be tracked
475
+ detector.analyze(buildToolCall({ actorId: 'no-track', toolName: 'http.get', capability: 'read' }));
476
+ const alerts = detector.analyze(buildToolCall({ actorId: 'no-track', toolName: 'slack.post', capability: 'admin' }));
477
+ const actorAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage' || a.anomaly_type === 'capability_escalation');
478
+ expect(actorAlerts).toHaveLength(0);
479
+ });
480
+ it('should respect track_tools: false', () => {
481
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ track_tools: false }));
482
+ for (let i = 0; i < 15; i++) {
483
+ const alerts = detector.analyze(buildToolCall({ toolName: 'http.get' }));
484
+ const toolRateAlerts = alerts.filter(a => a.anomaly_type === 'request_rate_spike' && a.entity_type === 'tool');
485
+ // No tool-level rate tracking
486
+ expect(toolRateAlerts).toHaveLength(0);
487
+ }
488
+ });
489
+ it('should respect track_workspaces: false', () => {
490
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ track_workspaces: false }));
491
+ for (let i = 0; i < 15; i++) {
492
+ const alerts = detector.analyze(buildToolCall({ workspaceId: 'ws-1' }));
493
+ const wsRateAlerts = alerts.filter(a => a.anomaly_type === 'request_rate_spike' && a.entity_type === 'workspace');
494
+ expect(wsRateAlerts).toHaveLength(0);
495
+ }
496
+ });
497
+ it('should use custom window_ms', () => {
498
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ window_ms: 5000 }));
499
+ const tc = buildToolCall({ toolName: 'http.window' });
500
+ detector.recordResult(tc, 100, false);
501
+ const baseline = detector.getBaseline('tool', 'http.window', 'latency');
502
+ expect(baseline).not.toBeNull();
503
+ expect(baseline.count).toBe(1);
504
+ });
505
+ });
506
+ // -----------------------------------------------------------------------
507
+ // Alert management
508
+ // -----------------------------------------------------------------------
509
+ describe('alert management', () => {
510
+ it('should return recent alerts via getAlerts()', () => {
511
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
512
+ // Generate some alerts by using new tools
513
+ detector.analyze(buildToolCall({ actorId: 'alert-mgmt', toolName: 'tool-a' }));
514
+ detector.analyze(buildToolCall({ actorId: 'alert-mgmt', toolName: 'tool-b' }));
515
+ detector.analyze(buildToolCall({ actorId: 'alert-mgmt', toolName: 'tool-c' }));
516
+ const alerts = detector.getAlerts();
517
+ // Should have new_tool_usage alerts for tool-b and tool-c
518
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
519
+ expect(newToolAlerts.length).toBeGreaterThanOrEqual(2);
520
+ });
521
+ it('should limit alerts with getAlerts(limit)', () => {
522
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
523
+ // Generate multiple alerts
524
+ detector.analyze(buildToolCall({ actorId: 'limit-test', toolName: 'tool-1' }));
525
+ detector.analyze(buildToolCall({ actorId: 'limit-test', toolName: 'tool-2' }));
526
+ detector.analyze(buildToolCall({ actorId: 'limit-test', toolName: 'tool-3' }));
527
+ detector.analyze(buildToolCall({ actorId: 'limit-test', toolName: 'tool-4' }));
528
+ const alerts = detector.getAlerts(2);
529
+ expect(alerts.length).toBeLessThanOrEqual(2);
530
+ });
531
+ it('should filter alerts by entity via getAlertsForEntity()', () => {
532
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
533
+ // Generate alerts for different actors
534
+ detector.analyze(buildToolCall({ actorId: 'entity-a', toolName: 'tool-1' }));
535
+ detector.analyze(buildToolCall({ actorId: 'entity-a', toolName: 'tool-2' })); // new_tool alert
536
+ detector.analyze(buildToolCall({ actorId: 'entity-b', toolName: 'tool-1' }));
537
+ detector.analyze(buildToolCall({ actorId: 'entity-b', toolName: 'tool-3' })); // new_tool alert
538
+ const alertsA = detector.getAlertsForEntity('actor', 'entity-a');
539
+ const alertsB = detector.getAlertsForEntity('actor', 'entity-b');
540
+ // Each should have their own alerts
541
+ for (const alert of alertsA) {
542
+ expect(alert.entity_id).toBe('entity-a');
543
+ }
544
+ for (const alert of alertsB) {
545
+ expect(alert.entity_id).toBe('entity-b');
546
+ }
547
+ });
548
+ it('should return empty array for non-existent entity', () => {
549
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
550
+ const alerts = detector.getAlertsForEntity('actor', 'does-not-exist');
551
+ expect(alerts).toEqual([]);
552
+ });
553
+ it('should cap stored alerts at maxAlerts', () => {
554
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
555
+ // Generate more than 1000 alerts by creating many new tool usages
556
+ // Each new actor gets one bootstrap tool, then every subsequent tool triggers an alert
557
+ for (let i = 0; i < 600; i++) {
558
+ const actorId = `bulk-actor-${i}`;
559
+ detector.analyze(buildToolCall({ actorId, toolName: 'tool-base' }));
560
+ detector.analyze(buildToolCall({ actorId, toolName: 'tool-new' }));
561
+ }
562
+ const allAlerts = detector.getAlerts(2000);
563
+ expect(allAlerts.length).toBeLessThanOrEqual(1000);
564
+ });
565
+ it('should have valid alert fields', () => {
566
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
567
+ detector.analyze(buildToolCall({ actorId: 'field-test', toolName: 'tool-a' }));
568
+ const alerts = detector.analyze(buildToolCall({ actorId: 'field-test', toolName: 'tool-b' }));
569
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
570
+ expect(newToolAlerts).toHaveLength(1);
571
+ const alert = newToolAlerts[0];
572
+ // Verify all required fields are present
573
+ expect(alert.alert_id).toBeTruthy();
574
+ expect(typeof alert.alert_id).toBe('string');
575
+ expect(alert.timestamp).toBeTruthy();
576
+ expect(new Date(alert.timestamp).getTime()).not.toBeNaN();
577
+ expect(alert.anomaly_type).toBe('new_tool_usage');
578
+ expect(alert.entity_type).toBe('actor');
579
+ expect(alert.entity_id).toBe('field-test');
580
+ expect(alert.metric).toBe('new_tool');
581
+ expect(typeof alert.current_value).toBe('number');
582
+ expect(typeof alert.baseline_mean).toBe('number');
583
+ expect(typeof alert.baseline_stddev).toBe('number');
584
+ expect(typeof alert.z_score).toBe('number');
585
+ expect(['low', 'medium', 'high']).toContain(alert.severity);
586
+ });
587
+ it('should generate unique alert_ids', () => {
588
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
589
+ detector.analyze(buildToolCall({ actorId: 'unique-id', toolName: 'tool-a' }));
590
+ detector.analyze(buildToolCall({ actorId: 'unique-id', toolName: 'tool-b' }));
591
+ detector.analyze(buildToolCall({ actorId: 'unique-id', toolName: 'tool-c' }));
592
+ const alerts = detector.getAlerts();
593
+ const ids = alerts.map(a => a.alert_id);
594
+ const uniqueIds = new Set(ids);
595
+ expect(uniqueIds.size).toBe(ids.length);
596
+ });
597
+ });
598
+ // -----------------------------------------------------------------------
599
+ // Baseline queries
600
+ // -----------------------------------------------------------------------
601
+ describe('getBaseline()', () => {
602
+ it('should return null for unknown metrics', () => {
603
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
604
+ const baseline = detector.getBaseline('tool', 'unknown-tool', 'latency');
605
+ expect(baseline).toBeNull();
606
+ });
607
+ it('should return correct stats after recording data', () => {
608
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
609
+ const tc = buildToolCall({ toolName: 'http.stats' });
610
+ // Record 5 latencies: 100, 200, 300, 400, 500
611
+ detector.recordResult(tc, 100, false);
612
+ detector.recordResult(tc, 200, false);
613
+ detector.recordResult(tc, 300, false);
614
+ detector.recordResult(tc, 400, false);
615
+ detector.recordResult(tc, 500, false);
616
+ const baseline = detector.getBaseline('tool', 'http.stats', 'latency');
617
+ expect(baseline).not.toBeNull();
618
+ expect(baseline.count).toBe(5);
619
+ expect(baseline.mean).toBe(300); // (100+200+300+400+500)/5
620
+ expect(baseline.stddev).toBeCloseTo(Math.sqrt(25000), 1); // sample stddev ~ 158.11
621
+ });
622
+ it('should return correct error rate baseline', () => {
623
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
624
+ const tc = buildToolCall({ actorId: 'err-baseline' });
625
+ // Record 8 successes and 2 errors
626
+ for (let i = 0; i < 8; i++) {
627
+ detector.recordResult(tc, 100, false);
628
+ }
629
+ for (let i = 0; i < 2; i++) {
630
+ detector.recordResult(tc, 100, true);
631
+ }
632
+ const baseline = detector.getBaseline('actor', 'err-baseline', 'error_rate');
633
+ expect(baseline).not.toBeNull();
634
+ expect(baseline.count).toBe(10);
635
+ expect(baseline.mean).toBeCloseTo(0.2, 2); // 2/10
636
+ });
637
+ });
638
+ // -----------------------------------------------------------------------
639
+ // reset()
640
+ // -----------------------------------------------------------------------
641
+ describe('reset()', () => {
642
+ it('should clear all tracking data', () => {
643
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
644
+ // Generate some state
645
+ detector.analyze(buildToolCall({ actorId: 'reset-test', toolName: 'tool-a' }));
646
+ detector.analyze(buildToolCall({ actorId: 'reset-test', toolName: 'tool-b' }));
647
+ detector.recordResult(buildToolCall({ toolName: 'http.reset' }), 100, false);
648
+ expect(detector.getAlerts().length).toBeGreaterThan(0);
649
+ expect(detector.getBaseline('tool', 'http.reset', 'latency')).not.toBeNull();
650
+ // Reset
651
+ detector.reset();
652
+ // Everything should be cleared
653
+ expect(detector.getAlerts()).toEqual([]);
654
+ expect(detector.getBaseline('tool', 'http.reset', 'latency')).toBeNull();
655
+ });
656
+ it('should allow new tool usage alerts again after reset', () => {
657
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
658
+ // Build tool history
659
+ detector.analyze(buildToolCall({ actorId: 'reset-actor', toolName: 'tool-a' }));
660
+ detector.analyze(buildToolCall({ actorId: 'reset-actor', toolName: 'tool-b' }));
661
+ detector.reset();
662
+ // After reset, tool-a should not trigger new_tool_usage (first tool again)
663
+ const alerts1 = detector.analyze(buildToolCall({ actorId: 'reset-actor', toolName: 'tool-a' }));
664
+ expect(alerts1.filter(a => a.anomaly_type === 'new_tool_usage')).toHaveLength(0);
665
+ // tool-b should now trigger new_tool_usage (it's the second tool after reset)
666
+ const alerts2 = detector.analyze(buildToolCall({ actorId: 'reset-actor', toolName: 'tool-b' }));
667
+ expect(alerts2.filter(a => a.anomaly_type === 'new_tool_usage')).toHaveLength(1);
668
+ });
669
+ it('should allow capability escalation alerts again after reset', () => {
670
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
671
+ // Build capability history
672
+ detector.analyze(buildToolCall({ actorId: 'reset-cap', capability: 'read' }));
673
+ detector.analyze(buildToolCall({ actorId: 'reset-cap', capability: 'write' }));
674
+ detector.reset();
675
+ // After reset, read should not alert (first cap)
676
+ const alerts1 = detector.analyze(buildToolCall({ actorId: 'reset-cap', capability: 'read' }));
677
+ expect(alerts1.filter(a => a.anomaly_type === 'capability_escalation')).toHaveLength(0);
678
+ // write should alert again (escalation from read)
679
+ const alerts2 = detector.analyze(buildToolCall({ actorId: 'reset-cap', capability: 'write' }));
680
+ expect(alerts2.filter(a => a.anomaly_type === 'capability_escalation')).toHaveLength(1);
681
+ });
682
+ });
683
+ // -----------------------------------------------------------------------
684
+ // Window pruning (data expiry)
685
+ // -----------------------------------------------------------------------
686
+ describe('window pruning', () => {
687
+ beforeEach(() => {
688
+ jest.useFakeTimers();
689
+ });
690
+ afterEach(() => {
691
+ jest.useRealTimers();
692
+ });
693
+ it('should prune old data points outside the window', () => {
694
+ const windowMs = 10000; // 10 seconds
695
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ window_ms: windowMs }));
696
+ const tc = buildToolCall({ toolName: 'http.prune' });
697
+ // Record at t=0
698
+ detector.recordResult(tc, 100, false);
699
+ expect(detector.getBaseline('tool', 'http.prune', 'latency').count).toBe(1);
700
+ // Advance past window
701
+ jest.advanceTimersByTime(windowMs + 1);
702
+ // Record a new point -- the old one should be pruned
703
+ detector.recordResult(tc, 200, false);
704
+ const baseline = detector.getBaseline('tool', 'http.prune', 'latency');
705
+ expect(baseline.count).toBe(1);
706
+ expect(baseline.mean).toBe(200); // Only the new data point
707
+ });
708
+ it('should keep data points within the window', () => {
709
+ const windowMs = 10000;
710
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ window_ms: windowMs }));
711
+ const tc = buildToolCall({ toolName: 'http.keep' });
712
+ // Record at t=0
713
+ detector.recordResult(tc, 100, false);
714
+ // Advance by half the window
715
+ jest.advanceTimersByTime(windowMs / 2);
716
+ // Record at t=5s
717
+ detector.recordResult(tc, 200, false);
718
+ // Both should still be in the window
719
+ const baseline = detector.getBaseline('tool', 'http.keep', 'latency');
720
+ expect(baseline.count).toBe(2);
721
+ expect(baseline.mean).toBe(150);
722
+ });
723
+ });
724
+ // -----------------------------------------------------------------------
725
+ // Rolling window statistics (mean, stddev, z-score)
726
+ // -----------------------------------------------------------------------
727
+ describe('rolling window statistics', () => {
728
+ it('should compute correct mean', () => {
729
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
730
+ const tc = buildToolCall({ toolName: 'http.mean' });
731
+ detector.recordResult(tc, 10, false);
732
+ detector.recordResult(tc, 20, false);
733
+ detector.recordResult(tc, 30, false);
734
+ const baseline = detector.getBaseline('tool', 'http.mean', 'latency');
735
+ expect(baseline.mean).toBe(20);
736
+ });
737
+ it('should compute correct stddev', () => {
738
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
739
+ const tc = buildToolCall({ toolName: 'http.stddev' });
740
+ // Values: 2, 4, 4, 4, 5, 5, 7, 9
741
+ // Mean = 5, Sample Variance = 4.571..., Sample Stddev = ~2.138
742
+ [2, 4, 4, 4, 5, 5, 7, 9].forEach(v => {
743
+ detector.recordResult(tc, v, false);
744
+ });
745
+ const baseline = detector.getBaseline('tool', 'http.stddev', 'latency');
746
+ expect(baseline.mean).toBe(5);
747
+ expect(baseline.stddev).toBeCloseTo(2.138, 2);
748
+ });
749
+ it('should return stddev of 0 with a single data point', () => {
750
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
751
+ const tc = buildToolCall({ toolName: 'http.single' });
752
+ detector.recordResult(tc, 100, false);
753
+ const baseline = detector.getBaseline('tool', 'http.single', 'latency');
754
+ expect(baseline.stddev).toBe(0);
755
+ });
756
+ it('should return mean of 0 with no data points', () => {
757
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
758
+ const baseline = detector.getBaseline('tool', 'http.empty', 'latency');
759
+ expect(baseline).toBeNull();
760
+ });
761
+ });
762
+ // -----------------------------------------------------------------------
763
+ // Severity classification
764
+ // -----------------------------------------------------------------------
765
+ describe('severity classification', () => {
766
+ it('should classify z-score 3-4 as low', () => {
767
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ min_samples: 5, z_score_threshold: 2 }));
768
+ const tc = buildToolCall({ toolName: 'http.sev-low' });
769
+ // Build baseline with values where we can control z-score
770
+ // Values: 10 x 100, then record 101, 99 for tiny variance
771
+ for (let i = 0; i < 10; i++) {
772
+ detector.recordResult(tc, 100, false);
773
+ }
774
+ detector.recordResult(tc, 102, false);
775
+ detector.recordResult(tc, 98, false);
776
+ // The baseline has mean ~100, tiny stddev
777
+ // We need a spike that gives z ~3-4
778
+ const baseline = detector.getBaseline('tool', 'http.sev-low', 'latency');
779
+ if (baseline && baseline.stddev > 0) {
780
+ // Compute a value that would give z ~ 3.5
781
+ const targetValue = baseline.mean + 3.5 * baseline.stddev;
782
+ const alerts = detector.analyzeResult(tc, targetValue, false);
783
+ const latencyAlerts = alerts.filter(a => a.anomaly_type === 'latency_spike');
784
+ if (latencyAlerts.length > 0) {
785
+ expect(latencyAlerts[0].severity).toBe('low');
786
+ }
787
+ }
788
+ });
789
+ });
790
+ // -----------------------------------------------------------------------
791
+ // Gateway integration (action modes)
792
+ // -----------------------------------------------------------------------
793
+ describe('action modes', () => {
794
+ it('should not block on action: "log"', () => {
795
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ action: 'log' }));
796
+ // Even if alerts are generated, action=log means we just record them
797
+ detector.analyze(buildToolCall({ actorId: 'log-test', toolName: 'tool-a' }));
798
+ const alerts = detector.analyze(buildToolCall({ actorId: 'log-test', toolName: 'tool-b' }));
799
+ // Alerts are generated but action is just 'log'
800
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
801
+ expect(newToolAlerts).toHaveLength(1);
802
+ // The detector itself doesn't enforce actions -- that's the gateway's job
803
+ // We just verify alerts are still produced
804
+ });
805
+ it('should not block on action: "flag"', () => {
806
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ action: 'flag' }));
807
+ detector.analyze(buildToolCall({ actorId: 'flag-test', toolName: 'tool-a' }));
808
+ const alerts = detector.analyze(buildToolCall({ actorId: 'flag-test', toolName: 'tool-b' }));
809
+ const newToolAlerts = alerts.filter(a => a.anomaly_type === 'new_tool_usage');
810
+ expect(newToolAlerts).toHaveLength(1);
811
+ });
812
+ it('action: "block" with high severity should produce alerts for gateway to act on', () => {
813
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig({ action: 'block' }));
814
+ // Generate a high severity alert (capability escalation to admin)
815
+ detector.analyze(buildToolCall({ actorId: 'block-test', capability: 'read' }));
816
+ const alerts = detector.analyze(buildToolCall({ actorId: 'block-test', capability: 'admin' }));
817
+ const highAlerts = alerts.filter(a => a.severity === 'high');
818
+ expect(highAlerts.length).toBeGreaterThanOrEqual(1);
819
+ });
820
+ });
821
+ // -----------------------------------------------------------------------
822
+ // Multiple anomaly types in single call
823
+ // -----------------------------------------------------------------------
824
+ describe('multiple anomaly types in single call', () => {
825
+ beforeEach(() => {
826
+ jest.useFakeTimers();
827
+ });
828
+ afterEach(() => {
829
+ jest.useRealTimers();
830
+ });
831
+ it('should detect multiple anomaly types simultaneously', () => {
832
+ // Establish history during business hours on a weekday
833
+ jest.setSystemTime(new Date('2025-01-15T14:00:00Z')); // Wednesday 2pm
834
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
835
+ // First, establish some history during business hours (no off-hours alert)
836
+ detector.analyze(buildToolCall({ actorId: 'multi', toolName: 'http.get', capability: 'read' }));
837
+ // Now set time to Saturday 3am UTC (off hours + weekend)
838
+ jest.setSystemTime(new Date('2025-01-18T03:00:00Z'));
839
+ // Trigger multiple anomalies at once:
840
+ // - new_tool_usage (slack.post is new)
841
+ // - capability_escalation (admin is new)
842
+ // - off_hours_activity (3am Saturday -- first time at this hour)
843
+ const alerts = detector.analyze(buildToolCall({
844
+ actorId: 'multi',
845
+ toolName: 'slack.post',
846
+ capability: 'admin',
847
+ }));
848
+ const types = new Set(alerts.map(a => a.anomaly_type));
849
+ expect(types.has('new_tool_usage')).toBe(true);
850
+ expect(types.has('capability_escalation')).toBe(true);
851
+ expect(types.has('off_hours_activity')).toBe(true);
852
+ });
853
+ });
854
+ // -----------------------------------------------------------------------
855
+ // Edge cases
856
+ // -----------------------------------------------------------------------
857
+ describe('edge cases', () => {
858
+ it('should handle empty actor id', () => {
859
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
860
+ const tc = buildToolCall({ actorId: '' });
861
+ // Should not throw
862
+ const alerts = detector.analyze(tc);
863
+ expect(Array.isArray(alerts)).toBe(true);
864
+ });
865
+ it('should handle empty tool name', () => {
866
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
867
+ const tc = buildToolCall({ toolName: '' });
868
+ const alerts = detector.analyze(tc);
869
+ expect(Array.isArray(alerts)).toBe(true);
870
+ });
871
+ it('should handle zero latency', () => {
872
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
873
+ const tc = buildToolCall();
874
+ detector.recordResult(tc, 0, false);
875
+ const baseline = detector.getBaseline('tool', tc.tool.name, 'latency');
876
+ expect(baseline.mean).toBe(0);
877
+ });
878
+ it('should handle negative latency gracefully', () => {
879
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
880
+ const tc = buildToolCall();
881
+ // Shouldn't throw
882
+ detector.recordResult(tc, -1, false);
883
+ const baseline = detector.getBaseline('tool', tc.tool.name, 'latency');
884
+ expect(baseline).not.toBeNull();
885
+ });
886
+ it('should handle very large latency values', () => {
887
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
888
+ const tc = buildToolCall({ toolName: 'http.large' });
889
+ detector.recordResult(tc, Number.MAX_SAFE_INTEGER, false);
890
+ const baseline = detector.getBaseline('tool', 'http.large', 'latency');
891
+ expect(baseline.mean).toBe(Number.MAX_SAFE_INTEGER);
892
+ });
893
+ it('should handle concurrent analysis of many actors', () => {
894
+ const detector = new anomaly_1.AnomalyDetector(defaultConfig());
895
+ // Simulate many different actors
896
+ for (let i = 0; i < 100; i++) {
897
+ const alerts = detector.analyze(buildToolCall({ actorId: `actor-${i}` }));
898
+ expect(Array.isArray(alerts)).toBe(true);
899
+ }
900
+ });
901
+ });
902
+ });
903
+ //# sourceMappingURL=anomaly-detector.test.js.map