whale-code 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,240 @@
1
+ // server/lib/ssrf-guard.ts — Shared SSRF validation with DNS resolve-then-check
2
+ //
3
+ // Used by workflow steps (webhook_out, custom), email attachments, and any other
4
+ // user-controlled URL fetching. Centralizes all IP blocking logic in one place.
5
+ import { resolve as dnsResolve } from "node:dns/promises";
6
+ // Private/internal IPv4 ranges
7
+ const PRIVATE_IPV4_RANGES = [
8
+ /^127\./, // Loopback
9
+ /^10\./, // RFC 1918
10
+ /^172\.(1[6-9]|2\d|3[01])\./, // RFC 1918
11
+ /^192\.168\./, // RFC 1918
12
+ /^169\.254\./, // Link-local / cloud metadata
13
+ /^0\./, // "This" network
14
+ /^100\.(6[4-9]|[7-9]\d|1[0-1]\d|12[0-7])\./, // CGNAT (RFC 6598)
15
+ /^198\.18\./, // Benchmarking (RFC 2544)
16
+ ];
17
+ // IPv6-mapped IPv4 patterns
18
+ // Dotted decimal form: ::ffff:127.0.0.1
19
+ const IPV6_MAPPED_V4 = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/i;
20
+ // Hex form (URL parser normalizes to this): ::ffff:7f00:1
21
+ const IPV6_MAPPED_V4_HEX = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i;
22
+ // Private/internal IPv6 ranges
23
+ const PRIVATE_IPV6_PATTERNS = [
24
+ /^::1$/, // Loopback
25
+ /^fe80:/i, // Link-local
26
+ /^fc00:/i, // Unique local
27
+ /^fd/i, // Unique local
28
+ /^::$/, // Unspecified address
29
+ ];
30
+ const BLOCKED_HOSTS = new Set([
31
+ "localhost",
32
+ "0.0.0.0",
33
+ "metadata.google.internal",
34
+ "metadata.internal",
35
+ ]);
36
+ const BLOCKED_TLDS = [".internal", ".local", ".localhost"];
37
+ /**
38
+ * P0 FIX: Normalize alternative IP encodings to standard dotted-decimal.
39
+ * Catches bypass attempts via decimal, hex, octal, and mixed representations:
40
+ * - Decimal: 2130706433 → 127.0.0.1
41
+ * - Hex: 0x7f000001 → 127.0.0.1
42
+ * - Octal: 0177.0.0.01 → 127.0.0.1
43
+ * - Mixed: 0x7f.0.0.1 → 127.0.0.1
44
+ * - Leading zeros: 0127.0.0.01 → 87.0.0.1 (octal interpretation)
45
+ *
46
+ * Returns the normalized dotted-decimal string, or null if not a recognizable IP.
47
+ */
48
+ function normalizeIp(input) {
49
+ const cleaned = input.trim();
50
+ // Case 1: Pure decimal integer (e.g. 2130706433 = 127.0.0.1)
51
+ if (/^\d{1,10}$/.test(cleaned) && !cleaned.startsWith("0")) {
52
+ const num = parseInt(cleaned, 10);
53
+ if (num >= 0 && num <= 0xffffffff) {
54
+ return `${(num >>> 24) & 0xff}.${(num >>> 16) & 0xff}.${(num >>> 8) & 0xff}.${num & 0xff}`;
55
+ }
56
+ }
57
+ // Case 2: Pure hex integer (e.g. 0x7f000001)
58
+ if (/^0x[0-9a-f]+$/i.test(cleaned)) {
59
+ const num = parseInt(cleaned, 16);
60
+ if (num >= 0 && num <= 0xffffffff) {
61
+ return `${(num >>> 24) & 0xff}.${(num >>> 16) & 0xff}.${(num >>> 8) & 0xff}.${num & 0xff}`;
62
+ }
63
+ }
64
+ // Case 3: Dotted form with octal/hex/decimal octets (e.g. 0177.0.0.01, 0x7f.0.0.1)
65
+ const parts = cleaned.split(".");
66
+ if (parts.length === 4) {
67
+ const octets = [];
68
+ for (const part of parts) {
69
+ let val;
70
+ if (/^0x[0-9a-f]+$/i.test(part)) {
71
+ // Hex octet
72
+ val = parseInt(part, 16);
73
+ }
74
+ else if (/^0\d+$/.test(part)) {
75
+ // Octal octet (leading zero)
76
+ val = parseInt(part, 8);
77
+ }
78
+ else if (/^\d+$/.test(part)) {
79
+ // Decimal octet
80
+ val = parseInt(part, 10);
81
+ }
82
+ else {
83
+ return null; // Not a valid octet
84
+ }
85
+ if (isNaN(val) || val < 0 || val > 255)
86
+ return null;
87
+ octets.push(val);
88
+ }
89
+ return octets.join(".");
90
+ }
91
+ return null;
92
+ }
93
+ /**
94
+ * Check if an IP address is in a private/internal range.
95
+ * P0 FIX: Now normalizes alternative IP encodings before checking.
96
+ */
97
+ function isPrivateIp(ip) {
98
+ // Strip brackets from IPv6
99
+ const cleaned = ip.replace(/^\[|\]$/g, "");
100
+ // Check IPv6-mapped IPv4 — dotted decimal form (::ffff:x.x.x.x)
101
+ const mapped = cleaned.match(IPV6_MAPPED_V4);
102
+ if (mapped) {
103
+ const v4 = mapped[1];
104
+ const normalized = normalizeIp(v4) || v4;
105
+ return PRIVATE_IPV4_RANGES.some((r) => r.test(normalized));
106
+ }
107
+ // Check IPv6-mapped IPv4 — hex form (::ffff:7f00:1, as normalized by URL parser)
108
+ const mappedHex = cleaned.match(IPV6_MAPPED_V4_HEX);
109
+ if (mappedHex) {
110
+ const hi = parseInt(mappedHex[1], 16);
111
+ const lo = parseInt(mappedHex[2], 16);
112
+ const v4 = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
113
+ return PRIVATE_IPV4_RANGES.some((r) => r.test(v4));
114
+ }
115
+ // P0 FIX: Normalize alternative encodings (decimal, hex, octal, mixed)
116
+ const normalized = normalizeIp(cleaned);
117
+ if (normalized) {
118
+ return PRIVATE_IPV4_RANGES.some((r) => r.test(normalized));
119
+ }
120
+ // Check plain IPv4 (standard dotted decimal — no leading zeros)
121
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(cleaned)) {
122
+ return PRIVATE_IPV4_RANGES.some((r) => r.test(cleaned));
123
+ }
124
+ // Check IPv6
125
+ return PRIVATE_IPV6_PATTERNS.some((r) => r.test(cleaned));
126
+ }
127
+ /**
128
+ * Check if a hostname looks like a direct IP address (v4, v6, or alternative encoding).
129
+ * Used to decide whether DNS failure should block or allow.
130
+ */
131
+ function looksLikeIp(host) {
132
+ const cleaned = host.replace(/^\[|\]$/g, "");
133
+ // Standard dotted decimal
134
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(cleaned))
135
+ return true;
136
+ // Pure decimal integer
137
+ if (/^\d{1,10}$/.test(cleaned))
138
+ return true;
139
+ // Hex integer
140
+ if (/^0x[0-9a-f]+$/i.test(cleaned))
141
+ return true;
142
+ // Dotted with octal/hex octets
143
+ if (/^[\d.x]+$/i.test(cleaned) && cleaned.split(".").length === 4)
144
+ return true;
145
+ // IPv6 (contains colons)
146
+ if (cleaned.includes(":"))
147
+ return true;
148
+ return false;
149
+ }
150
+ /**
151
+ * Validate a URL is safe to fetch (not targeting internal/private addresses).
152
+ * Does basic hostname checks first, then resolves DNS to catch rebinding attacks.
153
+ *
154
+ * @returns null if safe, or a string error message if blocked.
155
+ */
156
+ export async function validateUrl(urlStr) {
157
+ let u;
158
+ try {
159
+ u = new URL(urlStr);
160
+ }
161
+ catch {
162
+ return "Malformed URL";
163
+ }
164
+ // Block non-HTTP(S) schemes
165
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
166
+ return `Blocked scheme: ${u.protocol}`;
167
+ }
168
+ const host = u.hostname.toLowerCase();
169
+ // Block known internal hostnames
170
+ if (BLOCKED_HOSTS.has(host)) {
171
+ return "URL targets a blocked internal host";
172
+ }
173
+ // Block internal TLDs
174
+ for (const tld of BLOCKED_TLDS) {
175
+ if (host.endsWith(tld)) {
176
+ return "URL targets a blocked internal domain";
177
+ }
178
+ }
179
+ // Block .fly.dev (our own infrastructure)
180
+ if (host.endsWith(".fly.dev")) {
181
+ return "URL targets internal infrastructure";
182
+ }
183
+ // Block bare IPs that resolve to private ranges (including alternative encodings)
184
+ if (isPrivateIp(host)) {
185
+ return "URL targets a private/internal IP address";
186
+ }
187
+ // DNS resolve-then-check: catch DNS rebinding attacks
188
+ try {
189
+ const addresses = await dnsResolve(host);
190
+ for (const addr of addresses) {
191
+ if (isPrivateIp(addr)) {
192
+ return `DNS resolved to private address: ${addr}`;
193
+ }
194
+ }
195
+ }
196
+ catch {
197
+ // P0 FIX: DNS resolution failed — only allow through if host is already a
198
+ // validated direct IP (which was checked above). If it's a hostname that
199
+ // failed DNS, block it: we can't verify where it points.
200
+ if (!looksLikeIp(host)) {
201
+ return "DNS resolution failed — cannot verify target is safe";
202
+ }
203
+ // Direct IP that passed isPrivateIp check above — safe to proceed
204
+ }
205
+ return null;
206
+ }
207
+ /**
208
+ * Synchronous fast-path check (no DNS resolution).
209
+ * Use this when you can't await, or for backward compatibility.
210
+ * For full protection (including DNS rebinding), use validateUrl().
211
+ */
212
+ export function isBlockedUrl(urlStr) {
213
+ try {
214
+ const u = new URL(urlStr);
215
+ const host = u.hostname.toLowerCase();
216
+ if (BLOCKED_HOSTS.has(host) || host === "127.0.0.1" || host === "::1" || host === "[::1]")
217
+ return true;
218
+ if (PRIVATE_IPV4_RANGES.some((r) => r.test(host)))
219
+ return true;
220
+ if (PRIVATE_IPV6_PATTERNS.some((r) => r.test(host.replace(/^\[|\]$/g, ""))))
221
+ return true;
222
+ // P0 FIX: Check alternative IP encodings (decimal, hex, octal)
223
+ if (isPrivateIp(host))
224
+ return true;
225
+ if (host === "169.254.169.254")
226
+ return true;
227
+ for (const tld of BLOCKED_TLDS) {
228
+ if (host.endsWith(tld))
229
+ return true;
230
+ }
231
+ if (host.endsWith(".fly.dev"))
232
+ return true;
233
+ if (u.protocol !== "http:" && u.protocol !== "https:")
234
+ return true;
235
+ return false;
236
+ }
237
+ catch {
238
+ return true;
239
+ }
240
+ }
@@ -0,0 +1,7 @@
1
+ import { type SupabaseClient } from "@supabase/supabase-js";
2
+ /** Initialize the shared Supabase client (call once at startup) */
3
+ export declare function initSupabase(url: string, serviceRoleKey: string): void;
4
+ /** Get the shared service role Supabase client (singleton, retry-enabled) */
5
+ export declare function getServiceClient(): SupabaseClient;
6
+ /** Create a user-scoped client (for RLS context) — uses retry fetch */
7
+ export declare function createUserClient(url: string, anonKey: string, token: string): SupabaseClient;
@@ -0,0 +1,78 @@
1
+ // lib/supabase-client.ts — Resilient Supabase client with retry logic
2
+ // Fixes intermittent 520 errors from Cloudflare by retrying failed requests
3
+ // and reusing client instances instead of creating new ones per request.
4
+ import { createClient } from "@supabase/supabase-js";
5
+ const MAX_RETRIES = 3;
6
+ const INITIAL_BACKOFF_MS = 500;
7
+ const MAX_BACKOFF_MS = 5_000;
8
+ /** Custom fetch with retry for 5xx errors (Cloudflare 520/522/524) */
9
+ function createRetryFetch(maxRetries = MAX_RETRIES) {
10
+ return async (input, init) => {
11
+ let lastError = null;
12
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
13
+ try {
14
+ const res = await fetch(input, {
15
+ ...init,
16
+ // Keep connection alive to reduce TCP handshake overhead
17
+ keepalive: true,
18
+ });
19
+ // Retry on 5xx errors (Cloudflare 520 = origin error, 522 = timeout, 524 = timeout)
20
+ if (res.status >= 500 && attempt < maxRetries) {
21
+ const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt), MAX_BACKOFF_MS);
22
+ console.warn(`[supabase] ${res.status} on ${typeof input === 'string' ? input.split('?')[0] : 'request'}, retry ${attempt + 1}/${maxRetries} in ${backoff}ms`);
23
+ await new Promise(r => setTimeout(r, backoff));
24
+ continue;
25
+ }
26
+ return res;
27
+ }
28
+ catch (err) {
29
+ lastError = err;
30
+ // Retry on network errors (ECONNRESET, ETIMEDOUT, etc.)
31
+ if (attempt < maxRetries) {
32
+ const backoff = Math.min(INITIAL_BACKOFF_MS * Math.pow(2, attempt), MAX_BACKOFF_MS);
33
+ console.warn(`[supabase] Network error: ${lastError.message}, retry ${attempt + 1}/${maxRetries} in ${backoff}ms`);
34
+ await new Promise(r => setTimeout(r, backoff));
35
+ continue;
36
+ }
37
+ }
38
+ }
39
+ throw lastError || new Error("Supabase request failed after retries");
40
+ };
41
+ }
42
+ // Singleton service role client — reused across all requests
43
+ let _serviceClient = null;
44
+ let _supabaseUrl = "";
45
+ let _serviceRoleKey = "";
46
+ /** Initialize the shared Supabase client (call once at startup) */
47
+ export function initSupabase(url, serviceRoleKey) {
48
+ _supabaseUrl = url;
49
+ _serviceRoleKey = serviceRoleKey;
50
+ _serviceClient = null; // Reset on re-init
51
+ }
52
+ /** Get the shared service role Supabase client (singleton, retry-enabled) */
53
+ export function getServiceClient() {
54
+ if (!_serviceClient) {
55
+ if (!_supabaseUrl || !_serviceRoleKey) {
56
+ throw new Error("Supabase not initialized. Call initSupabase() first.");
57
+ }
58
+ _serviceClient = createClient(_supabaseUrl, _serviceRoleKey, {
59
+ global: {
60
+ fetch: createRetryFetch(),
61
+ },
62
+ auth: {
63
+ autoRefreshToken: false,
64
+ persistSession: false,
65
+ },
66
+ });
67
+ }
68
+ return _serviceClient;
69
+ }
70
+ /** Create a user-scoped client (for RLS context) — uses retry fetch */
71
+ export function createUserClient(url, anonKey, token) {
72
+ return createClient(url, anonKey, {
73
+ global: {
74
+ headers: { Authorization: `Bearer ${token}` },
75
+ fetch: createRetryFetch(2), // Fewer retries for user requests
76
+ },
77
+ });
78
+ }
@@ -0,0 +1,31 @@
1
+ export interface TemplateContext {
2
+ steps: Record<string, {
3
+ output?: unknown;
4
+ status?: string;
5
+ duration_ms?: number;
6
+ }>;
7
+ trigger: Record<string, unknown>;
8
+ input?: unknown;
9
+ env?: Record<string, unknown>;
10
+ workflow?: Record<string, unknown>;
11
+ run?: Record<string, unknown>;
12
+ }
13
+ /**
14
+ * Recursively resolve templates in any JSON-compatible value.
15
+ * - Strings: resolve {{...}} patterns
16
+ * - Objects: recurse into values
17
+ * - Arrays: recurse into elements
18
+ * - Primitives: return as-is
19
+ */
20
+ export declare function resolveTemplate(value: unknown, ctx: TemplateContext, depth?: number): unknown;
21
+ /**
22
+ * Evaluate a simple condition expression.
23
+ * Supports: ==, !=, >, <, >=, <=, contains, !contains, exists, !exists
24
+ * Templates in the expression are resolved first.
25
+ *
26
+ * Examples:
27
+ * "{{steps.check.output.count}} > 10"
28
+ * "{{steps.fetch.output.status}} == 'active'"
29
+ * "{{trigger.type}} != 'test'"
30
+ */
31
+ export declare function evaluateCondition(expression: string, ctx: TemplateContext): boolean;
@@ -0,0 +1,215 @@
1
+ // server/lib/template-resolver.ts — Resolves {{steps.X.output.Y}} and {{trigger.field}} templates
2
+ //
3
+ // Used by workflow engine to flow data between steps.
4
+ // Extends the fillTemplate() pattern from utils.ts with workflow-specific contexts.
5
+ const TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g;
6
+ /** Block prototype pollution / traversal attacks */
7
+ const BLOCKED_PROPS = new Set([
8
+ "__proto__", "constructor", "prototype",
9
+ "__defineGetter__", "__defineSetter__",
10
+ "__lookupGetter__", "__lookupSetter__",
11
+ ]);
12
+ /**
13
+ * Safely walk a dot-path into an object, blocking dangerous property names.
14
+ */
15
+ function walkPath(value, parts, startIndex) {
16
+ let current = value;
17
+ for (let i = startIndex; i < parts.length; i++) {
18
+ if (current === null || current === undefined)
19
+ return undefined;
20
+ if (typeof current !== "object")
21
+ return undefined;
22
+ if (BLOCKED_PROPS.has(parts[i]))
23
+ return undefined; // Block prototype traversal
24
+ current = current[parts[i]];
25
+ }
26
+ return current;
27
+ }
28
+ /**
29
+ * Resolve a single template expression like "steps.check.output.count" against the context.
30
+ * Returns the resolved value, or undefined if the path doesn't exist.
31
+ */
32
+ function resolveExpression(expr, ctx) {
33
+ const trimmed = expr.trim();
34
+ // Built-in variables
35
+ if (trimmed === "now")
36
+ return new Date().toISOString();
37
+ if (trimmed === "today")
38
+ return new Date().toISOString().slice(0, 10);
39
+ if (trimmed === "timestamp")
40
+ return Date.now();
41
+ // Navigate the dot path
42
+ const parts = trimmed.split(".");
43
+ const root = parts[0];
44
+ let value;
45
+ if (root === "steps" && parts.length >= 2) {
46
+ const stepKey = parts[1];
47
+ const stepData = ctx.steps[stepKey];
48
+ if (!stepData)
49
+ return undefined;
50
+ value = walkPath(stepData, parts, 2);
51
+ }
52
+ else if (root === "trigger") {
53
+ value = walkPath(ctx.trigger, parts, 1);
54
+ }
55
+ else if (root === "input") {
56
+ value = walkPath(ctx.input, parts, 1);
57
+ }
58
+ else if (root === "workflow" && ctx.workflow) {
59
+ value = walkPath(ctx.workflow, parts, 1);
60
+ }
61
+ else if (root === "run" && ctx.run) {
62
+ value = walkPath(ctx.run, parts, 1);
63
+ }
64
+ else if (root === "env" && ctx.env) {
65
+ // P0 FIX: Only allow explicitly whitelisted env keys — never expose process.env
66
+ const ALLOWED_ENV_KEYS = new Set(["APP_URL", "APP_NAME", "NODE_ENV"]);
67
+ if (parts.length >= 2 && !ALLOWED_ENV_KEYS.has(parts[1]))
68
+ return undefined;
69
+ value = walkPath(ctx.env, parts, 1);
70
+ }
71
+ else {
72
+ return undefined;
73
+ }
74
+ return value;
75
+ }
76
+ /**
77
+ * Resolve all {{...}} placeholders in a string.
78
+ * If the entire string is a single template AND the resolved value is not a string,
79
+ * return the raw value (preserving numbers, objects, arrays, booleans).
80
+ */
81
+ function resolveString(template, ctx) {
82
+ // Check if the entire string is a single template expression
83
+ const singleMatch = template.match(/^\{\{([^}]+)\}\}$/);
84
+ if (singleMatch) {
85
+ const resolved = resolveExpression(singleMatch[1], ctx);
86
+ return resolved !== undefined ? resolved : template;
87
+ }
88
+ // Multiple templates or mixed text — string interpolation
89
+ return template.replace(TEMPLATE_REGEX, (match, expr) => {
90
+ const resolved = resolveExpression(expr, ctx);
91
+ if (resolved === undefined)
92
+ return match;
93
+ if (typeof resolved === "object" && resolved !== null)
94
+ return JSON.stringify(resolved);
95
+ return String(resolved);
96
+ });
97
+ }
98
+ /**
99
+ * Recursively resolve templates in any JSON-compatible value.
100
+ * - Strings: resolve {{...}} patterns
101
+ * - Objects: recurse into values
102
+ * - Arrays: recurse into elements
103
+ * - Primitives: return as-is
104
+ */
105
+ export function resolveTemplate(value, ctx, depth = 0) {
106
+ // Guard against infinite recursion from circular or deeply nested structures
107
+ if (depth > 20)
108
+ return value;
109
+ if (typeof value === "string") {
110
+ return resolveString(value, ctx);
111
+ }
112
+ if (Array.isArray(value)) {
113
+ return value.map((item) => resolveTemplate(item, ctx, depth + 1));
114
+ }
115
+ if (value !== null && typeof value === "object") {
116
+ const resolved = {};
117
+ for (const [k, v] of Object.entries(value)) {
118
+ resolved[k] = resolveTemplate(v, ctx, depth + 1);
119
+ }
120
+ return resolved;
121
+ }
122
+ return value;
123
+ }
124
+ /**
125
+ * Evaluate a simple condition expression.
126
+ * Supports: ==, !=, >, <, >=, <=, contains, !contains, exists, !exists
127
+ * Templates in the expression are resolved first.
128
+ *
129
+ * Examples:
130
+ * "{{steps.check.output.count}} > 10"
131
+ * "{{steps.fetch.output.status}} == 'active'"
132
+ * "{{trigger.type}} != 'test'"
133
+ */
134
+ export function evaluateCondition(expression, ctx) {
135
+ // Resolve all templates in the expression first
136
+ const resolved = resolveString(expression, ctx);
137
+ const expr = typeof resolved === 'object' && resolved !== null
138
+ ? JSON.stringify(resolved)
139
+ : String(resolved);
140
+ // exists / !exists checks
141
+ if (expr.includes(" exists")) {
142
+ const parts = expr.split(/\s+exists/);
143
+ const val = parts[0].trim();
144
+ const isNegated = expression.trim().startsWith("!");
145
+ const exists = val !== "" && val !== "undefined" && val !== "null" && !val.includes("{{");
146
+ return isNegated ? !exists : exists;
147
+ }
148
+ // P1 FIX: Use regex-based matching with word boundaries to prevent data containing
149
+ // operator strings (e.g., "value >= 10" in a field) from being misinterpreted
150
+ const operatorPatterns = [
151
+ [/\s!==\s/, "!=="],
152
+ [/\s===\s/, "==="],
153
+ [/\s!=\s/, "!="],
154
+ [/\s==\s/, "=="],
155
+ [/\s>=\s/, ">="],
156
+ [/\s<=\s/, "<="],
157
+ [/\s>\s/, ">"],
158
+ [/\s<\s/, "<"],
159
+ [/\scontains\s/, "contains"],
160
+ [/\s!contains\s/, "!contains"],
161
+ ];
162
+ for (const [pattern, op] of operatorPatterns) {
163
+ // Use the LAST match: resolved template data (left side) may contain
164
+ // operator-like strings, but the actual operator is the rightmost one.
165
+ const globalRe = new RegExp(pattern.source, "g");
166
+ let lastMatch = null;
167
+ let m;
168
+ while ((m = globalRe.exec(expr)) !== null)
169
+ lastMatch = m;
170
+ if (!lastMatch || lastMatch.index === undefined)
171
+ continue;
172
+ let left = expr.substring(0, lastMatch.index).trim();
173
+ let right = expr.substring(lastMatch.index + lastMatch[0].length).trim();
174
+ // Strip quotes from right side
175
+ if ((right.startsWith("'") && right.endsWith("'")) || (right.startsWith('"') && right.endsWith('"'))) {
176
+ right = right.slice(1, -1);
177
+ }
178
+ if ((left.startsWith("'") && left.endsWith("'")) || (left.startsWith('"') && left.endsWith('"'))) {
179
+ left = left.slice(1, -1);
180
+ }
181
+ const numLeft = Number(left);
182
+ const numRight = Number(right);
183
+ const bothNumeric = !isNaN(numLeft) && !isNaN(numRight) && left !== "" && right !== "";
184
+ switch (op.trim()) {
185
+ case "===":
186
+ case "==":
187
+ return bothNumeric ? numLeft === numRight : left === right;
188
+ case "!==":
189
+ case "!=":
190
+ return bothNumeric ? numLeft !== numRight : left !== right;
191
+ case ">":
192
+ return bothNumeric ? numLeft > numRight : left > right;
193
+ case "<":
194
+ return bothNumeric ? numLeft < numRight : left < right;
195
+ case ">=":
196
+ return bothNumeric ? numLeft >= numRight : left >= right;
197
+ case "<=":
198
+ return bothNumeric ? numLeft <= numRight : left <= right;
199
+ case "contains":
200
+ return left.includes(right);
201
+ case "!contains":
202
+ return !left.includes(right);
203
+ }
204
+ }
205
+ // Truthy/falsy check — if expression resolved to a value
206
+ if (expr === "true" || expr === "1")
207
+ return true;
208
+ if (expr === "false" || expr === "0" || expr === "" || expr === "null" || expr === "undefined")
209
+ return false;
210
+ // If there are still unresolved templates, condition is false
211
+ if (expr.includes("{{"))
212
+ return false;
213
+ // Non-empty string is truthy
214
+ return expr.length > 0;
215
+ }
@@ -0,0 +1,16 @@
1
+ /** Escape characters that could manipulate PostgREST/ILIKE filter syntax.
2
+ * Escapes ILIKE wildcards (%, _) and backslashes, plus strips commas and parens
3
+ * that could manipulate PostgREST operators. */
4
+ export declare function sanitizeFilterValue(val: string): string;
5
+ /** Group array items by a key, returning counts */
6
+ export declare function groupBy(arr: Record<string, unknown>[], key: string): Record<string, number>;
7
+ /** Escape a value for CSV output */
8
+ export declare function escapeCSV(val: unknown): string;
9
+ /** Fill {{key}} placeholders in a template string */
10
+ export declare function fillTemplate(template: string, data: Record<string, unknown>): string;
11
+ /** Extract concise metrics from tool results for audit logging (never full payloads). */
12
+ export declare function summarizeResult(toolName: string, action: string | undefined, data: unknown): Record<string, unknown>;
13
+ /** Timeout wrapper for tool execution with AbortSignal support.
14
+ * Creates an AbortController, races the promise against a timeout,
15
+ * and aborts the signal on timeout so underlying work (fetch etc.) can cancel. */
16
+ export declare function withTimeout<T>(promiseOrFactory: Promise<T> | ((signal: AbortSignal) => Promise<T>), ms: number, name: string): Promise<T>;