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,88 @@
1
+ import * as fs from 'fs';
2
+ import { GatewayConfig } from '../types/config';
3
+
4
+ export interface ConfigIssue {
5
+ level: 'fatal' | 'warning';
6
+ message: string;
7
+ }
8
+
9
+ export interface ConfigValidationResult {
10
+ valid: boolean;
11
+ issues: ConfigIssue[];
12
+ }
13
+
14
+ /**
15
+ * Validate gateway configuration before startup.
16
+ * Returns fatal errors (should prevent startup) and warnings (log and continue).
17
+ */
18
+ export function validateConfig(config: GatewayConfig): ConfigValidationResult {
19
+ const issues: ConfigIssue[] = [];
20
+ const isProduction = process.env.NODE_ENV === 'production';
21
+
22
+ // Port validation
23
+ if (!Number.isInteger(config.port) || config.port < 1 || config.port > 65535) {
24
+ issues.push({ level: 'fatal', message: `Invalid port: ${config.port}. Must be between 1 and 65535.` });
25
+ }
26
+
27
+ // Policy file must be readable
28
+ try {
29
+ fs.accessSync(config.policy.pack_path, fs.constants.R_OK);
30
+ } catch {
31
+ issues.push({ level: 'fatal', message: `Policy file not readable: ${config.policy.pack_path}` });
32
+ }
33
+
34
+ // Weak secrets in production
35
+ if (isProduction) {
36
+ if (!process.env.JWT_SECRET) {
37
+ issues.push({ level: 'fatal', message: 'JWT_SECRET must be set in production.' });
38
+ }
39
+ if (!process.env.APPROVAL_SECRET) {
40
+ issues.push({ level: 'fatal', message: 'APPROVAL_SECRET must be set in production.' });
41
+ }
42
+ }
43
+
44
+ // Warnings: no persistent storage
45
+ if (!process.env.REDIS_URL && !process.env.DATABASE_URL) {
46
+ issues.push({ level: 'warning', message: 'No persistent storage configured (REDIS_URL or DATABASE_URL). Data will be lost on restart.' });
47
+ }
48
+
49
+ // Auth enabled but no keys
50
+ if (config.auth.enabled && Object.keys(config.auth.api_keys).length === 0 && !config.auth.jwt?.enabled) {
51
+ issues.push({ level: 'warning', message: 'Auth is enabled but no API keys or JWT configured. All authenticated requests will fail.' });
52
+ }
53
+
54
+ // Audit log directory
55
+ if (config.audit.enabled && config.audit.log_dir) {
56
+ try {
57
+ fs.accessSync(config.audit.log_dir, fs.constants.W_OK);
58
+ } catch {
59
+ issues.push({ level: 'warning', message: `Audit log directory not writable: ${config.audit.log_dir}` });
60
+ }
61
+ }
62
+
63
+ // Auth must be enabled — MCP HTTP transport requires authentication
64
+ if (!config.auth.enabled) {
65
+ issues.push({ level: 'fatal', message: 'Auth is disabled. MCP HTTP transport requires authentication. Set AUTH_ENABLED=true.' });
66
+ }
67
+
68
+ // RBAC default_role must not be admin or operator
69
+ if (config.auth.rbac?.default_role) {
70
+ const dr = config.auth.rbac.default_role;
71
+ if (dr === 'admin' || dr === 'operator') {
72
+ issues.push({ level: 'fatal', message: `RBAC default_role '${dr}' is a privileged role. Use 'agent', 'readonly', or a custom role.` });
73
+ }
74
+ }
75
+
76
+ // Proxy port validation
77
+ if (config.proxy?.enabled) {
78
+ if (!Number.isInteger(config.proxy.port) || config.proxy.port < 1 || config.proxy.port > 65535) {
79
+ issues.push({ level: 'fatal', message: `Invalid proxy port: ${config.proxy.port}. Must be between 1 and 65535.` });
80
+ }
81
+ if (config.proxy.port === config.port) {
82
+ issues.push({ level: 'fatal', message: `Proxy port (${config.proxy.port}) conflicts with server port (${config.port}).` });
83
+ }
84
+ }
85
+
86
+ const hasFatal = issues.some(i => i.level === 'fatal');
87
+ return { valid: !hasFatal, issues };
88
+ }
@@ -0,0 +1,213 @@
1
+ import { DLPReport, DLPRedaction, DLPSeverity } from '../types/tool-result';
2
+ import { DLPConfig } from '../types/config';
3
+ import { DLPScanner } from './scanner';
4
+ import { DLPBackend, DLPDetection } from './interfaces';
5
+
6
+ /** Maximum recursion depth for extracting string values from nested structures. */
7
+ const MAX_EXTRACT_DEPTH = 32;
8
+
9
+ /**
10
+ * CompositeDLPScanner wraps the existing DLPScanner and augments it with
11
+ * additional pluggable DLP backends (e.g. TruffleHog, regex backend).
12
+ *
13
+ * The scan flow:
14
+ * 1. Run the standard DLPScanner.scan() for full existing behavior
15
+ * 2. Extract all string values from the data object recursively
16
+ * 3. For each additional backend, call scanString on each extracted string
17
+ * 4. Merge backend detections into the DLPReport (deduplicate by pattern_name)
18
+ * 5. Recalculate severity based on all combined findings
19
+ *
20
+ * This design preserves full backward compatibility -- the existing DLPScanner
21
+ * continues to handle redaction, policy integration, and all current features.
22
+ * The backends provide supplemental detections that are merged into the report.
23
+ */
24
+ export class CompositeDLPScanner {
25
+ private readonly dlpScanner: DLPScanner;
26
+ private readonly backends: DLPBackend[];
27
+ private readonly config: DLPConfig;
28
+
29
+ constructor(config: DLPConfig, backends: DLPBackend[]) {
30
+ this.config = config;
31
+ this.dlpScanner = new DLPScanner(config);
32
+ this.backends = backends;
33
+ }
34
+
35
+ /**
36
+ * Get the underlying DLPScanner instance (for redaction operations, etc.).
37
+ */
38
+ get scanner(): DLPScanner {
39
+ return this.dlpScanner;
40
+ }
41
+
42
+ /**
43
+ * Scan data for secrets and PII using the built-in scanner and all backends.
44
+ *
45
+ * @param data - The data to scan (object, array, string, or primitive).
46
+ * @param basePath - Dot-notation prefix for paths within the data structure.
47
+ * @returns A merged DLPReport with detections from all sources.
48
+ */
49
+ scan(data: unknown, basePath: string = ''): DLPReport {
50
+ // Step 1: Run the standard DLPScanner for existing behavior
51
+ const baseReport = this.dlpScanner.scan(data, basePath);
52
+
53
+ // If DLP is disabled or there are no backends, return the base report as-is
54
+ if (!this.config.enabled || this.backends.length === 0) {
55
+ return baseReport;
56
+ }
57
+
58
+ // Step 2: Extract all string values with their paths from the data,
59
+ // including fields that the default recursive walker would miss
60
+ // (context.purpose, context.labels, actor.display).
61
+ const stringEntries = this.extractStrings(data, basePath, 0);
62
+
63
+ // Step 2.5: Explicitly extract commonly-missed ToolCall text fields.
64
+ // The generic walker handles args.* but context and actor are top-level
65
+ // fields that may not be traversed if 'data' is the full ToolCall object
66
+ // or if basePath filtering skips them.
67
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
68
+ const obj = data as Record<string, unknown>;
69
+ // context.purpose
70
+ if (obj.context && typeof obj.context === 'object') {
71
+ const ctx = obj.context as Record<string, unknown>;
72
+ if (typeof ctx.purpose === 'string') {
73
+ const p = basePath ? `${basePath}.context.purpose` : 'context.purpose';
74
+ if (!stringEntries.some(e => e.path === p)) {
75
+ stringEntries.push({ value: ctx.purpose, path: p });
76
+ }
77
+ }
78
+ // context.labels[]
79
+ if (Array.isArray(ctx.labels)) {
80
+ for (let i = 0; i < ctx.labels.length; i++) {
81
+ if (typeof ctx.labels[i] === 'string') {
82
+ const p = basePath ? `${basePath}.context.labels[${i}]` : `context.labels[${i}]`;
83
+ if (!stringEntries.some(e => e.path === p)) {
84
+ stringEntries.push({ value: ctx.labels[i] as string, path: p });
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ // actor.display
91
+ if (obj.actor && typeof obj.actor === 'object') {
92
+ const actor = obj.actor as Record<string, unknown>;
93
+ if (typeof actor.display === 'string') {
94
+ const p = basePath ? `${basePath}.actor.display` : 'actor.display';
95
+ if (!stringEntries.some(e => e.path === p)) {
96
+ stringEntries.push({ value: actor.display, path: p });
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ // Step 3: Run each backend on each string value
103
+ const backendDetections: Array<{ detection: DLPDetection; path: string }> = [];
104
+
105
+ for (const backend of this.backends) {
106
+ for (const entry of stringEntries) {
107
+ try {
108
+ const detections = backend.scanString(entry.value);
109
+ for (const detection of detections) {
110
+ backendDetections.push({ detection, path: entry.path });
111
+ }
112
+ } catch (err: unknown) {
113
+ // Graceful degradation: log and continue with other backends/strings
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ console.warn(`[CompositeDLPScanner] backend '${backend.name}' failed on path '${entry.path}': ${message}`);
116
+ }
117
+ }
118
+ }
119
+
120
+ // Step 4: Merge backend detections into the base report (deduplicate by pattern_name)
121
+ const existingPatterns = new Set(baseReport.detected);
122
+ const additionalDetected: string[] = [];
123
+ const additionalRedactions: DLPRedaction[] = [];
124
+
125
+ for (const { detection, path } of backendDetections) {
126
+ if (!existingPatterns.has(detection.pattern_name)) {
127
+ existingPatterns.add(detection.pattern_name);
128
+ additionalDetected.push(detection.pattern_name);
129
+ additionalRedactions.push({
130
+ path,
131
+ method: this.config.default_redaction_method,
132
+ original_type: detection.pattern_name,
133
+ });
134
+ }
135
+ }
136
+
137
+ const mergedDetected = [...baseReport.detected, ...additionalDetected];
138
+ const mergedRedactions = [...baseReport.redactions, ...additionalRedactions];
139
+
140
+ // Step 5: Recalculate severity based on all findings
141
+ const allSeverities: DLPSeverity[] = [baseReport.severity];
142
+ for (const { detection } of backendDetections) {
143
+ allSeverities.push(detection.severity);
144
+ }
145
+ const severity = this.highestSeverity(allSeverities);
146
+
147
+ return {
148
+ detected: mergedDetected,
149
+ redactions: mergedRedactions,
150
+ severity,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Recursively extract all string values and their dot-notation paths
156
+ * from a nested data structure.
157
+ */
158
+ private extractStrings(
159
+ data: unknown,
160
+ path: string,
161
+ depth: number,
162
+ ): Array<{ value: string; path: string }> {
163
+ if (depth > MAX_EXTRACT_DEPTH) {
164
+ return [];
165
+ }
166
+
167
+ if (data === null || data === undefined) {
168
+ return [];
169
+ }
170
+
171
+ if (typeof data === 'string') {
172
+ return [{ value: data, path }];
173
+ }
174
+
175
+ if (Array.isArray(data)) {
176
+ const results: Array<{ value: string; path: string }> = [];
177
+ for (let i = 0; i < data.length; i++) {
178
+ const childPath = path ? `${path}[${i}]` : `[${i}]`;
179
+ results.push(...this.extractStrings(data[i], childPath, depth + 1));
180
+ }
181
+ return results;
182
+ }
183
+
184
+ if (typeof data === 'object') {
185
+ const results: Array<{ value: string; path: string }> = [];
186
+ for (const [key, value] of Object.entries(data as Record<string, unknown>)) {
187
+ const childPath = path ? `${path}.${key}` : key;
188
+ results.push(...this.extractStrings(value, childPath, depth + 1));
189
+ }
190
+ return results;
191
+ }
192
+
193
+ // Non-string primitives are ignored
194
+ return [];
195
+ }
196
+
197
+ /**
198
+ * Return the highest severity from an array of severities.
199
+ */
200
+ private highestSeverity(severities: DLPSeverity[]): DLPSeverity {
201
+ const rank: Record<DLPSeverity, number> = { low: 0, medium: 1, high: 2 };
202
+ let highest: DLPSeverity = 'low';
203
+
204
+ for (const sev of severities) {
205
+ if (rank[sev] > rank[highest]) {
206
+ highest = sev;
207
+ }
208
+ if (highest === 'high') break; // Short-circuit
209
+ }
210
+
211
+ return highest;
212
+ }
213
+ }
@@ -0,0 +1,9 @@
1
+ export { DLPScanner } from './scanner';
2
+ export { DLPBackend, DLPDetection } from './interfaces';
3
+ export { DLPPattern, SECRET_PATTERNS, PII_PATTERNS } from './patterns';
4
+ export { RegexDLPBackend, RegexBackendConfig } from './regex-backend';
5
+ export { TruffleHogBackend, TruffleHogConfig } from './trufflehog-backend';
6
+ export { CompositeDLPScanner } from './composite-scanner';
7
+ export { PROMPT_INJECTION_PATTERNS, OUTPUT_INJECTION_PATTERNS } from './prompt-injection-patterns';
8
+ export { PromptInjectionBackend, PromptInjectionConfig } from './prompt-injection-backend';
9
+ export { normalizeText, normalizeLeetspeak, ZERO_WIDTH_REGEX, HOMOGLYPH_MAP, LEETSPEAK_MAP } from './text-normalizer';
@@ -0,0 +1,34 @@
1
+ import { DLPSeverity } from '../types/tool-result';
2
+
3
+ /**
4
+ * A detection returned by a DLP backend scanner.
5
+ *
6
+ * Each detection identifies a single sensitive value found within a string,
7
+ * including its location so that upstream consumers can apply redactions.
8
+ */
9
+ export interface DLPDetection {
10
+ /** Name of the pattern or detector that matched (e.g. 'aws_access_key', 'GitHubToken'). */
11
+ pattern_name: string;
12
+ /** Severity of the detection. */
13
+ severity: DLPSeverity;
14
+ /** The matched text. */
15
+ match: string;
16
+ /** Start index within the scanned string. */
17
+ start: number;
18
+ /** End index (exclusive) within the scanned string. */
19
+ end: number;
20
+ }
21
+
22
+ /**
23
+ * Pluggable backend interface for DLP secret/PII scanning.
24
+ *
25
+ * Implementations scan raw string values and return structured detections.
26
+ * The gateway can compose multiple backends (regex, trufflehog, etc.) via
27
+ * the CompositeDLPScanner.
28
+ */
29
+ export interface DLPBackend {
30
+ /** Scan a string for secrets/PII. Returns detected pattern names and their locations. */
31
+ scanString(value: string): DLPDetection[];
32
+ /** Name of this backend for logging. */
33
+ readonly name: string;
34
+ }
@@ -0,0 +1,30 @@
1
+ import { DLPSeverity } from '../types/tool-result';
2
+
3
+ export interface DLPPattern {
4
+ name: string;
5
+ pattern: RegExp;
6
+ severity: DLPSeverity;
7
+ }
8
+
9
+ // Detection patterns for secrets
10
+ export const SECRET_PATTERNS: DLPPattern[] = [
11
+ { name: 'aws_access_key', pattern: /AKIA[0-9A-Z]{16}/g, severity: 'high' },
12
+ { name: 'aws_secret_key', pattern: /(?:aws_secret_access_key|secret_key)\s*[=:]\s*[A-Za-z0-9/+=]{40}/gi, severity: 'high' },
13
+ { name: 'github_token', pattern: /gh[pousr]_[A-Za-z0-9_]{36,255}/g, severity: 'high' },
14
+ { name: 'generic_api_key', pattern: /(?:api[_-]?key|apikey)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{20,}['"]?/gi, severity: 'medium' },
15
+ { name: 'bearer_token', pattern: /Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, severity: 'high' },
16
+ { name: 'jwt_token', pattern: /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_\-]+/g, severity: 'high' },
17
+ { name: 'private_key', pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/g, severity: 'high' },
18
+ { name: 'password_field', pattern: /(?:password|passwd|pwd)\s*[=:]\s*['"]?[^\s'"]{8,}['"]?/gi, severity: 'high' },
19
+ { name: 'slack_token', pattern: /xox[baprs]-[0-9a-zA-Z-]{10,}/g, severity: 'high' },
20
+ { name: 'generic_secret', pattern: /(?:secret|token|credential)\s*[=:]\s*['"]?[A-Za-z0-9_\-]{16,}['"]?/gi, severity: 'medium' },
21
+ ];
22
+
23
+ // PII patterns
24
+ export const PII_PATTERNS: DLPPattern[] = [
25
+ { name: 'email', pattern: /[a-zA-Z0-9._%+\-]+@(?!(?:\d{1,3}\.){3}\d{1,3}\b)[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*\.[a-zA-Z]{2,}/g, severity: 'medium' },
26
+ { name: 'phone_us', pattern: /(?:\+1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g, severity: 'medium' },
27
+ { name: 'ssn', pattern: /\b\d{3}-\d{2}-\d{4}\b/g, severity: 'high' },
28
+ { name: 'credit_card', pattern: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g, severity: 'high' },
29
+ { name: 'ip_address', pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, severity: 'low' },
30
+ ];
@@ -0,0 +1,181 @@
1
+ import { DLPBackend, DLPDetection } from './interfaces';
2
+ import { DLPPattern } from './patterns';
3
+ import { DLPSeverity } from '../types/tool-result';
4
+ import { PROMPT_INJECTION_PATTERNS, OUTPUT_INJECTION_PATTERNS } from './prompt-injection-patterns';
5
+ import { normalizeText, normalizeLeetspeak } from './text-normalizer';
6
+
7
+ export interface PromptInjectionConfig {
8
+ /** Enable prompt injection detection. Default true. */
9
+ enabled?: boolean;
10
+ /** Additional custom patterns to scan alongside the built-in set. */
11
+ custom_patterns?: DLPPattern[];
12
+ /** Enable output-side pattern scanning. Default false. */
13
+ scan_output?: boolean;
14
+ }
15
+
16
+ /**
17
+ * DLP backend that detects common prompt injection patterns in text.
18
+ *
19
+ * Scans for 13 categories of prompt injection:
20
+ * - Direct instruction overrides
21
+ * - Role manipulation
22
+ * - Jailbreak keywords
23
+ * - System prompt extraction attempts
24
+ * - Delimiter injection
25
+ * - Context manipulation
26
+ * - Authority impersonation
27
+ * - Indirect injection
28
+ * - Encoding indicators
29
+ * - Synonym variants
30
+ * - Prompt leakage requests
31
+ * - Obfuscation markers
32
+ * - Multi-turn manipulation
33
+ *
34
+ * All text is normalized before pattern matching to resist evasion via
35
+ * zero-width chars, HTML entities, homoglyphs, URL encoding, etc.
36
+ *
37
+ * Combination severity scoring: 3+ medium = effective high, 2+ high = effective critical.
38
+ */
39
+ export class PromptInjectionBackend implements DLPBackend {
40
+ readonly name = 'prompt_injection';
41
+
42
+ private readonly enabled: boolean;
43
+ private readonly patterns: DLPPattern[];
44
+ private readonly outputPatterns: DLPPattern[];
45
+ private readonly scanOutput: boolean;
46
+
47
+ constructor(config?: PromptInjectionConfig) {
48
+ this.enabled = config?.enabled ?? true;
49
+ this.scanOutput = config?.scan_output ?? false;
50
+ this.patterns = [
51
+ ...PROMPT_INJECTION_PATTERNS,
52
+ ...(config?.custom_patterns ?? []),
53
+ ];
54
+ this.outputPatterns = OUTPUT_INJECTION_PATTERNS;
55
+ }
56
+
57
+ /**
58
+ * Scan a string for prompt injection patterns.
59
+ *
60
+ * Text is normalized before matching to defeat evasion techniques.
61
+ * Patterns are matched against both the standard-normalized text and
62
+ * the leetspeak-normalized variant. Positions in detections refer to
63
+ * the original (un-normalized) string for accurate redaction.
64
+ */
65
+ scanString(value: string): DLPDetection[] {
66
+ if (!this.enabled) {
67
+ return [];
68
+ }
69
+
70
+ const detections: DLPDetection[] = [];
71
+
72
+ // Normalize text for bypass resistance
73
+ const normalized = normalizeText(value);
74
+ const leetNormalized = normalizeLeetspeak(normalized);
75
+
76
+ // Choose pattern set: include output patterns when configured
77
+ const patternsToScan = this.scanOutput
78
+ ? [...this.patterns, ...this.outputPatterns]
79
+ : this.patterns;
80
+
81
+ // Scan standard-normalized text
82
+ this.matchPatterns(patternsToScan, normalized, detections);
83
+
84
+ // Scan leetspeak-normalized text (only if it differs from standard)
85
+ if (leetNormalized !== normalized) {
86
+ const existingNames = new Set(detections.map(d => d.pattern_name + ':' + d.match));
87
+ const leetDetections: DLPDetection[] = [];
88
+ this.matchPatterns(patternsToScan, leetNormalized, leetDetections);
89
+
90
+ // Deduplicate — only add leet detections not already found
91
+ for (const ld of leetDetections) {
92
+ const key = ld.pattern_name + ':' + ld.match;
93
+ if (!existingNames.has(key)) {
94
+ existingNames.add(key);
95
+ detections.push(ld);
96
+ }
97
+ }
98
+ }
99
+
100
+ // Apply combination severity scoring
101
+ this.applyCombinationScoring(detections);
102
+
103
+ return detections;
104
+ }
105
+
106
+ /**
107
+ * Scan output text specifically for output-side injection patterns.
108
+ * Called during postExecute DLP scanning.
109
+ */
110
+ scanOutputText(value: string): DLPDetection[] {
111
+ if (!this.enabled) {
112
+ return [];
113
+ }
114
+
115
+ const normalized = normalizeText(value);
116
+ const detections: DLPDetection[] = [];
117
+ this.matchPatterns(this.outputPatterns, normalized, detections);
118
+ return detections;
119
+ }
120
+
121
+ /**
122
+ * Run all patterns against a text value and append matches to the detections array.
123
+ */
124
+ private matchPatterns(
125
+ patterns: DLPPattern[],
126
+ text: string,
127
+ detections: DLPDetection[],
128
+ ): void {
129
+ for (const pat of patterns) {
130
+ pat.pattern.lastIndex = 0;
131
+ let m: RegExpExecArray | null;
132
+ while ((m = pat.pattern.exec(text)) !== null) {
133
+ detections.push({
134
+ pattern_name: pat.name,
135
+ severity: pat.severity,
136
+ match: m[0],
137
+ start: m.index,
138
+ end: m.index + m[0].length,
139
+ });
140
+ // Prevent infinite loops on zero-length matches
141
+ if (m[0].length === 0) {
142
+ pat.pattern.lastIndex++;
143
+ }
144
+ }
145
+ pat.pattern.lastIndex = 0;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Apply combination severity escalation:
151
+ * - 3+ medium detections -> add effective_severity 'high' metadata
152
+ * - 2+ high detections -> add effective_severity 'critical' metadata
153
+ *
154
+ * Mutates detections in place by upgrading severity where applicable.
155
+ */
156
+ private applyCombinationScoring(detections: DLPDetection[]): void {
157
+ if (detections.length < 2) return;
158
+
159
+ const mediumCount = detections.filter(d => d.severity === 'medium').length;
160
+ const highCount = detections.filter(d => d.severity === 'high').length;
161
+
162
+ // 3+ medium -> escalate all mediums to high
163
+ if (mediumCount >= 3) {
164
+ for (const d of detections) {
165
+ if (d.severity === 'medium') {
166
+ (d as any).effective_severity = 'high';
167
+ d.severity = 'high' as DLPSeverity;
168
+ }
169
+ }
170
+ }
171
+
172
+ // 2+ high -> mark effective severity as critical on all highs
173
+ if (highCount >= 2) {
174
+ for (const d of detections) {
175
+ if (d.severity === 'high') {
176
+ (d as any).effective_severity = 'critical';
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }