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,119 @@
1
+ import { TokenPricing } from '../types/budget';
2
+
3
+ /**
4
+ * Built-in model pricing database.
5
+ * Prices are per TOKEN (not per million). Updated March 2026.
6
+ *
7
+ * Fallback chain: workspace config → built-in pricing → undefined (falls to DEFAULT_COST_TABLE).
8
+ */
9
+ export const MODEL_PRICING: Record<string, TokenPricing> = {
10
+ // ── Anthropic ──────────────────────────────────────────────
11
+ 'claude-opus-4-6': { input_per_token: 15 / 1e6, output_per_token: 75 / 1e6 },
12
+ 'claude-sonnet-4-6': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
13
+ 'claude-haiku-4-5': { input_per_token: 1 / 1e6, output_per_token: 5 / 1e6 },
14
+ 'claude-sonnet-4-5': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
15
+ 'claude-3-5-sonnet-20241022': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
16
+ 'claude-3-5-haiku-20241022': { input_per_token: 1 / 1e6, output_per_token: 5 / 1e6 },
17
+ 'claude-3-opus-20240229': { input_per_token: 15 / 1e6, output_per_token: 75 / 1e6 },
18
+ 'claude-3-sonnet-20240229': { input_per_token: 3 / 1e6, output_per_token: 15 / 1e6 },
19
+ 'claude-3-haiku-20240307': { input_per_token: 0.25 / 1e6, output_per_token: 1.25 / 1e6 },
20
+
21
+ // ── OpenAI ─────────────────────────────────────────────────
22
+ 'gpt-4.1': { input_per_token: 2 / 1e6, output_per_token: 8 / 1e6 },
23
+ 'gpt-4.1-mini': { input_per_token: 0.4 / 1e6, output_per_token: 1.6 / 1e6 },
24
+ 'gpt-4.1-nano': { input_per_token: 0.1 / 1e6, output_per_token: 0.4 / 1e6 },
25
+ 'gpt-4o': { input_per_token: 2.5 / 1e6, output_per_token: 10 / 1e6 },
26
+ 'gpt-4o-mini': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
27
+ 'gpt-4-turbo': { input_per_token: 10 / 1e6, output_per_token: 30 / 1e6 },
28
+ 'o3': { input_per_token: 2 / 1e6, output_per_token: 8 / 1e6 },
29
+ 'o3-mini': { input_per_token: 1.1 / 1e6, output_per_token: 4.4 / 1e6 },
30
+ 'o1': { input_per_token: 15 / 1e6, output_per_token: 60 / 1e6 },
31
+ 'o1-mini': { input_per_token: 3 / 1e6, output_per_token: 12 / 1e6 },
32
+
33
+ // ── Google ─────────────────────────────────────────────────
34
+ 'gemini-2.5-pro': { input_per_token: 1.25 / 1e6, output_per_token: 10 / 1e6 },
35
+ 'gemini-2.5-flash': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
36
+ 'gemini-2.0-flash': { input_per_token: 0.1 / 1e6, output_per_token: 0.4 / 1e6 },
37
+ 'gemini-1.5-pro': { input_per_token: 1.25 / 1e6, output_per_token: 5 / 1e6 },
38
+ 'gemini-1.5-flash': { input_per_token: 0.075 / 1e6, output_per_token: 0.3 / 1e6 },
39
+
40
+ // ── Mistral ────────────────────────────────────────────────
41
+ 'mistral-large-latest': { input_per_token: 2 / 1e6, output_per_token: 6 / 1e6 },
42
+ 'mistral-small-latest': { input_per_token: 0.2 / 1e6, output_per_token: 0.6 / 1e6 },
43
+ 'codestral-latest': { input_per_token: 0.3 / 1e6, output_per_token: 0.9 / 1e6 },
44
+
45
+ // ── Meta (via API providers) ───────────────────────────────
46
+ 'llama-3.3-70b': { input_per_token: 0.8 / 1e6, output_per_token: 0.8 / 1e6 },
47
+ 'llama-3.1-405b': { input_per_token: 3 / 1e6, output_per_token: 3 / 1e6 },
48
+ 'llama-3.1-70b': { input_per_token: 0.8 / 1e6, output_per_token: 0.8 / 1e6 },
49
+ 'llama-3.1-8b': { input_per_token: 0.1 / 1e6, output_per_token: 0.1 / 1e6 },
50
+
51
+ // ── Cohere ─────────────────────────────────────────────────
52
+ 'command-r-plus': { input_per_token: 2.5 / 1e6, output_per_token: 10 / 1e6 },
53
+ 'command-r': { input_per_token: 0.15 / 1e6, output_per_token: 0.6 / 1e6 },
54
+ };
55
+
56
+ /**
57
+ * Alias map for model name normalization.
58
+ * Maps common variations/prefixes to canonical model names in MODEL_PRICING.
59
+ */
60
+ const MODEL_ALIASES: Record<string, string> = {
61
+ // OpenAI versioned snapshots
62
+ 'gpt-4o-2024-11-20': 'gpt-4o',
63
+ 'gpt-4o-2024-08-06': 'gpt-4o',
64
+ 'gpt-4o-mini-2024-07-18': 'gpt-4o-mini',
65
+ 'gpt-4-turbo-2024-04-09': 'gpt-4-turbo',
66
+ // Anthropic shortcuts
67
+ 'claude-3.5-sonnet': 'claude-sonnet-4-5',
68
+ 'claude-3.5-haiku': 'claude-haiku-4-5',
69
+ };
70
+
71
+ /**
72
+ * Resolve token pricing for a model name.
73
+ *
74
+ * Lookup order:
75
+ * 1. Exact match in `overrides` (workspace-level config)
76
+ * 2. Exact match in built-in MODEL_PRICING
77
+ * 3. Alias lookup
78
+ * 4. Prefix match (e.g. "gpt-4o-2024-05-13" → "gpt-4o")
79
+ * 5. undefined — caller falls back to DEFAULT_COST_TABLE
80
+ */
81
+ export function resolveModelPricing(
82
+ modelName: string,
83
+ overrides?: Record<string, TokenPricing>,
84
+ ): TokenPricing | undefined {
85
+ // 1. Workspace override (exact)
86
+ if (overrides?.[modelName]) return overrides[modelName];
87
+
88
+ // 2. Built-in exact match
89
+ if (MODEL_PRICING[modelName]) return MODEL_PRICING[modelName];
90
+
91
+ // 3. Alias lookup
92
+ const aliased = MODEL_ALIASES[modelName];
93
+ if (aliased) {
94
+ return overrides?.[aliased] ?? MODEL_PRICING[aliased];
95
+ }
96
+
97
+ // 4. Prefix match — find the longest matching key
98
+ // e.g. "claude-3-5-sonnet-20241022-v2" matches "claude-3-5-sonnet-20241022"
99
+ let bestMatch: string | undefined;
100
+ let bestLen = 0;
101
+
102
+ const allKeys = new Set([
103
+ ...Object.keys(MODEL_PRICING),
104
+ ...(overrides ? Object.keys(overrides) : []),
105
+ ]);
106
+
107
+ for (const key of allKeys) {
108
+ if (modelName.startsWith(key) && key.length > bestLen) {
109
+ bestMatch = key;
110
+ bestLen = key.length;
111
+ }
112
+ }
113
+
114
+ if (bestMatch) {
115
+ return overrides?.[bestMatch] ?? MODEL_PRICING[bestMatch];
116
+ }
117
+
118
+ return undefined;
119
+ }
@@ -0,0 +1,214 @@
1
+ import { UsageData } from '../types/tool-result';
2
+ import { TokenPricing } from '../types/budget';
3
+ import { resolveModelPricing } from './model-pricing';
4
+
5
+ export class UsageExtractor {
6
+ private tokenPricing: Record<string, TokenPricing>;
7
+
8
+ constructor(tokenPricing?: Record<string, TokenPricing>) {
9
+ this.tokenPricing = tokenPricing || {};
10
+ }
11
+
12
+ /**
13
+ * Extract usage data from HTTP response headers.
14
+ * Supports Anthropic, OpenAI-style, and generic header conventions.
15
+ */
16
+ extractFromHeaders(headers?: Record<string, string>): UsageData | undefined {
17
+ if (!headers) return undefined;
18
+
19
+ // Normalize header keys to lowercase for case-insensitive matching
20
+ const normalized: Record<string, string> = {};
21
+ for (const [key, value] of Object.entries(headers)) {
22
+ normalized[key.toLowerCase()] = value;
23
+ }
24
+
25
+ const inputTokens = this.parseIntHeader(normalized['x-usage-input-tokens']);
26
+ const outputTokens = this.parseIntHeader(normalized['x-usage-output-tokens']);
27
+ const totalTokens = this.parseIntHeader(normalized['x-usage-total-tokens']);
28
+ const providerCost = this.parseFloatHeader(normalized['x-usage-cost-usd']);
29
+
30
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined && providerCost === undefined) {
31
+ return undefined;
32
+ }
33
+
34
+ const usage: UsageData = {
35
+ source: 'headers',
36
+ };
37
+
38
+ if (inputTokens !== undefined) usage.input_tokens = inputTokens;
39
+ if (outputTokens !== undefined) usage.output_tokens = outputTokens;
40
+ if (totalTokens !== undefined) {
41
+ usage.total_tokens = totalTokens;
42
+ } else if (inputTokens !== undefined && outputTokens !== undefined) {
43
+ usage.total_tokens = inputTokens + outputTokens;
44
+ }
45
+ if (providerCost !== undefined) usage.provider_cost_usd = providerCost;
46
+
47
+ return usage;
48
+ }
49
+
50
+ /**
51
+ * Extract usage data from response body.
52
+ * Supports OpenAI-style `usage` object and Anthropic-style `usage` object,
53
+ * including cache tokens (Anthropic) and reasoning tokens (OpenAI o1/o3).
54
+ */
55
+ extractFromBody(body: unknown): UsageData | undefined {
56
+ if (!body || typeof body !== 'object') return undefined;
57
+
58
+ const bodyObj = body as Record<string, unknown>;
59
+ const usageObj = bodyObj.usage as Record<string, unknown> | undefined;
60
+
61
+ if (!usageObj || typeof usageObj !== 'object') return undefined;
62
+
63
+ // OpenAI format: usage.prompt_tokens, usage.completion_tokens, usage.total_tokens
64
+ const promptTokens = typeof usageObj.prompt_tokens === 'number' ? usageObj.prompt_tokens : undefined;
65
+ const completionTokens = typeof usageObj.completion_tokens === 'number' ? usageObj.completion_tokens : undefined;
66
+ const totalTokens = typeof usageObj.total_tokens === 'number' ? usageObj.total_tokens : undefined;
67
+
68
+ // Anthropic format: usage.input_tokens, usage.output_tokens
69
+ const inputTokens = typeof usageObj.input_tokens === 'number' ? usageObj.input_tokens : promptTokens;
70
+ const outputTokens = typeof usageObj.output_tokens === 'number' ? usageObj.output_tokens : completionTokens;
71
+
72
+ // Anthropic cache tokens
73
+ const cacheCreationTokens = typeof usageObj.cache_creation_input_tokens === 'number'
74
+ ? usageObj.cache_creation_input_tokens : undefined;
75
+ const cacheReadTokens = typeof usageObj.cache_read_input_tokens === 'number'
76
+ ? usageObj.cache_read_input_tokens : undefined;
77
+
78
+ // OpenAI reasoning tokens (o1/o3 models)
79
+ // Located at usage.completion_tokens_details.reasoning_tokens
80
+ let reasoningTokens: number | undefined;
81
+ const completionDetails = usageObj.completion_tokens_details as Record<string, unknown> | undefined;
82
+ if (completionDetails && typeof completionDetails === 'object') {
83
+ reasoningTokens = typeof completionDetails.reasoning_tokens === 'number'
84
+ ? completionDetails.reasoning_tokens : undefined;
85
+ }
86
+
87
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
88
+ return undefined;
89
+ }
90
+
91
+ const usage: UsageData = {
92
+ source: 'body',
93
+ };
94
+
95
+ if (inputTokens !== undefined) usage.input_tokens = inputTokens;
96
+ if (outputTokens !== undefined) usage.output_tokens = outputTokens;
97
+ if (totalTokens !== undefined) {
98
+ usage.total_tokens = totalTokens;
99
+ } else if (inputTokens !== undefined && outputTokens !== undefined) {
100
+ usage.total_tokens = inputTokens + outputTokens;
101
+ }
102
+ if (cacheCreationTokens !== undefined) usage.cache_creation_tokens = cacheCreationTokens;
103
+ if (cacheReadTokens !== undefined) usage.cache_read_tokens = cacheReadTokens;
104
+ if (reasoningTokens !== undefined) usage.reasoning_tokens = reasoningTokens;
105
+
106
+ return usage;
107
+ }
108
+
109
+ /**
110
+ * Compute cost from usage data using token pricing config.
111
+ * Falls back to built-in MODEL_PRICING when workspace config doesn't have the model.
112
+ * Returns the computed cost in USD, or undefined if pricing not available.
113
+ */
114
+ computeCost(usage: UsageData, model?: string): number | undefined {
115
+ if (!model) return undefined;
116
+
117
+ // Resolve pricing: workspace config → built-in pricing → undefined
118
+ const pricing = resolveModelPricing(model, this.tokenPricing);
119
+ if (!pricing) return undefined;
120
+
121
+ let cost = 0;
122
+
123
+ // Base input tokens (excluding cache tokens which are billed differently)
124
+ if (usage.input_tokens !== undefined) {
125
+ cost += usage.input_tokens * pricing.input_per_token;
126
+ }
127
+
128
+ // Base output tokens
129
+ if (usage.output_tokens !== undefined) {
130
+ cost += usage.output_tokens * pricing.output_per_token;
131
+ }
132
+
133
+ // Cache creation tokens: billed at cache_creation_multiplier × input price (default 1.25x)
134
+ if (usage.cache_creation_tokens !== undefined && usage.cache_creation_tokens > 0) {
135
+ const multiplier = pricing.cache_creation_multiplier ?? 1.25;
136
+ cost += usage.cache_creation_tokens * pricing.input_per_token * multiplier;
137
+ }
138
+
139
+ // Cache read tokens: billed at cache_read_multiplier × input price (default 0.1x)
140
+ if (usage.cache_read_tokens !== undefined && usage.cache_read_tokens > 0) {
141
+ const multiplier = pricing.cache_read_multiplier ?? 0.1;
142
+ cost += usage.cache_read_tokens * pricing.input_per_token * multiplier;
143
+ }
144
+
145
+ // Reasoning tokens (o1/o3): billed at output price
146
+ if (usage.reasoning_tokens !== undefined && usage.reasoning_tokens > 0) {
147
+ cost += usage.reasoning_tokens * pricing.output_per_token;
148
+ }
149
+
150
+ return cost;
151
+ }
152
+
153
+ /**
154
+ * Extract model name from response body.
155
+ * Supports OpenAI format (body.model) and Anthropic format (body.model).
156
+ */
157
+ extractModelFromBody(body: unknown): string | undefined {
158
+ if (!body || typeof body !== 'object') return undefined;
159
+ const bodyObj = body as Record<string, unknown>;
160
+ if (typeof bodyObj.model === 'string') return bodyObj.model;
161
+ return undefined;
162
+ }
163
+
164
+ /**
165
+ * Detect provider from model name.
166
+ */
167
+ detectProvider(model: string): string {
168
+ if (/^claude/i.test(model)) return 'anthropic';
169
+ if (/^gpt|^o[1-9]|^dall-e/i.test(model)) return 'openai';
170
+ if (/^gemini/i.test(model)) return 'google';
171
+ if (/^mistral|^mixtral/i.test(model)) return 'mistral';
172
+ if (/^llama/i.test(model)) return 'meta';
173
+ if (/^command/i.test(model)) return 'cohere';
174
+ return 'unknown';
175
+ }
176
+
177
+ /**
178
+ * Merge two UsageData objects. The second (body) takes precedence for conflicting fields,
179
+ * except provider_cost_usd which is preferred from headers.
180
+ */
181
+ merge(fromHeaders?: UsageData, fromBody?: UsageData): UsageData | undefined {
182
+ if (!fromHeaders && !fromBody) return undefined;
183
+ if (!fromHeaders) return fromBody;
184
+ if (!fromBody) return fromHeaders;
185
+
186
+ return {
187
+ input_tokens: fromBody.input_tokens ?? fromHeaders.input_tokens,
188
+ output_tokens: fromBody.output_tokens ?? fromHeaders.output_tokens,
189
+ total_tokens: fromBody.total_tokens ?? fromHeaders.total_tokens,
190
+ cache_creation_tokens: fromBody.cache_creation_tokens ?? fromHeaders.cache_creation_tokens,
191
+ cache_read_tokens: fromBody.cache_read_tokens ?? fromHeaders.cache_read_tokens,
192
+ reasoning_tokens: fromBody.reasoning_tokens ?? fromHeaders.reasoning_tokens,
193
+ provider_cost_usd: fromHeaders.provider_cost_usd ?? fromBody.provider_cost_usd,
194
+ computed_cost_usd: fromHeaders.computed_cost_usd ?? fromBody.computed_cost_usd,
195
+ source: fromBody.source && fromHeaders.source
196
+ ? `${fromHeaders.source}+${fromBody.source}`
197
+ : fromBody.source || fromHeaders.source,
198
+ model: fromBody.model ?? fromHeaders.model,
199
+ provider: fromBody.provider ?? fromHeaders.provider,
200
+ };
201
+ }
202
+
203
+ private parseIntHeader(value?: string): number | undefined {
204
+ if (value === undefined) return undefined;
205
+ const parsed = parseInt(value, 10);
206
+ return isNaN(parsed) ? undefined : parsed;
207
+ }
208
+
209
+ private parseFloatHeader(value?: string): number | undefined {
210
+ if (value === undefined) return undefined;
211
+ const parsed = parseFloat(value);
212
+ return isNaN(parsed) ? undefined : parsed;
213
+ }
214
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import * as fs from 'fs';
6
+ import { Command } from 'commander';
7
+
8
+ // Resolve the package root (two levels up from dist/src/cli.js)
9
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
10
+
11
+ // Read version from package.json
12
+ const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf-8'));
13
+
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('palaryn')
18
+ .description('Model-agnostic infrastructure layer for AI agent I/O security')
19
+ .version(pkg.version);
20
+
21
+ program
22
+ .command('start', { isDefault: true })
23
+ .description('Start the gateway server')
24
+ .option('-p, --port <port>', 'Server port', '3000')
25
+ .option('--host <host>', 'Bind address', '0.0.0.0')
26
+ .option('--policy-pack <path>', 'Path to policy pack YAML')
27
+ .option('--audit-log-dir <path>', 'Audit log directory')
28
+ .option('--no-auth', 'Disable authentication')
29
+ .action(async (opts) => {
30
+ // Set env vars from CLI flags BEFORE importing server modules
31
+ // (DEFAULT_CONFIG reads env vars at import time)
32
+ process.env.PORT = opts.port;
33
+ process.env.HOST = opts.host;
34
+
35
+ if (opts.policyPack) {
36
+ process.env.POLICY_PACK_PATH = opts.policyPack;
37
+ } else if (!process.env.POLICY_PACK_PATH) {
38
+ // Resolve default policy pack relative to the package installation dir
39
+ const bundledPack = path.join(PACKAGE_ROOT, 'policy-packs', 'default.yaml');
40
+ if (fs.existsSync(bundledPack)) {
41
+ process.env.POLICY_PACK_PATH = bundledPack;
42
+ }
43
+ }
44
+
45
+ if (opts.auditLogDir) {
46
+ process.env.AUDIT_LOG_DIR = opts.auditLogDir;
47
+ } else if (!process.env.AUDIT_LOG_DIR) {
48
+ // Default audit logs to ~/.palaryn/logs for global installs
49
+ const defaultLogDir = path.join(os.homedir(), '.palaryn', 'logs');
50
+ fs.mkdirSync(defaultLogDir, { recursive: true });
51
+ process.env.AUDIT_LOG_DIR = defaultLogDir;
52
+ }
53
+
54
+ if (!opts.auth) {
55
+ process.env.AUTH_ENABLED = 'false';
56
+ }
57
+
58
+ // Now import and start the server (env vars are set)
59
+ const { startServer } = await import('./server/index');
60
+ await startServer();
61
+ });
62
+
63
+ program
64
+ .command('mcp')
65
+ .description('Start MCP stdio server (for claude mcp add palaryn -- palaryn mcp)')
66
+ .option('--workspace <id>', 'Workspace ID')
67
+ .option('--actor <id>', 'Actor ID')
68
+ .action(async (opts) => {
69
+ if (opts.workspace) {
70
+ process.env.PALARYN_MCP_WORKSPACE = opts.workspace;
71
+ }
72
+ if (opts.actor) {
73
+ process.env.PALARYN_MCP_ACTOR = opts.actor;
74
+ }
75
+
76
+ // Resolve default policy pack for MCP server too
77
+ if (!process.env.POLICY_PACK_PATH) {
78
+ const bundledPack = path.join(PACKAGE_ROOT, 'policy-packs', 'default.yaml');
79
+ if (fs.existsSync(bundledPack)) {
80
+ process.env.POLICY_PACK_PATH = bundledPack;
81
+ }
82
+ }
83
+
84
+ const { startMCPServer } = await import('./mcp/server');
85
+ await startMCPServer();
86
+ });
87
+
88
+ program.parseAsync(process.argv).catch((err) => {
89
+ console.error('Fatal error:', err instanceof Error ? err.message : String(err));
90
+ process.exit(1);
91
+ });
@@ -0,0 +1,261 @@
1
+ import * as crypto from 'crypto';
2
+ import * as fs from 'fs';
3
+ import { GatewayConfig } from '../types/config';
4
+ import { MODEL_PRICING } from '../budget/model-pricing';
5
+
6
+ const isProduction = process.env.NODE_ENV === 'production';
7
+
8
+ /**
9
+ * Read a secret from a file path or env var.
10
+ * Lookup order:
11
+ * 1. `<ENV_VAR>_FILE` — path to a file whose contents are the secret
12
+ * (e.g. `/run/secrets/stripe_key`). Trailing whitespace is stripped.
13
+ * 2. `<ENV_VAR>` — plain env var (fallback, dev convenience).
14
+ *
15
+ * This lets you use Docker secrets, Kubernetes secret volumes, or any
16
+ * file-based secret manager without exposing values in env vars.
17
+ */
18
+ function readSecret(envVar: string): string | undefined {
19
+ const filePath = process.env[`${envVar}_FILE`];
20
+ if (filePath) {
21
+ try {
22
+ return fs.readFileSync(filePath, 'utf-8').trim();
23
+ } catch (err) {
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ throw new Error(`Failed to read secret file for ${envVar}_FILE (${filePath}): ${msg}`);
26
+ }
27
+ }
28
+ return process.env[envVar];
29
+ }
30
+
31
+ function requireSecret(envVar: string): string {
32
+ const value = readSecret(envVar);
33
+ if (value) return value;
34
+ if (isProduction) {
35
+ throw new Error(`${envVar} (or ${envVar}_FILE) must be set in production`);
36
+ }
37
+ const generated = crypto.randomBytes(32).toString('hex');
38
+ console.warn(`[config] WARNING: ${envVar} not set — generated random dev secret. Set ${envVar} or ${envVar}_FILE for stable restarts.`);
39
+ return generated;
40
+ }
41
+
42
+ export const DEFAULT_CONFIG: GatewayConfig = {
43
+ port: parseInt(process.env.PORT || '3000', 10),
44
+ host: process.env.HOST || '0.0.0.0',
45
+ auth: {
46
+ enabled: process.env.AUTH_ENABLED !== 'false',
47
+ api_keys: isProduction ? {} : {
48
+ 'dev-key-001': { workspace_id: 'ws_default', description: 'Development key' },
49
+ 'pn_2b922eca4de8b8666f1dcc72d0763de9440deebec42e9277a1a82352771362f3': { workspace_id: 'd214b8e5-da34-41a1-bf30-ae6c0e7dd50c', description: 'Android app key' },
50
+ },
51
+ jwt_secret: requireSecret('JWT_SECRET'),
52
+ jwt: {
53
+ enabled: false,
54
+ secret: process.env.JWT_SECRET,
55
+ algorithms: ['RS256', 'HS256'],
56
+ workspace_claim: 'workspace_id',
57
+ roles_claim: 'roles',
58
+ actor_claim: 'sub',
59
+ },
60
+ rbac: {
61
+ enabled: false, // Off by default for backwards compat
62
+ roles: {
63
+ admin: {
64
+ description: 'Full admin access',
65
+ permissions: ['admin:full'],
66
+ },
67
+ operator: {
68
+ description: 'Can execute tools and manage approvals',
69
+ permissions: ['tool:execute', 'approval:manage', 'trace:read', 'policy:read'],
70
+ },
71
+ readonly: {
72
+ description: 'Read-only access',
73
+ permissions: ['tool:execute:read', 'trace:read', 'policy:read'],
74
+ },
75
+ agent: {
76
+ description: 'AI agent - can execute tools',
77
+ permissions: ['tool:execute'],
78
+ },
79
+ },
80
+ default_role: 'agent',
81
+ },
82
+ },
83
+ policy: {
84
+ pack_path: process.env.POLICY_PACK_PATH || './policy-packs/default.yaml',
85
+ default_effect: 'DENY',
86
+ hot_reload: true,
87
+ },
88
+ dlp: {
89
+ enabled: true,
90
+ scan_args: true,
91
+ scan_output: true,
92
+ secrets_detection: true,
93
+ pii_detection: true,
94
+ prompt_injection_detection: true,
95
+ prompt_injection_action: 'log',
96
+ prompt_injection_block_threshold: 'high',
97
+ prompt_injection_response: 'deny',
98
+ default_redaction_method: 'mask',
99
+ },
100
+ budget: {
101
+ task_budget_usd: process.env.BUDGET_TASK_USD
102
+ ? parseFloat(process.env.BUDGET_TASK_USD) : 2.0,
103
+ user_daily_budget_usd: process.env.BUDGET_USER_DAILY_USD
104
+ ? parseFloat(process.env.BUDGET_USER_DAILY_USD) : 50.0,
105
+ user_monthly_budget_usd: process.env.BUDGET_USER_MONTHLY_USD
106
+ ? parseFloat(process.env.BUDGET_USER_MONTHLY_USD) : 500.0,
107
+ workspace_daily_budget_usd: process.env.BUDGET_WORKSPACE_DAILY_USD
108
+ ? parseFloat(process.env.BUDGET_WORKSPACE_DAILY_USD) : 200.0,
109
+ workspace_monthly_budget_usd: process.env.BUDGET_WORKSPACE_MONTHLY_USD
110
+ ? parseFloat(process.env.BUDGET_WORKSPACE_MONTHLY_USD) : 5000.0,
111
+ max_steps_per_task: process.env.BUDGET_MAX_STEPS
112
+ ? parseInt(process.env.BUDGET_MAX_STEPS, 10) : 50,
113
+ max_retries_per_call: 3,
114
+ max_wall_clock_ms: 300000,
115
+ token_pricing: MODEL_PRICING,
116
+ },
117
+ audit: {
118
+ enabled: true,
119
+ log_dir: process.env.AUDIT_LOG_DIR || './logs',
120
+ console_output: !isProduction,
121
+ retention_days: 30,
122
+ },
123
+ executor: {
124
+ http: {
125
+ timeout_ms: 15000,
126
+ max_retries: 3,
127
+ backoff_base_ms: 1000,
128
+ },
129
+ cache: {
130
+ enabled: true,
131
+ ttl_ms: 300000,
132
+ },
133
+ filesystem: process.env.PALARYN_FILE_ENABLED === 'true' ? {
134
+ enabled: true,
135
+ base_dir: process.env.PALARYN_FILE_BASE_DIR || './sandbox',
136
+ allowed_extensions: process.env.PALARYN_FILE_EXTENSIONS
137
+ ? process.env.PALARYN_FILE_EXTENSIONS.split(',').map(s => s.trim())
138
+ : undefined,
139
+ max_file_size_bytes: process.env.PALARYN_FILE_MAX_SIZE
140
+ ? parseInt(process.env.PALARYN_FILE_MAX_SIZE, 10)
141
+ : 10 * 1024 * 1024,
142
+ } : undefined,
143
+ sql: process.env.PALARYN_SQL_ENABLED === 'true' ? {
144
+ enabled: true,
145
+ connection_string: process.env.PALARYN_SQL_CONNECTION || '',
146
+ timeout_ms: process.env.PALARYN_SQL_TIMEOUT
147
+ ? parseInt(process.env.PALARYN_SQL_TIMEOUT, 10)
148
+ : 30000,
149
+ read_only: process.env.PALARYN_SQL_READONLY !== 'false',
150
+ max_rows: process.env.PALARYN_SQL_MAX_ROWS
151
+ ? parseInt(process.env.PALARYN_SQL_MAX_ROWS, 10)
152
+ : 1000,
153
+ blocked_tables: process.env.PALARYN_SQL_BLOCKED_TABLES
154
+ ? process.env.PALARYN_SQL_BLOCKED_TABLES.split(',').map(s => s.trim())
155
+ : undefined,
156
+ } : undefined,
157
+ shell: process.env.PALARYN_SHELL_ENABLED === 'true' ? {
158
+ enabled: true,
159
+ allowed_commands: process.env.PALARYN_SHELL_ALLOWED
160
+ ? process.env.PALARYN_SHELL_ALLOWED.split(',').map(s => s.trim())
161
+ : [],
162
+ blocked_commands: process.env.PALARYN_SHELL_BLOCKED
163
+ ? process.env.PALARYN_SHELL_BLOCKED.split(',').map(s => s.trim())
164
+ : undefined,
165
+ timeout_ms: process.env.PALARYN_SHELL_TIMEOUT
166
+ ? parseInt(process.env.PALARYN_SHELL_TIMEOUT, 10)
167
+ : 30000,
168
+ cwd: process.env.PALARYN_SHELL_CWD,
169
+ max_output_bytes: process.env.PALARYN_SHELL_MAX_OUTPUT
170
+ ? parseInt(process.env.PALARYN_SHELL_MAX_OUTPUT, 10)
171
+ : 1024 * 1024,
172
+ } : undefined,
173
+ websocket: process.env.PALARYN_WS_ENABLED === 'true' ? {
174
+ enabled: true,
175
+ allowed_urls: process.env.PALARYN_WS_ALLOWED_URLS
176
+ ? process.env.PALARYN_WS_ALLOWED_URLS.split(',').map(s => s.trim())
177
+ : [],
178
+ connect_timeout_ms: process.env.PALARYN_WS_CONNECT_TIMEOUT
179
+ ? parseInt(process.env.PALARYN_WS_CONNECT_TIMEOUT, 10)
180
+ : 10000,
181
+ max_message_size_bytes: process.env.PALARYN_WS_MAX_MSG_SIZE
182
+ ? parseInt(process.env.PALARYN_WS_MAX_MSG_SIZE, 10)
183
+ : 1024 * 1024,
184
+ } : undefined,
185
+ provider_intercept: process.env.PALARYN_PROVIDER_INTERCEPT_ENABLED === 'true' ? {
186
+ enabled: true,
187
+ provider_url_patterns: process.env.PALARYN_PROVIDER_URL_PATTERNS
188
+ ? process.env.PALARYN_PROVIDER_URL_PATTERNS.split(',').map(s => s.trim())
189
+ : ['api\\.anthropic\\.com', 'api\\.openai\\.com', 'generativelanguage\\.googleapis\\.com'],
190
+ scan_inputs: process.env.PALARYN_PROVIDER_SCAN_INPUTS !== 'false',
191
+ scan_outputs: process.env.PALARYN_PROVIDER_SCAN_OUTPUTS !== 'false',
192
+ } : undefined,
193
+ },
194
+ approval: {
195
+ enabled: true,
196
+ token_secret: requireSecret('APPROVAL_SECRET'),
197
+ default_ttl_seconds: 3600,
198
+ },
199
+ rate_limit: {
200
+ enabled: true,
201
+ actor_max_per_window: process.env.RATE_LIMIT_ACTOR_MAX
202
+ ? parseInt(process.env.RATE_LIMIT_ACTOR_MAX, 10) : 100,
203
+ workspace_max_per_window: process.env.RATE_LIMIT_WORKSPACE_MAX
204
+ ? parseInt(process.env.RATE_LIMIT_WORKSPACE_MAX, 10) : 500,
205
+ window_ms: process.env.RATE_LIMIT_WINDOW_MS
206
+ ? parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) : 60000,
207
+ },
208
+ anomaly: {
209
+ enabled: true,
210
+ window_ms: 300000, // 5 minutes (shorter for dev testing)
211
+ z_score_threshold: 2, // Lower threshold for easier testing
212
+ min_samples: 5, // Need only 5 data points before detection activates
213
+ action: 'flag', // 'flag' adds to result metadata; change to 'block' to deny
214
+ track_actors: true,
215
+ track_tools: true,
216
+ track_workspaces: true,
217
+ },
218
+ proxy: {
219
+ enabled: process.env.PROXY_ENABLED === 'true',
220
+ port: parseInt(process.env.PROXY_PORT || '3128', 10),
221
+ passthrough_domains: process.env.PROXY_PASSTHROUGH_DOMAINS
222
+ ? process.env.PROXY_PASSTHROUGH_DOMAINS.split(',').map(s => s.trim())
223
+ : undefined,
224
+ default_workspace_id: process.env.PALARYN_WORKSPACE_ID,
225
+ default_actor_id: process.env.PALARYN_ACTOR_ID,
226
+ require_auth: process.env.PROXY_REQUIRE_AUTH !== 'false',
227
+ },
228
+ cors_origins: process.env.CORS_ORIGINS
229
+ ? process.env.CORS_ORIGINS.split(',').map(s => s.trim())
230
+ : undefined,
231
+ oauth: {
232
+ enabled: false,
233
+ session_secret: '',
234
+ session_ttl_seconds: 604800,
235
+ },
236
+ stripe: readSecret('STRIPE_SECRET_KEY') ? {
237
+ secret_key: readSecret('STRIPE_SECRET_KEY')!,
238
+ webhook_secret: readSecret('STRIPE_WEBHOOK_SECRET') || '',
239
+ price_ids: {
240
+ pro_monthly: process.env.STRIPE_PRICE_PRO_MONTHLY || '',
241
+ business_monthly: process.env.STRIPE_PRICE_BUSINESS_MONTHLY || '',
242
+ },
243
+ checkout_success_url: process.env.STRIPE_CHECKOUT_SUCCESS_URL,
244
+ checkout_cancel_url: process.env.STRIPE_CHECKOUT_CANCEL_URL,
245
+ portal_return_url: process.env.STRIPE_PORTAL_RETURN_URL,
246
+ } : undefined,
247
+ frontend: {
248
+ enabled: process.env.FRONTEND_ENABLED === 'true',
249
+ build_path: process.env.FRONTEND_BUILD_PATH || './web/dist',
250
+ },
251
+ mcp_oauth: process.env.MCP_OAUTH_ENABLED === 'true' ? {
252
+ enabled: true,
253
+ base_url: process.env.MCP_OAUTH_BASE_URL,
254
+ access_token_ttl: process.env.MCP_OAUTH_ACCESS_TOKEN_TTL
255
+ ? parseInt(process.env.MCP_OAUTH_ACCESS_TOKEN_TTL, 10)
256
+ : undefined,
257
+ refresh_token_ttl: process.env.MCP_OAUTH_REFRESH_TOKEN_TTL
258
+ ? parseInt(process.env.MCP_OAUTH_REFRESH_TOKEN_TTL, 10)
259
+ : undefined,
260
+ } : undefined,
261
+ };