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,1039 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const http = __importStar(require("http"));
37
+ const gateway_1 = require("../../src/server/gateway");
38
+ const metrics_1 = require("../../src/metrics");
39
+ const tracing_1 = require("../../src/tracing");
40
+ // ---------------------------------------------------------------------------
41
+ // Local test HTTP server
42
+ // ---------------------------------------------------------------------------
43
+ let testServer;
44
+ let testPort;
45
+ beforeAll((done) => {
46
+ testServer = http.createServer((req, res) => {
47
+ res.writeHead(200, { 'Content-Type': 'application/json' });
48
+ res.end(JSON.stringify({ ok: true }));
49
+ });
50
+ testServer.listen(0, '127.0.0.1', () => {
51
+ testPort = testServer.address().port;
52
+ done();
53
+ });
54
+ });
55
+ afterAll((done) => {
56
+ testServer.close(done);
57
+ });
58
+ // ---------------------------------------------------------------------------
59
+ // Test config factory
60
+ // ---------------------------------------------------------------------------
61
+ function testConfig(overrides) {
62
+ return {
63
+ port: 0,
64
+ host: '127.0.0.1',
65
+ auth: { enabled: false, api_keys: {} },
66
+ policy: { pack_path: './policy-packs/dev_fast.yaml', default_effect: 'ALLOW', hot_reload: false },
67
+ dlp: { enabled: false, scan_args: false, scan_output: false, secrets_detection: false, pii_detection: false, default_redaction_method: 'mask' },
68
+ budget: { task_budget_usd: 100, max_steps_per_task: 1000, max_retries_per_call: 3, max_wall_clock_ms: 300000 },
69
+ audit: { enabled: false, log_dir: '', console_output: false, retention_days: 30 },
70
+ executor: { http: { timeout_ms: 5000, max_retries: 1, backoff_base_ms: 100 }, cache: { enabled: false, ttl_ms: 0 } },
71
+ approval: { enabled: false, token_secret: 'test-secret', default_ttl_seconds: 3600 },
72
+ ...overrides,
73
+ };
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Tool call builder
77
+ // ---------------------------------------------------------------------------
78
+ let callCounter = 0;
79
+ function buildToolCall(overrides) {
80
+ callCounter++;
81
+ return {
82
+ tool_call_id: `tc-branch-${Date.now()}-${callCounter}`,
83
+ task_id: 'task-branch-001',
84
+ workspace_id: 'ws-test',
85
+ actor: { type: 'agent', id: 'test-agent' },
86
+ source: { platform: 'unit-test' },
87
+ tool: { name: 'http.request', capability: 'read' },
88
+ args: { method: 'GET', url: `http://127.0.0.1:${testPort}/get` },
89
+ ...overrides,
90
+ };
91
+ }
92
+ /** Disable SSRF protection so tests can reach 127.0.0.1 */
93
+ function disableSSRF(gw) {
94
+ gw.getHttpExecutor().ssrfProtectionEnabled = false;
95
+ return gw;
96
+ }
97
+ // ===========================================================================
98
+ // Tests
99
+ // ===========================================================================
100
+ describe('Gateway Branches', () => {
101
+ // =========================================================================
102
+ // 1. DLP scanning branches in preExecute
103
+ // =========================================================================
104
+ describe('DLP scanning branches in preExecute', () => {
105
+ let gateway;
106
+ afterEach(() => {
107
+ gateway.shutdown();
108
+ });
109
+ it('detects secrets in args when DLP is enabled and scan_args is true', async () => {
110
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
111
+ dlp: {
112
+ enabled: true,
113
+ scan_args: true,
114
+ scan_output: false,
115
+ secrets_detection: true,
116
+ pii_detection: false,
117
+ default_redaction_method: 'mask',
118
+ },
119
+ })));
120
+ const tc = buildToolCall({
121
+ args: {
122
+ method: 'GET',
123
+ url: `http://127.0.0.1:${testPort}/get`,
124
+ api_key: 'AKIAIOSFODNN7EXAMPLE',
125
+ },
126
+ });
127
+ const pre = await gateway.preExecute(tc);
128
+ // DLP should detect the AWS key pattern in args
129
+ expect(pre.argsDlp).toBeDefined();
130
+ expect(pre.argsDlp.detected.length).toBeGreaterThan(0);
131
+ expect(pre.argsDlp.detected.some((d) => d.includes('aws') || d.includes('secret') || d.includes('key'))).toBe(true);
132
+ });
133
+ it('returns severity high when AWS key is detected in args', async () => {
134
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
135
+ dlp: {
136
+ enabled: true,
137
+ scan_args: true,
138
+ scan_output: false,
139
+ secrets_detection: true,
140
+ pii_detection: false,
141
+ default_redaction_method: 'mask',
142
+ },
143
+ })));
144
+ const tc = buildToolCall({
145
+ args: {
146
+ method: 'GET',
147
+ url: `http://127.0.0.1:${testPort}/get`,
148
+ secret: 'AKIAIOSFODNN7EXAMPLE',
149
+ },
150
+ });
151
+ const pre = await gateway.preExecute(tc);
152
+ expect(pre.argsDlp).toBeDefined();
153
+ expect(pre.argsDlp.severity).toBe('high');
154
+ });
155
+ it('blocks when DLP severity is critical via policy default_effect DENY and DLP detection', async () => {
156
+ // With default_effect DENY and DLP detection the policy will deny
157
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
158
+ policy: { pack_path: './policy-packs/dev_fast.yaml', default_effect: 'DENY', hot_reload: false },
159
+ dlp: {
160
+ enabled: true,
161
+ scan_args: true,
162
+ scan_output: false,
163
+ secrets_detection: true,
164
+ pii_detection: true,
165
+ default_redaction_method: 'mask',
166
+ },
167
+ })));
168
+ const tc = buildToolCall({
169
+ args: {
170
+ method: 'GET',
171
+ url: `http://127.0.0.1:${testPort}/get`,
172
+ credentials: 'AKIAIOSFODNN7EXAMPLE',
173
+ },
174
+ });
175
+ const pre = await gateway.preExecute(tc);
176
+ // DLP detects secrets; the request should still be allowed by policy (dev_fast allows reads)
177
+ // but the DLP info should be attached
178
+ expect(pre.argsDlp).toBeDefined();
179
+ expect(pre.argsDlp.detected.length).toBeGreaterThan(0);
180
+ });
181
+ it('allows with DLP info when severity is low (no secrets/PII)', async () => {
182
+ // Disable PII detection to avoid matching on 127.0.0.1 in the URL
183
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
184
+ dlp: {
185
+ enabled: true,
186
+ scan_args: true,
187
+ scan_output: false,
188
+ secrets_detection: true,
189
+ pii_detection: false,
190
+ default_redaction_method: 'mask',
191
+ },
192
+ })));
193
+ const tc = buildToolCall({
194
+ args: {
195
+ method: 'GET',
196
+ url: `http://127.0.0.1:${testPort}/get`,
197
+ data: 'hello world',
198
+ },
199
+ });
200
+ const pre = await gateway.preExecute(tc);
201
+ expect(pre.allowed).toBe(true);
202
+ expect(pre.argsDlp).toBeDefined();
203
+ expect(pre.argsDlp.detected.length).toBe(0);
204
+ expect(pre.argsDlp.severity).toBe('low');
205
+ });
206
+ it('does not scan args when scan_args is false', async () => {
207
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
208
+ dlp: {
209
+ enabled: true,
210
+ scan_args: false,
211
+ scan_output: false,
212
+ secrets_detection: true,
213
+ pii_detection: true,
214
+ default_redaction_method: 'mask',
215
+ },
216
+ })));
217
+ const tc = buildToolCall({
218
+ args: {
219
+ method: 'GET',
220
+ url: `http://127.0.0.1:${testPort}/get`,
221
+ secret: 'AKIAIOSFODNN7EXAMPLE',
222
+ },
223
+ });
224
+ const pre = await gateway.preExecute(tc);
225
+ expect(pre.argsDlp).toBeDefined();
226
+ // When scan_args is false, no detections should be made
227
+ expect(pre.argsDlp.detected.length).toBe(0);
228
+ });
229
+ it('detects PII (credit card) in args when pii_detection is enabled', async () => {
230
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
231
+ dlp: {
232
+ enabled: true,
233
+ scan_args: true,
234
+ scan_output: false,
235
+ secrets_detection: false,
236
+ pii_detection: true,
237
+ default_redaction_method: 'mask',
238
+ },
239
+ })));
240
+ const tc = buildToolCall({
241
+ args: {
242
+ method: 'GET',
243
+ url: `http://127.0.0.1:${testPort}/get`,
244
+ card: '4111-1111-1111-1111',
245
+ },
246
+ });
247
+ const pre = await gateway.preExecute(tc);
248
+ expect(pre.argsDlp).toBeDefined();
249
+ expect(pre.argsDlp.detected.length).toBeGreaterThan(0);
250
+ });
251
+ });
252
+ // =========================================================================
253
+ // 2. Budget checking branches in preExecute
254
+ // =========================================================================
255
+ describe('Budget checking branches in preExecute', () => {
256
+ let gateway;
257
+ afterEach(() => {
258
+ gateway.shutdown();
259
+ });
260
+ it('blocks when task budget is exceeded (very low budget)', async () => {
261
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
262
+ budget: {
263
+ task_budget_usd: 0.0001,
264
+ max_steps_per_task: 1000,
265
+ max_retries_per_call: 3,
266
+ max_wall_clock_ms: 300000,
267
+ },
268
+ })));
269
+ const tc = buildToolCall();
270
+ const pre = await gateway.preExecute(tc);
271
+ // With an extremely low budget, the estimated cost should exceed it
272
+ expect(pre.allowed).toBe(false);
273
+ expect(pre.result).toBeDefined();
274
+ expect(pre.result.status).toBe('blocked');
275
+ expect(pre.result.error).toContain('Budget exceeded');
276
+ });
277
+ it('blocks when step limit is exceeded', async () => {
278
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
279
+ budget: {
280
+ task_budget_usd: 100,
281
+ max_steps_per_task: 1,
282
+ max_retries_per_call: 3,
283
+ max_wall_clock_ms: 300000,
284
+ },
285
+ })));
286
+ // First call should succeed (1 step allowed)
287
+ const tc1 = buildToolCall({ task_id: 'task-step-limit' });
288
+ const result1 = await gateway.execute(tc1);
289
+ expect(result1.status).toBe('ok');
290
+ // Second call on same task should be blocked (max_steps_per_task: 1)
291
+ const tc2 = buildToolCall({ task_id: 'task-step-limit' });
292
+ const result2 = await gateway.execute(tc2);
293
+ expect(result2.status).toBe('blocked');
294
+ expect(result2.error).toContain('Budget exceeded');
295
+ });
296
+ it('budget block includes budget report in the result', async () => {
297
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
298
+ budget: {
299
+ task_budget_usd: 0.0001,
300
+ max_steps_per_task: 1000,
301
+ max_retries_per_call: 3,
302
+ max_wall_clock_ms: 300000,
303
+ },
304
+ })));
305
+ const tc = buildToolCall();
306
+ const result = await gateway.execute(tc);
307
+ expect(result.status).toBe('blocked');
308
+ expect(result.budget).toBeDefined();
309
+ expect(result.budget.estimated_cost_usd).toBeGreaterThanOrEqual(0);
310
+ });
311
+ it('blocks with workspace budget when workspace_daily_budget_usd is exceeded', async () => {
312
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
313
+ budget: {
314
+ task_budget_usd: 100,
315
+ max_steps_per_task: 1000,
316
+ max_retries_per_call: 3,
317
+ max_wall_clock_ms: 300000,
318
+ workspace_daily_budget_usd: 0.0001,
319
+ },
320
+ })));
321
+ const tc = buildToolCall();
322
+ const pre = await gateway.preExecute(tc);
323
+ expect(pre.allowed).toBe(false);
324
+ expect(pre.result).toBeDefined();
325
+ expect(pre.result.error).toContain('Budget exceeded');
326
+ });
327
+ });
328
+ // =========================================================================
329
+ // 3. Post-execute pipeline branches
330
+ // =========================================================================
331
+ describe('Post-execute pipeline branches', () => {
332
+ let gateway;
333
+ afterEach(() => {
334
+ gateway.shutdown();
335
+ });
336
+ it('DLP scans output when scan_output is true', async () => {
337
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
338
+ dlp: {
339
+ enabled: true,
340
+ scan_args: false,
341
+ scan_output: true,
342
+ secrets_detection: true,
343
+ pii_detection: true,
344
+ default_redaction_method: 'mask',
345
+ },
346
+ })));
347
+ const tc = buildToolCall();
348
+ const result = await gateway.execute(tc);
349
+ // Even if output is clean, the DLP field should exist in the result
350
+ expect(result.status).toBe('ok');
351
+ expect(result.dlp).toBeDefined();
352
+ expect(result.dlp.severity).toBeDefined();
353
+ });
354
+ it('DLP output detections are included in result when output contains secrets', async () => {
355
+ // Create a server that returns a secret in the response body
356
+ const secretServer = http.createServer((req, res) => {
357
+ res.writeHead(200, { 'Content-Type': 'application/json' });
358
+ res.end(JSON.stringify({ api_key: 'AKIAIOSFODNN7EXAMPLE', data: 'test' }));
359
+ });
360
+ await new Promise((resolve) => {
361
+ secretServer.listen(0, '127.0.0.1', () => resolve());
362
+ });
363
+ const secretPort = secretServer.address().port;
364
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
365
+ dlp: {
366
+ enabled: true,
367
+ scan_args: false,
368
+ scan_output: true,
369
+ secrets_detection: true,
370
+ pii_detection: false,
371
+ default_redaction_method: 'mask',
372
+ },
373
+ })));
374
+ const tc = buildToolCall({
375
+ args: { method: 'GET', url: `http://127.0.0.1:${secretPort}/data` },
376
+ });
377
+ const result = await gateway.execute(tc);
378
+ expect(result.status).toBe('ok');
379
+ expect(result.dlp).toBeDefined();
380
+ expect(result.dlp.detected.length).toBeGreaterThan(0);
381
+ await new Promise((resolve) => secretServer.close(() => resolve()));
382
+ });
383
+ it('budget is recorded after successful execution', async () => {
384
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
385
+ const tc = buildToolCall();
386
+ const result = await gateway.execute(tc);
387
+ expect(result.status).toBe('ok');
388
+ expect(result.budget).toBeDefined();
389
+ expect(result.budget.estimated_cost_usd).toBeGreaterThanOrEqual(0);
390
+ expect(result.budget.spent_cost_usd_task).toBeGreaterThanOrEqual(0);
391
+ });
392
+ it('records metrics (recordLLMUsage) when output has model/usage info', async () => {
393
+ // Create a server that returns LLM-style usage info
394
+ const llmServer = http.createServer((req, res) => {
395
+ res.writeHead(200, { 'Content-Type': 'application/json' });
396
+ res.end(JSON.stringify({
397
+ model: 'gpt-4',
398
+ usage: {
399
+ prompt_tokens: 100,
400
+ completion_tokens: 50,
401
+ total_tokens: 150,
402
+ },
403
+ choices: [{ message: { content: 'Hello' } }],
404
+ }));
405
+ });
406
+ await new Promise((resolve) => {
407
+ llmServer.listen(0, '127.0.0.1', () => resolve());
408
+ });
409
+ const llmPort = llmServer.address().port;
410
+ const metrics = new metrics_1.GatewayMetrics();
411
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), metrics));
412
+ const tc = buildToolCall({
413
+ args: { method: 'POST', url: `http://127.0.0.1:${llmPort}/v1/chat/completions`, model: 'gpt-4' },
414
+ });
415
+ const result = await gateway.execute(tc);
416
+ expect(result.status).toBe('ok');
417
+ // Metrics should have been recorded -- check that metricsText includes LLM data
418
+ const metricsText = await metrics.getMetrics();
419
+ expect(metricsText).toContain('palaryn_requests_total');
420
+ await new Promise((resolve) => llmServer.close(() => resolve()));
421
+ });
422
+ it('records tool execution metrics on success', async () => {
423
+ const metrics = new metrics_1.GatewayMetrics();
424
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), metrics));
425
+ const tc = buildToolCall();
426
+ const result = await gateway.execute(tc);
427
+ expect(result.status).toBe('ok');
428
+ const metricsText = await metrics.getMetrics();
429
+ expect(metricsText).toContain('palaryn_requests_total');
430
+ // Should record an 'ok' status
431
+ expect(metricsText).toContain('status="ok"');
432
+ });
433
+ });
434
+ // =========================================================================
435
+ // 4. Anomaly detection branches
436
+ // =========================================================================
437
+ describe('Anomaly detection branches', () => {
438
+ let gateway;
439
+ afterEach(() => {
440
+ const detector = gateway.getAnomalyDetector();
441
+ if (detector)
442
+ detector.destroy();
443
+ gateway.shutdown();
444
+ });
445
+ it('anomaly detected logs but allows by default (action: log)', async () => {
446
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
447
+ anomaly: {
448
+ enabled: true,
449
+ window_ms: 3600000,
450
+ z_score_threshold: 3,
451
+ min_samples: 10,
452
+ action: 'log',
453
+ track_actors: true,
454
+ track_tools: true,
455
+ track_workspaces: true,
456
+ },
457
+ })));
458
+ const tc = buildToolCall();
459
+ const pre = await gateway.preExecute(tc);
460
+ // With action 'log' (default), anomalies should not block
461
+ expect(pre.allowed).toBe(true);
462
+ });
463
+ it('anomaly detector is created when anomaly config is enabled', () => {
464
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
465
+ anomaly: {
466
+ enabled: true,
467
+ window_ms: 3600000,
468
+ z_score_threshold: 3,
469
+ min_samples: 10,
470
+ action: 'log',
471
+ },
472
+ })));
473
+ expect(gateway.getAnomalyDetector()).toBeDefined();
474
+ });
475
+ it('anomaly detector is not created when anomaly config is absent', () => {
476
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
477
+ expect(gateway.getAnomalyDetector()).toBeUndefined();
478
+ });
479
+ it('getAnomalyBaseline returns baseline data after requests', async () => {
480
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
481
+ anomaly: {
482
+ enabled: true,
483
+ window_ms: 3600000,
484
+ z_score_threshold: 3,
485
+ min_samples: 1,
486
+ action: 'log',
487
+ track_actors: true,
488
+ track_tools: true,
489
+ track_workspaces: true,
490
+ },
491
+ })));
492
+ // Make a request so anomaly detector records data
493
+ const tc = buildToolCall();
494
+ await gateway.execute(tc);
495
+ const detector = gateway.getAnomalyDetector();
496
+ // After at least one request, the detector should have some baseline data
497
+ const baseline = detector.getBaseline('actor', 'test-agent', 'request_rate');
498
+ // Baseline may or may not exist depending on internal tracking, but the method should not throw
499
+ // The rolling window should have some data points
500
+ const report = detector.getBaselineReport();
501
+ expect(report).toBeDefined();
502
+ expect(report.current).toBeDefined();
503
+ expect(report.baseline).toBeDefined();
504
+ });
505
+ it('anomaly block when action is block and high severity alert', async () => {
506
+ // Use very low thresholds to trigger anomaly on burst
507
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
508
+ anomaly: {
509
+ enabled: true,
510
+ window_ms: 60000,
511
+ z_score_threshold: 0.01, // extremely low threshold to trigger easily
512
+ min_samples: 2,
513
+ action: 'block',
514
+ track_actors: true,
515
+ track_tools: true,
516
+ track_workspaces: true,
517
+ },
518
+ })));
519
+ // Build a baseline first with unique task_ids to avoid idempotency
520
+ for (let i = 0; i < 5; i++) {
521
+ const tc = buildToolCall({ task_id: `task-anomaly-baseline-${i}` });
522
+ await gateway.preExecute(tc);
523
+ }
524
+ // The anomaly detector tracks request rate; with very low z_score_threshold
525
+ // subsequent requests should trigger anomaly alerts. However, blocking only
526
+ // happens if a 'high' severity alert is present. The severity assignment
527
+ // depends on z-scores. We verify the gateway respects the block action config.
528
+ const tc = buildToolCall({ task_id: 'task-anomaly-block' });
529
+ const pre = await gateway.preExecute(tc);
530
+ // The result depends on whether alerts met the 'high' severity threshold.
531
+ // We primarily test that the code path does not throw.
532
+ expect(pre).toBeDefined();
533
+ expect(typeof pre.allowed).toBe('boolean');
534
+ });
535
+ });
536
+ // =========================================================================
537
+ // 5. reportUsage method
538
+ // =========================================================================
539
+ describe('reportUsage method', () => {
540
+ let gateway;
541
+ afterEach(() => {
542
+ gateway.shutdown();
543
+ });
544
+ it('records cost via reportUsage with valid usage data', () => {
545
+ const metrics = new metrics_1.GatewayMetrics();
546
+ gateway = new gateway_1.Gateway(testConfig(), metrics);
547
+ // This should not throw
548
+ gateway.reportUsage({
549
+ tool_call_id: 'tc-usage-001',
550
+ task_id: 'task-usage-001',
551
+ workspace_id: 'ws-test',
552
+ actor_id: 'test-agent',
553
+ actual_cost_usd: 0.05,
554
+ });
555
+ // No exception = success. We can verify via metrics.
556
+ expect(true).toBe(true);
557
+ });
558
+ it('records token usage via reportUsage with LLM usage data', async () => {
559
+ const metrics = new metrics_1.GatewayMetrics();
560
+ gateway = new gateway_1.Gateway(testConfig(), metrics);
561
+ gateway.reportUsage({
562
+ tool_call_id: 'tc-usage-002',
563
+ task_id: 'task-usage-002',
564
+ workspace_id: 'ws-test',
565
+ actor_id: 'test-agent',
566
+ actual_cost_usd: 0.10,
567
+ usage: {
568
+ input_tokens: 500,
569
+ output_tokens: 200,
570
+ total_tokens: 700,
571
+ },
572
+ });
573
+ const metricsText = await metrics.getMetrics();
574
+ // Should contain cost and token counters
575
+ expect(metricsText).toContain('palaryn_cost_usd_total');
576
+ expect(metricsText).toContain('palaryn_token_usage_total');
577
+ });
578
+ it('reportUsage works without metrics (no crash)', () => {
579
+ gateway = new gateway_1.Gateway(testConfig());
580
+ // No metrics passed, should not throw
581
+ gateway.reportUsage({
582
+ tool_call_id: 'tc-usage-003',
583
+ task_id: 'task-usage-003',
584
+ actual_cost_usd: 0.01,
585
+ });
586
+ });
587
+ it('reportUsage handles missing workspace and actor', () => {
588
+ gateway = new gateway_1.Gateway(testConfig());
589
+ // workspace_id and actor_id not provided
590
+ gateway.reportUsage({
591
+ tool_call_id: 'tc-usage-004',
592
+ task_id: 'task-usage-004',
593
+ });
594
+ });
595
+ });
596
+ // =========================================================================
597
+ // 6. getTaskTrace
598
+ // =========================================================================
599
+ describe('getTaskTrace', () => {
600
+ let gateway;
601
+ afterEach(() => {
602
+ gateway.shutdown();
603
+ });
604
+ it('returns events filtered by task_id after execution', async () => {
605
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
606
+ audit: { enabled: true, log_dir: '', console_output: false, retention_days: 30 },
607
+ })));
608
+ const taskId = `task-trace-${Date.now()}`;
609
+ const tc = buildToolCall({ task_id: taskId });
610
+ await gateway.execute(tc);
611
+ const trace = gateway.getTaskTrace(taskId);
612
+ expect(trace).toBeDefined();
613
+ expect(Array.isArray(trace)).toBe(true);
614
+ // There should be at least one audit event for this task
615
+ expect(trace.length).toBeGreaterThan(0);
616
+ });
617
+ it('returns empty array for unknown task_id', () => {
618
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
619
+ audit: { enabled: true, log_dir: '', console_output: false, retention_days: 30 },
620
+ })));
621
+ const trace = gateway.getTaskTrace('nonexistent-task');
622
+ expect(trace).toBeDefined();
623
+ expect(Array.isArray(trace)).toBe(true);
624
+ expect(trace.length).toBe(0);
625
+ });
626
+ });
627
+ // =========================================================================
628
+ // 7. getCurrentPolicy / validatePolicy
629
+ // =========================================================================
630
+ describe('getCurrentPolicy / validatePolicy', () => {
631
+ let gateway;
632
+ afterEach(() => {
633
+ gateway.shutdown();
634
+ });
635
+ it('getCurrentPolicy returns the loaded policy pack', () => {
636
+ gateway = new gateway_1.Gateway(testConfig());
637
+ const policy = gateway.getCurrentPolicy();
638
+ expect(policy).toBeDefined();
639
+ expect(policy.name).toBeDefined();
640
+ expect(policy.rules).toBeDefined();
641
+ expect(Array.isArray(policy.rules)).toBe(true);
642
+ expect(policy.rules.length).toBeGreaterThan(0);
643
+ });
644
+ it('validatePolicy returns success for a valid pack', () => {
645
+ gateway = new gateway_1.Gateway(testConfig());
646
+ const pack = {
647
+ name: 'test-pack',
648
+ version: '1.0.0',
649
+ rules: [
650
+ {
651
+ name: 'allow-all',
652
+ effect: 'ALLOW',
653
+ priority: 1,
654
+ conditions: {},
655
+ },
656
+ ],
657
+ };
658
+ const result = gateway.validatePolicy(pack);
659
+ expect(result).toBeDefined();
660
+ expect(result.valid).toBe(true);
661
+ });
662
+ it('validatePolicy returns errors for an invalid pack', () => {
663
+ gateway = new gateway_1.Gateway(testConfig());
664
+ const pack = {
665
+ name: '',
666
+ version: '',
667
+ rules: [],
668
+ };
669
+ const result = gateway.validatePolicy(pack);
670
+ expect(result).toBeDefined();
671
+ // Depending on validation rules, this may or may not have errors
672
+ // At minimum, the method should return a result with a valid property
673
+ expect(typeof result.valid).toBe('boolean');
674
+ });
675
+ });
676
+ // =========================================================================
677
+ // 8. Tracing branches (when tracing is enabled)
678
+ // =========================================================================
679
+ describe('Tracing branches', () => {
680
+ let gateway;
681
+ afterEach(() => {
682
+ gateway.shutdown();
683
+ });
684
+ it('creates spans for execute when tracing is enabled', async () => {
685
+ const tracer = new tracing_1.GatewayTracer({
686
+ enabled: true,
687
+ service_name: 'palaryn-test',
688
+ use_simple_processor: true,
689
+ // Point to a non-existent endpoint so we don't need a real collector
690
+ otlp_endpoint: 'http://127.0.0.1:1/v1/traces',
691
+ });
692
+ tracer.setup();
693
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), undefined, tracer));
694
+ const tc = buildToolCall();
695
+ const result = await gateway.execute(tc);
696
+ // The request should still succeed even with tracing enabled
697
+ expect(result.status).toBe('ok');
698
+ expect(result.timing).toBeDefined();
699
+ expect(result.timing.duration_ms).toBeGreaterThanOrEqual(0);
700
+ await tracer.shutdown();
701
+ });
702
+ it('records span errors on execution failure with tracing', async () => {
703
+ const tracer = new tracing_1.GatewayTracer({
704
+ enabled: true,
705
+ service_name: 'palaryn-test-error',
706
+ use_simple_processor: true,
707
+ otlp_endpoint: 'http://127.0.0.1:1/v1/traces',
708
+ });
709
+ tracer.setup();
710
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), undefined, tracer));
711
+ // Register an executor that throws to trigger error span
712
+ class FailExecutor {
713
+ async execute() {
714
+ throw new Error('Traced failure');
715
+ }
716
+ }
717
+ const registry = gateway.getExecutorRegistry();
718
+ const httpExec = registry.resolve('http.request');
719
+ registry.clear();
720
+ registry.register('fail.*', new FailExecutor());
721
+ registry.register('http.*', httpExec);
722
+ registry.register('*', httpExec);
723
+ const tc = buildToolCall({
724
+ tool: { name: 'fail.test', capability: 'read' },
725
+ args: {},
726
+ });
727
+ const result = await gateway.execute(tc);
728
+ expect(result.status).toBe('error');
729
+ expect(result.error).toContain('Traced failure');
730
+ await tracer.shutdown();
731
+ });
732
+ it('runs without spans when tracing is disabled (no tracer)', async () => {
733
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
734
+ const tc = buildToolCall();
735
+ const result = await gateway.execute(tc);
736
+ expect(result.status).toBe('ok');
737
+ });
738
+ });
739
+ // =========================================================================
740
+ // 9. Edge cases
741
+ // =========================================================================
742
+ describe('Edge cases', () => {
743
+ let gateway;
744
+ afterEach(() => {
745
+ gateway.shutdown();
746
+ });
747
+ it('preExecute with no matching executor does not crash (fallback exists)', async () => {
748
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
749
+ // The catch-all '*' executor exists by default, so even unknown tool names resolve
750
+ const tc = buildToolCall({
751
+ tool: { name: 'unknown.tool.xyz', capability: 'read' },
752
+ args: { method: 'GET', url: `http://127.0.0.1:${testPort}/get` },
753
+ });
754
+ const pre = await gateway.preExecute(tc);
755
+ // Should pass pre-execute (policy allows reads)
756
+ expect(pre.allowed).toBe(true);
757
+ });
758
+ it('execute with gateway shutting down reflects shutdown state', async () => {
759
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
760
+ expect(gateway.isShuttingDown).toBe(false);
761
+ // Execute a request first
762
+ const tc1 = buildToolCall();
763
+ const result1 = await gateway.execute(tc1);
764
+ expect(result1.status).toBe('ok');
765
+ // Shutdown the gateway
766
+ await gateway.shutdown();
767
+ expect(gateway.isShuttingDown).toBe(true);
768
+ });
769
+ it('preExecute sets timestamp if not already set', async () => {
770
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
771
+ const tc = buildToolCall();
772
+ delete tc.timestamp;
773
+ expect(tc.timestamp).toBeUndefined();
774
+ await gateway.preExecute(tc);
775
+ // After preExecute, timestamp should be set
776
+ expect(tc.timestamp).toBeDefined();
777
+ expect(typeof tc.timestamp).toBe('string');
778
+ });
779
+ it('preExecute does not overwrite existing timestamp', async () => {
780
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig()));
781
+ const existingTs = '2025-01-01T00:00:00.000Z';
782
+ const tc = buildToolCall({ timestamp: existingTs });
783
+ await gateway.preExecute(tc);
784
+ expect(tc.timestamp).toBe(existingTs);
785
+ });
786
+ it('execution error releases budget reservation', async () => {
787
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
788
+ budget: {
789
+ task_budget_usd: 0.1,
790
+ max_steps_per_task: 100,
791
+ max_retries_per_call: 1,
792
+ max_wall_clock_ms: 300000,
793
+ },
794
+ })));
795
+ class FailExecutor {
796
+ async execute() {
797
+ throw new Error('Budget release test');
798
+ }
799
+ }
800
+ const registry = gateway.getExecutorRegistry();
801
+ const httpExec = registry.resolve('http.request');
802
+ registry.clear();
803
+ registry.register('fail.*', new FailExecutor());
804
+ registry.register('http.*', httpExec);
805
+ registry.register('*', httpExec);
806
+ const taskId = `task-budget-release-${Date.now()}`;
807
+ const tc = buildToolCall({
808
+ task_id: taskId,
809
+ tool: { name: 'fail.test', capability: 'read' },
810
+ args: {},
811
+ });
812
+ const result = await gateway.execute(tc);
813
+ expect(result.status).toBe('error');
814
+ // Budget should not be permanently consumed -- next call should work
815
+ const tc2 = buildToolCall({
816
+ task_id: taskId,
817
+ tool: { name: 'http.request', capability: 'read' },
818
+ args: { method: 'GET', url: `http://127.0.0.1:${testPort}/get` },
819
+ });
820
+ const result2 = await gateway.execute(tc2);
821
+ expect(result2.status).toBe('ok');
822
+ });
823
+ it('execute records error metrics on failure', async () => {
824
+ const metrics = new metrics_1.GatewayMetrics();
825
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), metrics));
826
+ class FailExecutor {
827
+ async execute() {
828
+ throw new Error('Metrics error test');
829
+ }
830
+ }
831
+ const registry = gateway.getExecutorRegistry();
832
+ const httpExec = registry.resolve('http.request');
833
+ registry.clear();
834
+ registry.register('fail.*', new FailExecutor());
835
+ registry.register('http.*', httpExec);
836
+ registry.register('*', httpExec);
837
+ const tc = buildToolCall({
838
+ tool: { name: 'fail.metrics', capability: 'read' },
839
+ args: {},
840
+ });
841
+ const result = await gateway.execute(tc);
842
+ expect(result.status).toBe('error');
843
+ const metricsText = await metrics.getMetrics();
844
+ expect(metricsText).toContain('palaryn_requests_total');
845
+ expect(metricsText).toContain('status="error"');
846
+ expect(metricsText).toContain('palaryn_executor_errors_total');
847
+ });
848
+ it('merged DLP report combines args and output detections', async () => {
849
+ // Create a server that returns a body containing a secret
850
+ const secretServer = http.createServer((req, res) => {
851
+ res.writeHead(200, { 'Content-Type': 'application/json' });
852
+ res.end(JSON.stringify({ secret_key: 'AKIAIOSFODNN7EXAMPLE' }));
853
+ });
854
+ await new Promise((resolve) => {
855
+ secretServer.listen(0, '127.0.0.1', () => resolve());
856
+ });
857
+ const secretPort = secretServer.address().port;
858
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
859
+ dlp: {
860
+ enabled: true,
861
+ scan_args: true,
862
+ scan_output: true,
863
+ secrets_detection: true,
864
+ pii_detection: true,
865
+ default_redaction_method: 'mask',
866
+ },
867
+ })));
868
+ const tc = buildToolCall({
869
+ args: {
870
+ method: 'GET',
871
+ url: `http://127.0.0.1:${secretPort}/data`,
872
+ ssn: '123-45-6789',
873
+ },
874
+ });
875
+ const result = await gateway.execute(tc);
876
+ expect(result.status).toBe('ok');
877
+ expect(result.dlp).toBeDefined();
878
+ // Should have detections from both args (SSN) and output (AWS key)
879
+ expect(result.dlp.detected.length).toBeGreaterThan(0);
880
+ await new Promise((resolve) => secretServer.close(() => resolve()));
881
+ });
882
+ it('reloadPolicy returns error for invalid pack path', () => {
883
+ // Create gateway with a valid path, then swap config to invalid
884
+ gateway = new gateway_1.Gateway(testConfig());
885
+ gateway.config.policy.pack_path = '/nonexistent/invalid-policy.yaml';
886
+ const result = gateway.reloadPolicy();
887
+ expect(result.success).toBe(false);
888
+ expect(result.error).toBeDefined();
889
+ expect(result.ruleCount).toBe(0);
890
+ });
891
+ it('getPolicyPackPath returns the configured path', () => {
892
+ gateway = new gateway_1.Gateway(testConfig());
893
+ const path = gateway.getPolicyPackPath();
894
+ expect(path).toBe('./policy-packs/dev_fast.yaml');
895
+ });
896
+ });
897
+ // =========================================================================
898
+ // 10. DLP output warning for sensitive operations (scan_output disabled)
899
+ // =========================================================================
900
+ describe('DLP output warning for sensitive operations', () => {
901
+ let gateway;
902
+ afterEach(() => {
903
+ gateway.shutdown();
904
+ });
905
+ it('warns (but does not block) when scan_output disabled for write operation', async () => {
906
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
907
+ dlp: {
908
+ enabled: true,
909
+ scan_args: false,
910
+ scan_output: false,
911
+ secrets_detection: true,
912
+ pii_detection: true,
913
+ default_redaction_method: 'mask',
914
+ },
915
+ // Need approval disabled and policy to allow writes
916
+ })));
917
+ const tc = buildToolCall({
918
+ tool: { name: 'http.request', capability: 'write' },
919
+ args: { method: 'POST', url: `http://127.0.0.1:${testPort}/data` },
920
+ });
921
+ const result = await gateway.execute(tc);
922
+ // Should still succeed -- warning is just logged, not blocking
923
+ expect(result.status).toBe('ok');
924
+ expect(result.dlp).toBeDefined();
925
+ });
926
+ });
927
+ // =========================================================================
928
+ // 11. Metrics integration tests
929
+ // =========================================================================
930
+ describe('Metrics integration', () => {
931
+ let gateway;
932
+ afterEach(() => {
933
+ gateway.shutdown();
934
+ });
935
+ it('records policy decision metrics', async () => {
936
+ const metrics = new metrics_1.GatewayMetrics();
937
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), metrics));
938
+ const tc = buildToolCall();
939
+ await gateway.execute(tc);
940
+ const metricsText = await metrics.getMetrics();
941
+ expect(metricsText).toContain('palaryn_policy_decisions_total');
942
+ });
943
+ it('records DLP detection metrics when secrets are found', async () => {
944
+ const metrics = new metrics_1.GatewayMetrics();
945
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
946
+ dlp: {
947
+ enabled: true,
948
+ scan_args: true,
949
+ scan_output: false,
950
+ secrets_detection: true,
951
+ pii_detection: false,
952
+ default_redaction_method: 'mask',
953
+ },
954
+ }), metrics));
955
+ const tc = buildToolCall({
956
+ args: {
957
+ method: 'GET',
958
+ url: `http://127.0.0.1:${testPort}/get`,
959
+ key: 'AKIAIOSFODNN7EXAMPLE',
960
+ },
961
+ });
962
+ await gateway.execute(tc);
963
+ const metricsText = await metrics.getMetrics();
964
+ expect(metricsText).toContain('palaryn_dlp_detections_total');
965
+ });
966
+ it('records budget block metrics when budget is exceeded', async () => {
967
+ const metrics = new metrics_1.GatewayMetrics();
968
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
969
+ budget: {
970
+ task_budget_usd: 0.0001,
971
+ max_steps_per_task: 1000,
972
+ max_retries_per_call: 3,
973
+ max_wall_clock_ms: 300000,
974
+ },
975
+ }), metrics));
976
+ const tc = buildToolCall();
977
+ await gateway.execute(tc);
978
+ const metricsText = await metrics.getMetrics();
979
+ expect(metricsText).toContain('palaryn_budget_blocked_total');
980
+ });
981
+ it('records cost metrics on successful execution', async () => {
982
+ const metrics = new metrics_1.GatewayMetrics();
983
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig(), metrics));
984
+ const tc = buildToolCall();
985
+ await gateway.execute(tc);
986
+ const metricsText = await metrics.getMetrics();
987
+ expect(metricsText).toContain('palaryn_cost_usd_total');
988
+ });
989
+ });
990
+ // =========================================================================
991
+ // 12. classifyBudgetReason helper (tested via budget block messages)
992
+ // =========================================================================
993
+ describe('Budget reason classification', () => {
994
+ let gateway;
995
+ afterEach(() => {
996
+ gateway.shutdown();
997
+ });
998
+ it('classifies task budget reason', async () => {
999
+ const metrics = new metrics_1.GatewayMetrics();
1000
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
1001
+ budget: {
1002
+ task_budget_usd: 0.0001,
1003
+ max_steps_per_task: 1000,
1004
+ max_retries_per_call: 3,
1005
+ max_wall_clock_ms: 300000,
1006
+ },
1007
+ }), metrics));
1008
+ const tc = buildToolCall();
1009
+ const result = await gateway.execute(tc);
1010
+ expect(result.status).toBe('blocked');
1011
+ const metricsText = await metrics.getMetrics();
1012
+ // Budget block should be classified as 'task'
1013
+ expect(metricsText).toContain('palaryn_budget_blocked_total');
1014
+ expect(metricsText).toContain('reason_type="task"');
1015
+ });
1016
+ it('classifies step limit reason', async () => {
1017
+ const metrics = new metrics_1.GatewayMetrics();
1018
+ gateway = disableSSRF(new gateway_1.Gateway(testConfig({
1019
+ budget: {
1020
+ task_budget_usd: 100,
1021
+ max_steps_per_task: 1,
1022
+ max_retries_per_call: 3,
1023
+ max_wall_clock_ms: 300000,
1024
+ },
1025
+ }), metrics));
1026
+ const taskId = `task-step-classify-${Date.now()}`;
1027
+ // First call uses the step
1028
+ const tc1 = buildToolCall({ task_id: taskId });
1029
+ await gateway.execute(tc1);
1030
+ // Second call should be blocked with step_limit
1031
+ const tc2 = buildToolCall({ task_id: taskId });
1032
+ const result = await gateway.execute(tc2);
1033
+ expect(result.status).toBe('blocked');
1034
+ const metricsText = await metrics.getMetrics();
1035
+ expect(metricsText).toContain('reason_type="step_limit"');
1036
+ });
1037
+ });
1038
+ });
1039
+ //# sourceMappingURL=gateway-branches.test.js.map