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,315 @@
1
+ import { ProviderInterceptConfig } from '../types/config';
2
+
3
+ export interface ProviderToolBlock {
4
+ provider: 'claude' | 'openai' | 'gemini' | 'unknown';
5
+ tool_name: string;
6
+ tool_type: string;
7
+ inputs: Record<string, unknown>;
8
+ outputs?: unknown;
9
+ block_index: number;
10
+ }
11
+
12
+ type Provider = ProviderToolBlock['provider'];
13
+
14
+ const DEFAULT_URL_PATTERNS = [
15
+ 'api\\.anthropic\\.com',
16
+ 'api\\.openai\\.com',
17
+ 'generativelanguage\\.googleapis\\.com',
18
+ ];
19
+
20
+ const CLAUDE_TOOL_TYPES: Record<string, string> = {
21
+ computer_20241022: 'computer_use',
22
+ str_replace_editor: 'text_editor',
23
+ bash_20241022: 'bash',
24
+ };
25
+
26
+ const OPENAI_TOOL_TYPES = new Set([
27
+ 'code_interpreter',
28
+ 'file_search',
29
+ 'web_search',
30
+ ]);
31
+
32
+ const GEMINI_TOOL_TYPES = new Set([
33
+ 'code_execution',
34
+ 'google_search',
35
+ ]);
36
+
37
+ export class ProviderInterceptor {
38
+ private urlPatterns: RegExp[];
39
+ private scanInputs: boolean;
40
+ private scanOutputs: boolean;
41
+
42
+ constructor(config: ProviderInterceptConfig) {
43
+ const patterns = config.provider_url_patterns?.length
44
+ ? config.provider_url_patterns
45
+ : DEFAULT_URL_PATTERNS;
46
+ this.urlPatterns = patterns.map((p) => new RegExp(p));
47
+ this.scanInputs = config.scan_inputs;
48
+ this.scanOutputs = config.scan_outputs;
49
+ }
50
+
51
+ /** Check if a URL matches any provider pattern */
52
+ matchesProvider(url: string): boolean {
53
+ return this.urlPatterns.some((re) => re.test(url));
54
+ }
55
+
56
+ /** Detect which provider a URL belongs to */
57
+ detectProvider(url: string): Provider {
58
+ if (/api\.anthropic\.com/.test(url)) return 'claude';
59
+ if (/api\.openai\.com/.test(url)) return 'openai';
60
+ if (/generativelanguage\.googleapis\.com/.test(url)) return 'gemini';
61
+ return 'unknown';
62
+ }
63
+
64
+ /** Extract tool blocks from a request body (pre-execution) */
65
+ extractFromRequest(body: unknown, provider: string): ProviderToolBlock[] {
66
+ if (!this.scanInputs || !body || typeof body !== 'object') return [];
67
+ try {
68
+ switch (provider) {
69
+ case 'claude': return this.extractClaudeRequest(body);
70
+ case 'openai': return this.extractOpenAIRequest(body);
71
+ case 'gemini': return this.extractGeminiRequest(body);
72
+ default: return [];
73
+ }
74
+ } catch {
75
+ return [];
76
+ }
77
+ }
78
+
79
+ /** Extract tool blocks from a response body (post-execution) */
80
+ extractFromResponse(body: unknown, provider: string): ProviderToolBlock[] {
81
+ if (!this.scanOutputs || !body || typeof body !== 'object') return [];
82
+ try {
83
+ switch (provider) {
84
+ case 'claude': return this.extractClaudeResponse(body);
85
+ case 'openai': return this.extractOpenAIResponse(body);
86
+ case 'gemini': return this.extractGeminiResponse(body);
87
+ default: return [];
88
+ }
89
+ } catch {
90
+ return [];
91
+ }
92
+ }
93
+
94
+ /** Get all inputs as a flat object for DLP scanning */
95
+ flattenInputsForDLP(blocks: ProviderToolBlock[]): Record<string, unknown> {
96
+ const result: Record<string, unknown> = {};
97
+ for (const block of blocks) {
98
+ for (const [key, value] of Object.entries(block.inputs)) {
99
+ result[`${block.tool_name}.${key}`] = value;
100
+ }
101
+ }
102
+ return result;
103
+ }
104
+
105
+ /** Get all outputs as a flat object for DLP scanning */
106
+ flattenOutputsForDLP(blocks: ProviderToolBlock[]): Record<string, unknown> {
107
+ const result: Record<string, unknown> = {};
108
+ for (const block of blocks) {
109
+ if (block.outputs !== undefined) {
110
+ result[`${block.tool_name}.output`] = block.outputs;
111
+ }
112
+ }
113
+ return result;
114
+ }
115
+
116
+ // --- Claude extraction ---
117
+
118
+ private extractClaudeRequest(body: unknown): ProviderToolBlock[] {
119
+ const blocks: ProviderToolBlock[] = [];
120
+ const messages = getArray(body, 'messages');
121
+ let index = 0;
122
+ for (const msg of messages) {
123
+ const content = getArray(msg, 'content');
124
+ for (const part of content) {
125
+ if (!isObject(part)) continue;
126
+ const type = getString(part, 'type');
127
+ if (type === 'tool_use') {
128
+ const name = getString(part, 'name') || 'unknown';
129
+ blocks.push({
130
+ provider: 'claude',
131
+ tool_name: name,
132
+ tool_type: resolveClaudeToolType(name),
133
+ inputs: isObject(part.input) ? part.input as Record<string, unknown> : {},
134
+ block_index: index++,
135
+ });
136
+ } else if (type === 'tool_result') {
137
+ const toolUseId = getString(part, 'tool_use_id') || 'unknown';
138
+ blocks.push({
139
+ provider: 'claude',
140
+ tool_name: `tool_result:${toolUseId}`,
141
+ tool_type: 'tool_result',
142
+ inputs: {},
143
+ outputs: part.content,
144
+ block_index: index++,
145
+ });
146
+ }
147
+ }
148
+ }
149
+ return blocks;
150
+ }
151
+
152
+ private extractClaudeResponse(body: unknown): ProviderToolBlock[] {
153
+ const blocks: ProviderToolBlock[] = [];
154
+ const content = getArray(body, 'content');
155
+ let index = 0;
156
+ for (const part of content) {
157
+ if (!isObject(part)) continue;
158
+ if (getString(part, 'type') === 'tool_use') {
159
+ const name = getString(part, 'name') || 'unknown';
160
+ blocks.push({
161
+ provider: 'claude',
162
+ tool_name: name,
163
+ tool_type: resolveClaudeToolType(name),
164
+ inputs: isObject(part.input) ? part.input as Record<string, unknown> : {},
165
+ block_index: index++,
166
+ });
167
+ }
168
+ }
169
+ return blocks;
170
+ }
171
+
172
+ // --- OpenAI extraction ---
173
+
174
+ private extractOpenAIRequest(body: unknown): ProviderToolBlock[] {
175
+ const blocks: ProviderToolBlock[] = [];
176
+ const messages = getArray(body, 'messages');
177
+ let index = 0;
178
+ for (const msg of messages) {
179
+ const toolCalls = getArray(msg, 'tool_calls');
180
+ for (const tc of toolCalls) {
181
+ if (!isObject(tc)) continue;
182
+ const fn = isObject(tc.function) ? tc.function as Record<string, unknown> : null;
183
+ if (!fn) continue;
184
+ const name = getString(fn, 'name') || 'unknown';
185
+ const args = parseJsonString(getString(fn, 'arguments'));
186
+ blocks.push({
187
+ provider: 'openai',
188
+ tool_name: name,
189
+ tool_type: resolveOpenAIToolType(name),
190
+ inputs: args,
191
+ block_index: index++,
192
+ });
193
+ }
194
+ }
195
+ return blocks;
196
+ }
197
+
198
+ private extractOpenAIResponse(body: unknown): ProviderToolBlock[] {
199
+ const blocks: ProviderToolBlock[] = [];
200
+ const choices = getArray(body, 'choices');
201
+ let index = 0;
202
+ for (const choice of choices) {
203
+ if (!isObject(choice)) continue;
204
+ const message = isObject(choice.message) ? choice.message as Record<string, unknown> : null;
205
+ if (!message) continue;
206
+ const toolCalls = getArray(message, 'tool_calls');
207
+ for (const tc of toolCalls) {
208
+ if (!isObject(tc)) continue;
209
+ const fn = isObject(tc.function) ? tc.function as Record<string, unknown> : null;
210
+ if (!fn) continue;
211
+ const name = getString(fn, 'name') || 'unknown';
212
+ const args = parseJsonString(getString(fn, 'arguments'));
213
+ blocks.push({
214
+ provider: 'openai',
215
+ tool_name: name,
216
+ tool_type: resolveOpenAIToolType(name),
217
+ inputs: args,
218
+ block_index: index++,
219
+ });
220
+ }
221
+ }
222
+ return blocks;
223
+ }
224
+
225
+ // --- Gemini extraction ---
226
+
227
+ private extractGeminiRequest(body: unknown): ProviderToolBlock[] {
228
+ const blocks: ProviderToolBlock[] = [];
229
+ const contents = getArray(body, 'contents');
230
+ let index = 0;
231
+ for (const content of contents) {
232
+ if (!isObject(content)) continue;
233
+ const parts = getArray(content, 'parts');
234
+ for (const part of parts) {
235
+ if (!isObject(part)) continue;
236
+ const fc = isObject(part.functionCall) ? part.functionCall as Record<string, unknown> : null;
237
+ if (!fc) continue;
238
+ const name = getString(fc, 'name') || 'unknown';
239
+ blocks.push({
240
+ provider: 'gemini',
241
+ tool_name: name,
242
+ tool_type: resolveGeminiToolType(name),
243
+ inputs: isObject(fc.args) ? fc.args as Record<string, unknown> : {},
244
+ block_index: index++,
245
+ });
246
+ }
247
+ }
248
+ return blocks;
249
+ }
250
+
251
+ private extractGeminiResponse(body: unknown): ProviderToolBlock[] {
252
+ const blocks: ProviderToolBlock[] = [];
253
+ const candidates = getArray(body, 'candidates');
254
+ let index = 0;
255
+ for (const candidate of candidates) {
256
+ if (!isObject(candidate)) continue;
257
+ const content = isObject(candidate.content) ? candidate.content as Record<string, unknown> : null;
258
+ if (!content) continue;
259
+ const parts = getArray(content, 'parts');
260
+ for (const part of parts) {
261
+ if (!isObject(part)) continue;
262
+ const fc = isObject(part.functionCall) ? part.functionCall as Record<string, unknown> : null;
263
+ if (!fc) continue;
264
+ const name = getString(fc, 'name') || 'unknown';
265
+ blocks.push({
266
+ provider: 'gemini',
267
+ tool_name: name,
268
+ tool_type: resolveGeminiToolType(name),
269
+ inputs: isObject(fc.args) ? fc.args as Record<string, unknown> : {},
270
+ block_index: index++,
271
+ });
272
+ }
273
+ }
274
+ return blocks;
275
+ }
276
+ }
277
+
278
+ // --- Helpers ---
279
+
280
+ function isObject(val: unknown): val is Record<string, unknown> {
281
+ return val !== null && typeof val === 'object' && !Array.isArray(val);
282
+ }
283
+
284
+ function getArray(obj: unknown, key: string): unknown[] {
285
+ if (!isObject(obj)) return [];
286
+ const val = obj[key];
287
+ return Array.isArray(val) ? val : [];
288
+ }
289
+
290
+ function getString(obj: Record<string, unknown>, key: string): string {
291
+ const val = obj[key];
292
+ return typeof val === 'string' ? val : '';
293
+ }
294
+
295
+ function parseJsonString(str: string): Record<string, unknown> {
296
+ if (!str) return {};
297
+ try {
298
+ const parsed = JSON.parse(str);
299
+ return isObject(parsed) ? parsed : {};
300
+ } catch {
301
+ return {};
302
+ }
303
+ }
304
+
305
+ function resolveClaudeToolType(name: string): string {
306
+ return CLAUDE_TOOL_TYPES[name] || 'function';
307
+ }
308
+
309
+ function resolveOpenAIToolType(name: string): string {
310
+ return OPENAI_TOOL_TYPES.has(name) ? name : 'function';
311
+ }
312
+
313
+ function resolveGeminiToolType(name: string): string {
314
+ return GEMINI_TOOL_TYPES.has(name) ? name : 'function';
315
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Hybrid OAuth Token Verifier for MCP bearer auth.
3
+ *
4
+ * Implements OAuthTokenVerifier from the MCP SDK. Tries OAuth token
5
+ * verification first, then falls back to API key lookup (config keys +
6
+ * SaaS user keys). This preserves backward compatibility so that existing
7
+ * `--header "Authorization: Bearer <api-key>"` usage still works.
8
+ */
9
+ import * as crypto from 'crypto';
10
+ import { OAuthTokenVerifier } from '@modelcontextprotocol/sdk/server/auth/provider.js';
11
+ import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
12
+ import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
13
+ import { AuthConfig } from '../types/config';
14
+ import { UserApiKeyStore } from '../storage/interfaces';
15
+ import { resolvePermissions } from '../middleware/auth';
16
+ import { PalarynOAuthProvider } from './oauth-provider';
17
+
18
+ export interface HybridVerifierDeps {
19
+ oauthProvider?: PalarynOAuthProvider;
20
+ authConfig: AuthConfig;
21
+ userApiKeyStore?: UserApiKeyStore;
22
+ }
23
+
24
+ export class HybridTokenVerifier implements OAuthTokenVerifier {
25
+ private oauthProvider?: PalarynOAuthProvider;
26
+ private authConfig: AuthConfig;
27
+ private userApiKeyStore?: UserApiKeyStore;
28
+
29
+ constructor(deps: HybridVerifierDeps) {
30
+ this.oauthProvider = deps.oauthProvider;
31
+ this.authConfig = deps.authConfig;
32
+ this.userApiKeyStore = deps.userApiKeyStore;
33
+ }
34
+
35
+ /**
36
+ * Constant-time API key lookup. Iterates all configured keys and uses
37
+ * crypto.timingSafeEqual to prevent timing-based side-channel attacks.
38
+ */
39
+ private constantTimeLookup(token: string): string | undefined {
40
+ const tokenHash = crypto.createHash('sha256').update(token).digest();
41
+ let matched: string | undefined;
42
+
43
+ for (const key of Object.keys(this.authConfig.api_keys)) {
44
+ const keyHash = crypto.createHash('sha256').update(key).digest();
45
+ if (crypto.timingSafeEqual(tokenHash, keyHash)) {
46
+ matched = key;
47
+ }
48
+ }
49
+
50
+ return matched;
51
+ }
52
+
53
+ async verifyAccessToken(token: string): Promise<AuthInfo> {
54
+ // 1. Try OAuth token first (if provider is configured)
55
+ if (this.oauthProvider) {
56
+ try {
57
+ return await this.oauthProvider.verifyAccessToken(token);
58
+ } catch {
59
+ // Not an OAuth token — fall through to API key
60
+ }
61
+ }
62
+
63
+ // 2. Try config API keys (constant-time comparison to prevent timing attacks)
64
+ if (this.authConfig.enabled) {
65
+ const matchedKey = this.constantTimeLookup(token);
66
+ const keyConfig = matchedKey ? this.authConfig.api_keys[matchedKey] : undefined;
67
+ if (keyConfig && !keyConfig.revoked) {
68
+ if (keyConfig.expires_at) {
69
+ const expiresAt = new Date(keyConfig.expires_at);
70
+ if (expiresAt.getTime() <= Date.now()) {
71
+ throw new InvalidTokenError('API key has expired');
72
+ }
73
+ }
74
+
75
+ // Fire-and-forget: update last_used_at
76
+ keyConfig.last_used_at = new Date().toISOString();
77
+
78
+ const roles = keyConfig.roles || [];
79
+ const permissions = resolvePermissions(roles, this.authConfig.rbac);
80
+
81
+ return {
82
+ token,
83
+ clientId: 'api_key',
84
+ scopes: ['mcp:tools'],
85
+ expiresAt: keyConfig.expires_at
86
+ ? Math.floor(new Date(keyConfig.expires_at).getTime() / 1000)
87
+ : Math.floor(Date.now() / 1000) + 86400, // default: 24h from now
88
+ extra: {
89
+ workspace_id: keyConfig.workspace_id,
90
+ actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
91
+ roles,
92
+ permissions,
93
+ auth_method: 'api_key',
94
+ },
95
+ };
96
+ }
97
+ }
98
+
99
+ // 3. Try SaaS-generated API keys (UserApiKeyStore, supports salted + unsalted)
100
+ if (this.userApiKeyStore) {
101
+ let saasKey: ReturnType<typeof this.userApiKeyStore.getByKeyHash>;
102
+ if (this.userApiKeyStore.verifyToken) {
103
+ saasKey = this.userApiKeyStore.verifyToken(token);
104
+ } else {
105
+ const keyHash = crypto.createHash('sha256').update(token).digest('hex');
106
+ saasKey = this.userApiKeyStore.getByKeyHash(keyHash);
107
+ }
108
+ if (saasKey && !saasKey.revoked) {
109
+ // Fire-and-forget: update last_used_at
110
+ this.userApiKeyStore.update(saasKey.id, { last_used_at: new Date().toISOString() });
111
+
112
+ const roles = saasKey.roles || [];
113
+ const permissions = resolvePermissions(roles, this.authConfig.rbac);
114
+
115
+ return {
116
+ token,
117
+ clientId: 'api_key',
118
+ scopes: ['mcp:tools'],
119
+ expiresAt: Math.floor(Date.now() / 1000) + 86400, // 24h
120
+ extra: {
121
+ workspace_id: saasKey.workspace_id,
122
+ actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
123
+ user_id: saasKey.user_id,
124
+ roles,
125
+ permissions,
126
+ auth_method: 'api_key',
127
+ api_key_tags: saasKey.tags && saasKey.tags.length > 0 ? saasKey.tags : undefined,
128
+ },
129
+ };
130
+ }
131
+ }
132
+
133
+ // 4. Auth disabled — anonymous access
134
+ if (!this.authConfig.enabled) {
135
+ return {
136
+ token,
137
+ clientId: 'anonymous',
138
+ scopes: ['mcp:tools'],
139
+ expiresAt: Math.floor(Date.now() / 1000) + 86400,
140
+ extra: {
141
+ workspace_id: 'ws_default',
142
+ actor_id: 'anonymous',
143
+ roles: [],
144
+ permissions: [],
145
+ auth_method: 'none',
146
+ },
147
+ };
148
+ }
149
+
150
+ throw new InvalidTokenError('Invalid access token');
151
+ }
152
+ }