@iflow-mcp/jakeliume-webpeel 0.22.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 (547) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +313 -0
  3. package/dist/cache.d.ts +30 -0
  4. package/dist/cache.js +139 -0
  5. package/dist/cli/commands/auth.d.ts +5 -0
  6. package/dist/cli/commands/auth.js +411 -0
  7. package/dist/cli/commands/doctor.d.ts +37 -0
  8. package/dist/cli/commands/doctor.js +371 -0
  9. package/dist/cli/commands/fetch.d.ts +6 -0
  10. package/dist/cli/commands/fetch.js +1345 -0
  11. package/dist/cli/commands/guide.d.ts +2 -0
  12. package/dist/cli/commands/guide.js +183 -0
  13. package/dist/cli/commands/interact.d.ts +5 -0
  14. package/dist/cli/commands/interact.js +840 -0
  15. package/dist/cli/commands/jobs.d.ts +5 -0
  16. package/dist/cli/commands/jobs.js +997 -0
  17. package/dist/cli/commands/monitor.d.ts +12 -0
  18. package/dist/cli/commands/monitor.js +197 -0
  19. package/dist/cli/commands/observe.d.ts +12 -0
  20. package/dist/cli/commands/observe.js +158 -0
  21. package/dist/cli/commands/screenshot.d.ts +5 -0
  22. package/dist/cli/commands/screenshot.js +282 -0
  23. package/dist/cli/commands/search.d.ts +5 -0
  24. package/dist/cli/commands/search.js +1021 -0
  25. package/dist/cli/commands/setup.d.ts +13 -0
  26. package/dist/cli/commands/setup.js +244 -0
  27. package/dist/cli/commands/skill.d.ts +15 -0
  28. package/dist/cli/commands/skill.js +195 -0
  29. package/dist/cli/utils.d.ts +84 -0
  30. package/dist/cli/utils.js +806 -0
  31. package/dist/cli-auth.d.ts +75 -0
  32. package/dist/cli-auth.js +369 -0
  33. package/dist/cli.d.ts +17 -0
  34. package/dist/cli.js +99 -0
  35. package/dist/core/actions.d.ts +69 -0
  36. package/dist/core/actions.js +495 -0
  37. package/dist/core/agent.d.ts +98 -0
  38. package/dist/core/agent.js +558 -0
  39. package/dist/core/answer.d.ts +42 -0
  40. package/dist/core/answer.js +395 -0
  41. package/dist/core/application-tracker.d.ts +84 -0
  42. package/dist/core/application-tracker.js +184 -0
  43. package/dist/core/apply.d.ts +162 -0
  44. package/dist/core/apply.js +816 -0
  45. package/dist/core/auth-detection.d.ts +35 -0
  46. package/dist/core/auth-detection.js +358 -0
  47. package/dist/core/auto-extract.d.ts +82 -0
  48. package/dist/core/auto-extract.js +604 -0
  49. package/dist/core/auto-interact.d.ts +23 -0
  50. package/dist/core/auto-interact.js +246 -0
  51. package/dist/core/bm25-filter.d.ts +66 -0
  52. package/dist/core/bm25-filter.js +288 -0
  53. package/dist/core/branding.d.ts +54 -0
  54. package/dist/core/branding.js +234 -0
  55. package/dist/core/browser-fetch.d.ts +323 -0
  56. package/dist/core/browser-fetch.js +1600 -0
  57. package/dist/core/browser-pool.d.ts +91 -0
  58. package/dist/core/browser-pool.js +550 -0
  59. package/dist/core/budget.d.ts +42 -0
  60. package/dist/core/budget.js +324 -0
  61. package/dist/core/business-intel.d.ts +47 -0
  62. package/dist/core/business-intel.js +279 -0
  63. package/dist/core/cache.d.ts +13 -0
  64. package/dist/core/cache.js +121 -0
  65. package/dist/core/cf-worker-proxy.d.ts +32 -0
  66. package/dist/core/cf-worker-proxy.js +87 -0
  67. package/dist/core/challenge-detection.d.ts +26 -0
  68. package/dist/core/challenge-detection.js +468 -0
  69. package/dist/core/change-tracking.d.ts +75 -0
  70. package/dist/core/change-tracking.js +276 -0
  71. package/dist/core/chunker.d.ts +46 -0
  72. package/dist/core/chunker.js +249 -0
  73. package/dist/core/chunking.d.ts +42 -0
  74. package/dist/core/chunking.js +181 -0
  75. package/dist/core/circuit-breaker.d.ts +44 -0
  76. package/dist/core/circuit-breaker.js +85 -0
  77. package/dist/core/content-pruner.d.ts +47 -0
  78. package/dist/core/content-pruner.js +425 -0
  79. package/dist/core/cookie-cache.d.ts +60 -0
  80. package/dist/core/cookie-cache.js +163 -0
  81. package/dist/core/crawl-checkpoint.d.ts +54 -0
  82. package/dist/core/crawl-checkpoint.js +104 -0
  83. package/dist/core/crawler.d.ts +84 -0
  84. package/dist/core/crawler.js +349 -0
  85. package/dist/core/cross-verify.d.ts +27 -0
  86. package/dist/core/cross-verify.js +93 -0
  87. package/dist/core/deep-fetch.d.ts +74 -0
  88. package/dist/core/deep-fetch.js +405 -0
  89. package/dist/core/deep-research.d.ts +141 -0
  90. package/dist/core/deep-research.js +972 -0
  91. package/dist/core/design-analysis.d.ts +70 -0
  92. package/dist/core/design-analysis.js +490 -0
  93. package/dist/core/design-compare.d.ts +38 -0
  94. package/dist/core/design-compare.js +264 -0
  95. package/dist/core/diff.d.ts +61 -0
  96. package/dist/core/diff.js +289 -0
  97. package/dist/core/dns-cache.d.ts +20 -0
  98. package/dist/core/dns-cache.js +198 -0
  99. package/dist/core/documents.d.ts +23 -0
  100. package/dist/core/documents.js +123 -0
  101. package/dist/core/domain-memory.d.ts +66 -0
  102. package/dist/core/domain-memory.js +163 -0
  103. package/dist/core/domain-verify.d.ts +40 -0
  104. package/dist/core/domain-verify.js +379 -0
  105. package/dist/core/engine-ranker.d.ts +112 -0
  106. package/dist/core/engine-ranker.js +395 -0
  107. package/dist/core/extract-inline.d.ts +38 -0
  108. package/dist/core/extract-inline.js +215 -0
  109. package/dist/core/extract-listings.d.ts +38 -0
  110. package/dist/core/extract-listings.js +461 -0
  111. package/dist/core/extract.d.ts +9 -0
  112. package/dist/core/extract.js +139 -0
  113. package/dist/core/fetch-cache.d.ts +57 -0
  114. package/dist/core/fetch-cache.js +95 -0
  115. package/dist/core/fetcher.d.ts +13 -0
  116. package/dist/core/fetcher.js +12 -0
  117. package/dist/core/google-cache.d.ts +29 -0
  118. package/dist/core/google-cache.js +180 -0
  119. package/dist/core/google-serp-parser.d.ts +82 -0
  120. package/dist/core/google-serp-parser.js +287 -0
  121. package/dist/core/hotel-search.d.ts +122 -0
  122. package/dist/core/hotel-search.js +382 -0
  123. package/dist/core/http-fetch.d.ts +72 -0
  124. package/dist/core/http-fetch.js +820 -0
  125. package/dist/core/human.d.ts +175 -0
  126. package/dist/core/human.js +680 -0
  127. package/dist/core/image-caption.d.ts +44 -0
  128. package/dist/core/image-caption.js +271 -0
  129. package/dist/core/jobs.d.ts +75 -0
  130. package/dist/core/jobs.js +634 -0
  131. package/dist/core/json-ld.d.ts +15 -0
  132. package/dist/core/json-ld.js +617 -0
  133. package/dist/core/language-detect.d.ts +18 -0
  134. package/dist/core/language-detect.js +135 -0
  135. package/dist/core/links.d.ts +10 -0
  136. package/dist/core/links.js +44 -0
  137. package/dist/core/llm-extract.d.ts +71 -0
  138. package/dist/core/llm-extract.js +507 -0
  139. package/dist/core/llm-provider.d.ts +100 -0
  140. package/dist/core/llm-provider.js +702 -0
  141. package/dist/core/local-search.d.ts +60 -0
  142. package/dist/core/local-search.js +308 -0
  143. package/dist/core/logger.d.ts +28 -0
  144. package/dist/core/logger.js +104 -0
  145. package/dist/core/map.d.ts +33 -0
  146. package/dist/core/map.js +127 -0
  147. package/dist/core/markdown.d.ts +92 -0
  148. package/dist/core/markdown.js +809 -0
  149. package/dist/core/metadata.d.ts +34 -0
  150. package/dist/core/metadata.js +422 -0
  151. package/dist/core/observe.d.ts +113 -0
  152. package/dist/core/observe.js +395 -0
  153. package/dist/core/ocr.d.ts +12 -0
  154. package/dist/core/ocr.js +33 -0
  155. package/dist/core/paginate.d.ts +31 -0
  156. package/dist/core/paginate.js +106 -0
  157. package/dist/core/pdf.d.ts +8 -0
  158. package/dist/core/pdf.js +25 -0
  159. package/dist/core/peel-tls.d.ts +25 -0
  160. package/dist/core/peel-tls.js +220 -0
  161. package/dist/core/pipeline.d.ts +132 -0
  162. package/dist/core/pipeline.js +1666 -0
  163. package/dist/core/profiles.d.ts +61 -0
  164. package/dist/core/profiles.js +350 -0
  165. package/dist/core/prompt-guard.d.ts +30 -0
  166. package/dist/core/prompt-guard.js +119 -0
  167. package/dist/core/proxy-config.d.ts +90 -0
  168. package/dist/core/proxy-config.js +172 -0
  169. package/dist/core/quick-answer.d.ts +53 -0
  170. package/dist/core/quick-answer.js +833 -0
  171. package/dist/core/rate-governor.d.ts +80 -0
  172. package/dist/core/rate-governor.js +238 -0
  173. package/dist/core/readability.d.ts +57 -0
  174. package/dist/core/readability.js +533 -0
  175. package/dist/core/research.d.ts +66 -0
  176. package/dist/core/research.js +270 -0
  177. package/dist/core/retry.d.ts +60 -0
  178. package/dist/core/retry.js +119 -0
  179. package/dist/core/safe-browsing.d.ts +30 -0
  180. package/dist/core/safe-browsing.js +206 -0
  181. package/dist/core/schema-extraction.d.ts +66 -0
  182. package/dist/core/schema-extraction.js +352 -0
  183. package/dist/core/schema-postprocess.d.ts +32 -0
  184. package/dist/core/schema-postprocess.js +469 -0
  185. package/dist/core/schema-templates.d.ts +19 -0
  186. package/dist/core/schema-templates.js +143 -0
  187. package/dist/core/screenshot.d.ts +224 -0
  188. package/dist/core/screenshot.js +207 -0
  189. package/dist/core/search-engines.d.ts +25 -0
  190. package/dist/core/search-engines.js +182 -0
  191. package/dist/core/search-provider.d.ts +243 -0
  192. package/dist/core/search-provider.js +1629 -0
  193. package/dist/core/searxng-provider.d.ts +35 -0
  194. package/dist/core/searxng-provider.js +105 -0
  195. package/dist/core/selective-evidence.d.ts +151 -0
  196. package/dist/core/selective-evidence.js +389 -0
  197. package/dist/core/site-search.d.ts +44 -0
  198. package/dist/core/site-search.js +252 -0
  199. package/dist/core/sitemap.d.ts +23 -0
  200. package/dist/core/sitemap.js +105 -0
  201. package/dist/core/source-credibility.d.ts +29 -0
  202. package/dist/core/source-credibility.js +584 -0
  203. package/dist/core/source-scoring.d.ts +166 -0
  204. package/dist/core/source-scoring.js +396 -0
  205. package/dist/core/stemmer.d.ts +38 -0
  206. package/dist/core/stemmer.js +509 -0
  207. package/dist/core/strategies.d.ts +104 -0
  208. package/dist/core/strategies.js +1044 -0
  209. package/dist/core/strategy-hooks.d.ts +145 -0
  210. package/dist/core/strategy-hooks.js +74 -0
  211. package/dist/core/structured-extract.d.ts +43 -0
  212. package/dist/core/structured-extract.js +550 -0
  213. package/dist/core/summarize.d.ts +17 -0
  214. package/dist/core/summarize.js +78 -0
  215. package/dist/core/synonyms.d.ts +42 -0
  216. package/dist/core/synonyms.js +184 -0
  217. package/dist/core/system-monitor.d.ts +61 -0
  218. package/dist/core/system-monitor.js +133 -0
  219. package/dist/core/table-format.d.ts +30 -0
  220. package/dist/core/table-format.js +146 -0
  221. package/dist/core/threat-feeds.d.ts +23 -0
  222. package/dist/core/threat-feeds.js +104 -0
  223. package/dist/core/timing.d.ts +21 -0
  224. package/dist/core/timing.js +33 -0
  225. package/dist/core/transcript-export.d.ts +47 -0
  226. package/dist/core/transcript-export.js +107 -0
  227. package/dist/core/user-agents.d.ts +82 -0
  228. package/dist/core/user-agents.js +239 -0
  229. package/dist/core/vertical-search.d.ts +54 -0
  230. package/dist/core/vertical-search.js +158 -0
  231. package/dist/core/watch-manager.d.ts +175 -0
  232. package/dist/core/watch-manager.js +416 -0
  233. package/dist/core/watch.d.ts +101 -0
  234. package/dist/core/watch.js +389 -0
  235. package/dist/core/youtube.d.ts +130 -0
  236. package/dist/core/youtube.js +1175 -0
  237. package/dist/ee/challenge-re-export.d.ts +1 -0
  238. package/dist/ee/challenge-re-export.js +1 -0
  239. package/dist/ee/challenge-solver.d.ts +72 -0
  240. package/dist/ee/challenge-solver.js +720 -0
  241. package/dist/ee/domain-extractors.d.ts +8 -0
  242. package/dist/ee/domain-extractors.js +8 -0
  243. package/dist/ee/domain-intel.d.ts +16 -0
  244. package/dist/ee/domain-intel.js +133 -0
  245. package/dist/ee/extractors/allrecipes.d.ts +2 -0
  246. package/dist/ee/extractors/allrecipes.js +120 -0
  247. package/dist/ee/extractors/amazon.d.ts +2 -0
  248. package/dist/ee/extractors/amazon.js +78 -0
  249. package/dist/ee/extractors/arxiv.d.ts +2 -0
  250. package/dist/ee/extractors/arxiv.js +137 -0
  251. package/dist/ee/extractors/bestbuy.d.ts +2 -0
  252. package/dist/ee/extractors/bestbuy.js +78 -0
  253. package/dist/ee/extractors/carscom.d.ts +2 -0
  254. package/dist/ee/extractors/carscom.js +121 -0
  255. package/dist/ee/extractors/coingecko.d.ts +2 -0
  256. package/dist/ee/extractors/coingecko.js +134 -0
  257. package/dist/ee/extractors/craigslist.d.ts +2 -0
  258. package/dist/ee/extractors/craigslist.js +92 -0
  259. package/dist/ee/extractors/devto.d.ts +2 -0
  260. package/dist/ee/extractors/devto.js +135 -0
  261. package/dist/ee/extractors/ebay.d.ts +2 -0
  262. package/dist/ee/extractors/ebay.js +90 -0
  263. package/dist/ee/extractors/espn.d.ts +2 -0
  264. package/dist/ee/extractors/espn.js +260 -0
  265. package/dist/ee/extractors/etsy.d.ts +2 -0
  266. package/dist/ee/extractors/etsy.js +52 -0
  267. package/dist/ee/extractors/facebook.d.ts +2 -0
  268. package/dist/ee/extractors/facebook.js +46 -0
  269. package/dist/ee/extractors/github.d.ts +2 -0
  270. package/dist/ee/extractors/github.js +196 -0
  271. package/dist/ee/extractors/google-flights.d.ts +2 -0
  272. package/dist/ee/extractors/google-flights.js +176 -0
  273. package/dist/ee/extractors/hackernews.d.ts +2 -0
  274. package/dist/ee/extractors/hackernews.js +147 -0
  275. package/dist/ee/extractors/imdb.d.ts +2 -0
  276. package/dist/ee/extractors/imdb.js +172 -0
  277. package/dist/ee/extractors/index.d.ts +26 -0
  278. package/dist/ee/extractors/index.js +247 -0
  279. package/dist/ee/extractors/instagram.d.ts +2 -0
  280. package/dist/ee/extractors/instagram.js +102 -0
  281. package/dist/ee/extractors/kalshi.d.ts +2 -0
  282. package/dist/ee/extractors/kalshi.js +121 -0
  283. package/dist/ee/extractors/kayak-cars.d.ts +2 -0
  284. package/dist/ee/extractors/kayak-cars.js +270 -0
  285. package/dist/ee/extractors/linkedin.d.ts +2 -0
  286. package/dist/ee/extractors/linkedin.js +113 -0
  287. package/dist/ee/extractors/medium.d.ts +2 -0
  288. package/dist/ee/extractors/medium.js +130 -0
  289. package/dist/ee/extractors/news.d.ts +4 -0
  290. package/dist/ee/extractors/news.js +173 -0
  291. package/dist/ee/extractors/npm.d.ts +2 -0
  292. package/dist/ee/extractors/npm.js +86 -0
  293. package/dist/ee/extractors/pdf.d.ts +2 -0
  294. package/dist/ee/extractors/pdf.js +108 -0
  295. package/dist/ee/extractors/pinterest.d.ts +2 -0
  296. package/dist/ee/extractors/pinterest.js +34 -0
  297. package/dist/ee/extractors/polymarket.d.ts +2 -0
  298. package/dist/ee/extractors/polymarket.js +358 -0
  299. package/dist/ee/extractors/producthunt.d.ts +2 -0
  300. package/dist/ee/extractors/producthunt.js +88 -0
  301. package/dist/ee/extractors/pubmed.d.ts +2 -0
  302. package/dist/ee/extractors/pubmed.js +162 -0
  303. package/dist/ee/extractors/pypi.d.ts +2 -0
  304. package/dist/ee/extractors/pypi.js +80 -0
  305. package/dist/ee/extractors/reddit.d.ts +2 -0
  306. package/dist/ee/extractors/reddit.js +438 -0
  307. package/dist/ee/extractors/redfin.d.ts +2 -0
  308. package/dist/ee/extractors/redfin.js +156 -0
  309. package/dist/ee/extractors/semanticscholar.d.ts +2 -0
  310. package/dist/ee/extractors/semanticscholar.js +131 -0
  311. package/dist/ee/extractors/shared.d.ts +12 -0
  312. package/dist/ee/extractors/shared.js +76 -0
  313. package/dist/ee/extractors/soundcloud.d.ts +2 -0
  314. package/dist/ee/extractors/soundcloud.js +34 -0
  315. package/dist/ee/extractors/sportsbetting.d.ts +2 -0
  316. package/dist/ee/extractors/sportsbetting.js +37 -0
  317. package/dist/ee/extractors/spotify.d.ts +2 -0
  318. package/dist/ee/extractors/spotify.js +34 -0
  319. package/dist/ee/extractors/stackoverflow.d.ts +2 -0
  320. package/dist/ee/extractors/stackoverflow.js +61 -0
  321. package/dist/ee/extractors/substack.d.ts +2 -0
  322. package/dist/ee/extractors/substack.js +115 -0
  323. package/dist/ee/extractors/substackroot.d.ts +2 -0
  324. package/dist/ee/extractors/substackroot.js +46 -0
  325. package/dist/ee/extractors/tiktok.d.ts +2 -0
  326. package/dist/ee/extractors/tiktok.js +29 -0
  327. package/dist/ee/extractors/tradingview.d.ts +2 -0
  328. package/dist/ee/extractors/tradingview.js +182 -0
  329. package/dist/ee/extractors/twitch.d.ts +2 -0
  330. package/dist/ee/extractors/twitch.js +36 -0
  331. package/dist/ee/extractors/twitter.d.ts +2 -0
  332. package/dist/ee/extractors/twitter.js +327 -0
  333. package/dist/ee/extractors/types.d.ts +14 -0
  334. package/dist/ee/extractors/types.js +1 -0
  335. package/dist/ee/extractors/walmart.d.ts +2 -0
  336. package/dist/ee/extractors/walmart.js +50 -0
  337. package/dist/ee/extractors/weather.d.ts +2 -0
  338. package/dist/ee/extractors/weather.js +133 -0
  339. package/dist/ee/extractors/wikipedia.d.ts +4 -0
  340. package/dist/ee/extractors/wikipedia.js +235 -0
  341. package/dist/ee/extractors/yelp.d.ts +2 -0
  342. package/dist/ee/extractors/yelp.js +216 -0
  343. package/dist/ee/extractors/youtube.d.ts +2 -0
  344. package/dist/ee/extractors/youtube.js +189 -0
  345. package/dist/ee/extractors/zillow.d.ts +54 -0
  346. package/dist/ee/extractors/zillow.js +247 -0
  347. package/dist/ee/extractors-re-export.d.ts +1 -0
  348. package/dist/ee/extractors-re-export.js +1 -0
  349. package/dist/ee/premium-hooks.d.ts +20 -0
  350. package/dist/ee/premium-hooks.js +50 -0
  351. package/dist/ee/spa-detection.d.ts +2 -0
  352. package/dist/ee/spa-detection.js +2 -0
  353. package/dist/ee/stability.d.ts +4 -0
  354. package/dist/ee/stability.js +29 -0
  355. package/dist/ee/swr-cache.d.ts +14 -0
  356. package/dist/ee/swr-cache.js +34 -0
  357. package/dist/index.d.ts +143 -0
  358. package/dist/index.js +291 -0
  359. package/dist/integrations/index.d.ts +2 -0
  360. package/dist/integrations/index.js +2 -0
  361. package/dist/integrations/langchain.d.ts +64 -0
  362. package/dist/integrations/langchain.js +115 -0
  363. package/dist/integrations/llamaindex.d.ts +50 -0
  364. package/dist/integrations/llamaindex.js +91 -0
  365. package/dist/mcp/handlers/act.d.ts +5 -0
  366. package/dist/mcp/handlers/act.js +34 -0
  367. package/dist/mcp/handlers/definitions.d.ts +6 -0
  368. package/dist/mcp/handlers/definitions.js +395 -0
  369. package/dist/mcp/handlers/extract.d.ts +7 -0
  370. package/dist/mcp/handlers/extract.js +135 -0
  371. package/dist/mcp/handlers/fetch.d.ts +6 -0
  372. package/dist/mcp/handlers/fetch.js +98 -0
  373. package/dist/mcp/handlers/find.d.ts +5 -0
  374. package/dist/mcp/handlers/find.js +137 -0
  375. package/dist/mcp/handlers/index.d.ts +13 -0
  376. package/dist/mcp/handlers/index.js +63 -0
  377. package/dist/mcp/handlers/legacy.d.ts +25 -0
  378. package/dist/mcp/handlers/legacy.js +450 -0
  379. package/dist/mcp/handlers/meta.d.ts +6 -0
  380. package/dist/mcp/handlers/meta.js +40 -0
  381. package/dist/mcp/handlers/monitor.d.ts +5 -0
  382. package/dist/mcp/handlers/monitor.js +41 -0
  383. package/dist/mcp/handlers/observe.d.ts +8 -0
  384. package/dist/mcp/handlers/observe.js +37 -0
  385. package/dist/mcp/handlers/read.d.ts +6 -0
  386. package/dist/mcp/handlers/read.js +78 -0
  387. package/dist/mcp/handlers/see.d.ts +5 -0
  388. package/dist/mcp/handlers/see.js +75 -0
  389. package/dist/mcp/handlers/types.d.ts +29 -0
  390. package/dist/mcp/handlers/types.js +28 -0
  391. package/dist/mcp/server.d.ts +7 -0
  392. package/dist/mcp/server.js +108 -0
  393. package/dist/mcp/smart-router.d.ts +23 -0
  394. package/dist/mcp/smart-router.js +178 -0
  395. package/dist/server/app.d.ts +14 -0
  396. package/dist/server/app.js +632 -0
  397. package/dist/server/auth-store.d.ts +28 -0
  398. package/dist/server/auth-store.js +88 -0
  399. package/dist/server/bull-queues.d.ts +60 -0
  400. package/dist/server/bull-queues.js +90 -0
  401. package/dist/server/email-service.d.ts +55 -0
  402. package/dist/server/email-service.js +291 -0
  403. package/dist/server/job-queue.d.ts +100 -0
  404. package/dist/server/job-queue.js +145 -0
  405. package/dist/server/logger.d.ts +10 -0
  406. package/dist/server/logger.js +37 -0
  407. package/dist/server/middleware/audit-log.d.ts +14 -0
  408. package/dist/server/middleware/audit-log.js +73 -0
  409. package/dist/server/middleware/auth.d.ts +35 -0
  410. package/dist/server/middleware/auth.js +225 -0
  411. package/dist/server/middleware/rate-limit.d.ts +50 -0
  412. package/dist/server/middleware/rate-limit.js +270 -0
  413. package/dist/server/middleware/scope-guard.d.ts +25 -0
  414. package/dist/server/middleware/scope-guard.js +45 -0
  415. package/dist/server/middleware/url-validator.d.ts +15 -0
  416. package/dist/server/middleware/url-validator.js +201 -0
  417. package/dist/server/openapi.yaml +6418 -0
  418. package/dist/server/pg-auth-store.d.ts +146 -0
  419. package/dist/server/pg-auth-store.js +576 -0
  420. package/dist/server/pg-job-queue.d.ts +59 -0
  421. package/dist/server/pg-job-queue.js +375 -0
  422. package/dist/server/routes/activity.d.ts +6 -0
  423. package/dist/server/routes/activity.js +79 -0
  424. package/dist/server/routes/admin-active.d.ts +7 -0
  425. package/dist/server/routes/admin-active.js +120 -0
  426. package/dist/server/routes/admin-stats.d.ts +7 -0
  427. package/dist/server/routes/admin-stats.js +176 -0
  428. package/dist/server/routes/agent.d.ts +24 -0
  429. package/dist/server/routes/agent.js +480 -0
  430. package/dist/server/routes/answer.d.ts +5 -0
  431. package/dist/server/routes/answer.js +125 -0
  432. package/dist/server/routes/ask.d.ts +28 -0
  433. package/dist/server/routes/ask.js +295 -0
  434. package/dist/server/routes/batch.d.ts +6 -0
  435. package/dist/server/routes/batch.js +493 -0
  436. package/dist/server/routes/cache-warm.d.ts +25 -0
  437. package/dist/server/routes/cache-warm.js +212 -0
  438. package/dist/server/routes/cli-usage.d.ts +6 -0
  439. package/dist/server/routes/cli-usage.js +127 -0
  440. package/dist/server/routes/compat.d.ts +23 -0
  441. package/dist/server/routes/compat.js +652 -0
  442. package/dist/server/routes/crawl.d.ts +13 -0
  443. package/dist/server/routes/crawl.js +287 -0
  444. package/dist/server/routes/deep-fetch.d.ts +8 -0
  445. package/dist/server/routes/deep-fetch.js +57 -0
  446. package/dist/server/routes/deep-research.d.ts +11 -0
  447. package/dist/server/routes/deep-research.js +232 -0
  448. package/dist/server/routes/demo.d.ts +24 -0
  449. package/dist/server/routes/demo.js +517 -0
  450. package/dist/server/routes/do.d.ts +8 -0
  451. package/dist/server/routes/do.js +72 -0
  452. package/dist/server/routes/extract.d.ts +14 -0
  453. package/dist/server/routes/extract.js +325 -0
  454. package/dist/server/routes/feed.d.ts +15 -0
  455. package/dist/server/routes/feed.js +311 -0
  456. package/dist/server/routes/fetch-queue.d.ts +13 -0
  457. package/dist/server/routes/fetch-queue.js +357 -0
  458. package/dist/server/routes/fetch.d.ts +7 -0
  459. package/dist/server/routes/fetch.js +1274 -0
  460. package/dist/server/routes/go.d.ts +14 -0
  461. package/dist/server/routes/go.js +81 -0
  462. package/dist/server/routes/health.d.ts +11 -0
  463. package/dist/server/routes/health.js +141 -0
  464. package/dist/server/routes/jobs.d.ts +7 -0
  465. package/dist/server/routes/jobs.js +574 -0
  466. package/dist/server/routes/map.d.ts +11 -0
  467. package/dist/server/routes/map.js +116 -0
  468. package/dist/server/routes/mcp.d.ts +14 -0
  469. package/dist/server/routes/mcp.js +197 -0
  470. package/dist/server/routes/metrics.d.ts +37 -0
  471. package/dist/server/routes/metrics.js +149 -0
  472. package/dist/server/routes/oauth.d.ts +9 -0
  473. package/dist/server/routes/oauth.js +396 -0
  474. package/dist/server/routes/playground.d.ts +17 -0
  475. package/dist/server/routes/playground.js +283 -0
  476. package/dist/server/routes/reader.d.ts +18 -0
  477. package/dist/server/routes/reader.js +192 -0
  478. package/dist/server/routes/research.d.ts +14 -0
  479. package/dist/server/routes/research.js +482 -0
  480. package/dist/server/routes/screenshot.d.ts +22 -0
  481. package/dist/server/routes/screenshot.js +820 -0
  482. package/dist/server/routes/search.d.ts +6 -0
  483. package/dist/server/routes/search.js +874 -0
  484. package/dist/server/routes/session.d.ts +17 -0
  485. package/dist/server/routes/session.js +548 -0
  486. package/dist/server/routes/share.d.ts +18 -0
  487. package/dist/server/routes/share.js +462 -0
  488. package/dist/server/routes/smart-search/handlers/cars.d.ts +2 -0
  489. package/dist/server/routes/smart-search/handlers/cars.js +102 -0
  490. package/dist/server/routes/smart-search/handlers/flights.d.ts +2 -0
  491. package/dist/server/routes/smart-search/handlers/flights.js +72 -0
  492. package/dist/server/routes/smart-search/handlers/general.d.ts +13 -0
  493. package/dist/server/routes/smart-search/handlers/general.js +717 -0
  494. package/dist/server/routes/smart-search/handlers/hotels.d.ts +2 -0
  495. package/dist/server/routes/smart-search/handlers/hotels.js +88 -0
  496. package/dist/server/routes/smart-search/handlers/products.d.ts +2 -0
  497. package/dist/server/routes/smart-search/handlers/products.js +1309 -0
  498. package/dist/server/routes/smart-search/handlers/rental.d.ts +2 -0
  499. package/dist/server/routes/smart-search/handlers/rental.js +154 -0
  500. package/dist/server/routes/smart-search/handlers/restaurants.d.ts +2 -0
  501. package/dist/server/routes/smart-search/handlers/restaurants.js +225 -0
  502. package/dist/server/routes/smart-search/handlers/transit-verdict.d.ts +41 -0
  503. package/dist/server/routes/smart-search/handlers/transit-verdict.js +224 -0
  504. package/dist/server/routes/smart-search/index.d.ts +19 -0
  505. package/dist/server/routes/smart-search/index.js +546 -0
  506. package/dist/server/routes/smart-search/intent.d.ts +3 -0
  507. package/dist/server/routes/smart-search/intent.js +264 -0
  508. package/dist/server/routes/smart-search/llm.d.ts +16 -0
  509. package/dist/server/routes/smart-search/llm.js +70 -0
  510. package/dist/server/routes/smart-search/sources/reddit.d.ts +18 -0
  511. package/dist/server/routes/smart-search/sources/reddit.js +34 -0
  512. package/dist/server/routes/smart-search/sources/yelp.d.ts +25 -0
  513. package/dist/server/routes/smart-search/sources/yelp.js +171 -0
  514. package/dist/server/routes/smart-search/sources/youtube.d.ts +8 -0
  515. package/dist/server/routes/smart-search/sources/youtube.js +9 -0
  516. package/dist/server/routes/smart-search/types.d.ts +81 -0
  517. package/dist/server/routes/smart-search/types.js +1 -0
  518. package/dist/server/routes/smart-search/utils.d.ts +20 -0
  519. package/dist/server/routes/smart-search/utils.js +146 -0
  520. package/dist/server/routes/stats.d.ts +6 -0
  521. package/dist/server/routes/stats.js +71 -0
  522. package/dist/server/routes/stripe.d.ts +15 -0
  523. package/dist/server/routes/stripe.js +296 -0
  524. package/dist/server/routes/transcript-export.d.ts +10 -0
  525. package/dist/server/routes/transcript-export.js +178 -0
  526. package/dist/server/routes/usage.d.ts +9 -0
  527. package/dist/server/routes/usage.js +279 -0
  528. package/dist/server/routes/users.d.ts +8 -0
  529. package/dist/server/routes/users.js +1867 -0
  530. package/dist/server/routes/watch.d.ts +15 -0
  531. package/dist/server/routes/watch.js +309 -0
  532. package/dist/server/routes/webhooks.d.ts +26 -0
  533. package/dist/server/routes/webhooks.js +170 -0
  534. package/dist/server/routes/youtube.d.ts +6 -0
  535. package/dist/server/routes/youtube.js +130 -0
  536. package/dist/server/sentry.d.ts +14 -0
  537. package/dist/server/sentry.js +104 -0
  538. package/dist/server/types.d.ts +15 -0
  539. package/dist/server/types.js +7 -0
  540. package/dist/server/utils/response.d.ts +44 -0
  541. package/dist/server/utils/response.js +69 -0
  542. package/dist/server/utils/sse.d.ts +22 -0
  543. package/dist/server/utils/sse.js +38 -0
  544. package/dist/types.d.ts +552 -0
  545. package/dist/types.js +39 -0
  546. package/llms.txt +105 -0
  547. package/package.json +189 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Redis-backed rate limiting middleware.
3
+ *
4
+ * Uses `rate-limiter-flexible` for atomic Redis operations — works correctly
5
+ * across all 6 K8s API pods (shared state via Redis).
6
+ *
7
+ * Falls back to in-memory when REDIS_URL is not set (CLI/local dev).
8
+ *
9
+ * Features:
10
+ * - Per-key (API key or IP) rate limiting
11
+ * - Tier-based limits: free=50/hr, pro=100/hr, max=500/hr
12
+ * - Route-based cost weighting: crawl=5x, render=3x, batch/screenshot=2x
13
+ * - Per-IP rate limiting ON TOP of API key limits (abuse prevention)
14
+ * - Exempt paths for health/docs endpoints
15
+ */
16
+ import '../types.js';
17
+ // ─── Tier burst limits ───────────────────────────────────────────────────────
18
+ const TIER_BURST_LIMITS = {
19
+ free: 50,
20
+ pro: 100,
21
+ max: 500,
22
+ admin: 999999,
23
+ };
24
+ /** Global per-IP limit regardless of API key (prevents shared-key abuse) */
25
+ const GLOBAL_IP_LIMIT = 1000; // per hour
26
+ // ─── Exempt paths ────────────────────────────────────────────────────────────
27
+ const EXEMPT_PATHS = [
28
+ '/health',
29
+ '/ready',
30
+ '/openapi.json',
31
+ '/openapi.yaml',
32
+ '/docs',
33
+ '/v1/usage',
34
+ '/v1/me',
35
+ '/v1/keys',
36
+ '/v1/activity',
37
+ '/v1/stats',
38
+ ];
39
+ // ─── RateLimiter class (Redis or in-memory) ──────────────────────────────────
40
+ export class RateLimiter {
41
+ keyLimiter; // RateLimiterRedis or RateLimiterMemory
42
+ ipLimiter; // separate limiter for per-IP limits
43
+ windowMs;
44
+ initialized = false;
45
+ initPromise;
46
+ constructor(windowMs = 3_600_000) {
47
+ this.windowMs = windowMs;
48
+ this.initPromise = this.init();
49
+ }
50
+ async init() {
51
+ const duration = Math.floor(this.windowMs / 1000); // seconds
52
+ try {
53
+ // Try Redis first
54
+ const redisUrl = process.env.REDIS_URL;
55
+ if (redisUrl) {
56
+ // Dynamic import to avoid hard dependency
57
+ const [{ RateLimiterRedis }, IoRedisModule] = await Promise.all([
58
+ import('rate-limiter-flexible'),
59
+ import('ioredis'),
60
+ ]);
61
+ const IoRedis = IoRedisModule.default ?? IoRedisModule;
62
+ const parsed = new URL(redisUrl);
63
+ const redisClient = new IoRedis({
64
+ host: parsed.hostname,
65
+ port: parseInt(parsed.port || '6379', 10),
66
+ password: process.env.REDIS_PASSWORD || undefined,
67
+ db: parseInt(parsed.pathname?.slice(1) || '0', 10) || 0,
68
+ enableOfflineQueue: false,
69
+ maxRetriesPerRequest: 1,
70
+ lazyConnect: true,
71
+ });
72
+ await redisClient.connect();
73
+ // Key-based limiter (per API key or unauthenticated IP)
74
+ this.keyLimiter = new RateLimiterRedis({
75
+ storeClient: redisClient,
76
+ keyPrefix: 'rl:key',
77
+ points: 500, // max tier limit — actual check done per-tier
78
+ duration,
79
+ blockDuration: 0, // don't block, just check
80
+ });
81
+ // IP-based limiter (global per-IP cap, on top of key limits)
82
+ this.ipLimiter = new RateLimiterRedis({
83
+ storeClient: redisClient,
84
+ keyPrefix: 'rl:ip',
85
+ points: GLOBAL_IP_LIMIT,
86
+ duration,
87
+ blockDuration: 0,
88
+ });
89
+ console.log('[rate-limit] Redis-backed rate limiting active (shared across pods)');
90
+ this.initialized = true;
91
+ return;
92
+ }
93
+ }
94
+ catch (err) {
95
+ console.warn('[rate-limit] Failed to init Redis rate limiter, falling back to in-memory:', err.message);
96
+ }
97
+ // Fallback: in-memory
98
+ const { RateLimiterMemory } = await import('rate-limiter-flexible');
99
+ this.keyLimiter = new RateLimiterMemory({
100
+ keyPrefix: 'rl:key',
101
+ points: 500,
102
+ duration: Math.floor(this.windowMs / 1000),
103
+ });
104
+ this.ipLimiter = new RateLimiterMemory({
105
+ keyPrefix: 'rl:ip',
106
+ points: GLOBAL_IP_LIMIT,
107
+ duration: Math.floor(this.windowMs / 1000),
108
+ });
109
+ console.log('[rate-limit] In-memory rate limiting active (single-pod only)');
110
+ this.initialized = true;
111
+ }
112
+ async waitForInit() {
113
+ if (!this.initialized)
114
+ await this.initPromise;
115
+ }
116
+ /**
117
+ * Check if request is allowed.
118
+ * Returns { allowed, remaining, retryAfter? }
119
+ */
120
+ async checkLimit(identifier, limit, cost = 1) {
121
+ await this.waitForInit();
122
+ try {
123
+ const res = await this.keyLimiter.consume(identifier, cost);
124
+ // rate-limiter-flexible tracks points consumed; we need to check against the per-tier limit
125
+ const consumed = res.consumedPoints;
126
+ if (consumed > limit) {
127
+ // Over the tier limit — reject
128
+ const msBeforeNext = res.msBeforeNext;
129
+ return {
130
+ allowed: false,
131
+ remaining: 0,
132
+ retryAfter: Math.ceil(msBeforeNext / 1000),
133
+ };
134
+ }
135
+ return {
136
+ allowed: true,
137
+ remaining: Math.max(0, limit - consumed),
138
+ };
139
+ }
140
+ catch (rejRes) {
141
+ // RateLimiterRes when rate limited
142
+ if (rejRes && rejRes.msBeforeNext !== undefined) {
143
+ return {
144
+ allowed: false,
145
+ remaining: 0,
146
+ retryAfter: Math.ceil(rejRes.msBeforeNext / 1000),
147
+ };
148
+ }
149
+ // Unexpected error — allow through (fail-open)
150
+ console.error('[rate-limit] Error checking rate limit:', rejRes);
151
+ return { allowed: true, remaining: limit };
152
+ }
153
+ }
154
+ /**
155
+ * Check per-IP limit (global cap regardless of API key).
156
+ */
157
+ async checkIpLimit(ip, cost = 1) {
158
+ await this.waitForInit();
159
+ try {
160
+ const res = await this.ipLimiter.consume(ip, cost);
161
+ return {
162
+ allowed: true,
163
+ remaining: Math.max(0, GLOBAL_IP_LIMIT - res.consumedPoints),
164
+ };
165
+ }
166
+ catch (rejRes) {
167
+ if (rejRes && rejRes.msBeforeNext !== undefined) {
168
+ return {
169
+ allowed: false,
170
+ remaining: 0,
171
+ retryAfter: Math.ceil(rejRes.msBeforeNext / 1000),
172
+ };
173
+ }
174
+ return { allowed: true, remaining: GLOBAL_IP_LIMIT };
175
+ }
176
+ }
177
+ /**
178
+ * Backward compat: cleanup is no-op for Redis (TTL handles it).
179
+ * Kept for in-memory fallback and for app.ts which calls it on an interval.
180
+ */
181
+ cleanup() {
182
+ // No-op for Redis; RateLimiterMemory handles its own cleanup
183
+ }
184
+ }
185
+ // ─── Middleware ───────────────────────────────────────────────────────────────
186
+ export function createRateLimitMiddleware(limiter) {
187
+ return async (req, res, next) => {
188
+ try {
189
+ // Skip rate limiting for exempt endpoints
190
+ if (EXEMPT_PATHS.some(p => req.path === p || req.path.startsWith(p + '/'))) {
191
+ return next();
192
+ }
193
+ // Resolve real client IP (Cloudflare → x-forwarded-for → x-real-ip → req.ip)
194
+ const forwardedFor = req.headers['x-forwarded-for'];
195
+ const firstForwardedIp = typeof forwardedFor === 'string'
196
+ ? forwardedFor.split(',')[0].trim()
197
+ : Array.isArray(forwardedFor) ? forwardedFor[0] : undefined;
198
+ const clientIp = req.headers['cf-connecting-ip']
199
+ || firstForwardedIp
200
+ || req.headers['x-real-ip']
201
+ || req.ip
202
+ || 'unknown';
203
+ // Key-based identifier: prefer API key, fall back to IP
204
+ const keyIdentifier = req.auth?.keyInfo?.key || `ip:${clientIp}`;
205
+ // Tier-based limit
206
+ const tier = req.auth?.tier || 'free';
207
+ const limit = TIER_BURST_LIMITS[tier] || 50;
208
+ // Route-based cost weighting
209
+ let cost = 1;
210
+ const path = req.path;
211
+ if (path.includes('/crawl') || path.includes('/map'))
212
+ cost = 5;
213
+ else if (path.includes('/batch'))
214
+ cost = 2;
215
+ else if (path.includes('/screenshot'))
216
+ cost = 2;
217
+ else if (req.query.render === 'true' || req.body?.render === true)
218
+ cost = 3;
219
+ // Check 1: Per-key rate limit
220
+ const keyResult = await limiter.checkLimit(keyIdentifier, limit, cost);
221
+ // Check 2: Per-IP rate limit (on top of key limit, prevents shared-key abuse)
222
+ const ipResult = await limiter.checkIpLimit(clientIp, cost);
223
+ // Use the more restrictive result
224
+ const allowed = keyResult.allowed && ipResult.allowed;
225
+ const remaining = Math.min(keyResult.remaining, ipResult.remaining);
226
+ const retryAfter = !allowed
227
+ ? Math.max(keyResult.retryAfter || 0, ipResult.retryAfter || 0)
228
+ : undefined;
229
+ // Set rate limit headers
230
+ const now = Date.now();
231
+ const resetTimestamp = Math.ceil((now + limiter.windowMs) / 1000);
232
+ res.setHeader('X-RateLimit-Limit', limit.toString());
233
+ res.setHeader('X-RateLimit-Remaining', Math.max(0, remaining).toString());
234
+ res.setHeader('X-RateLimit-Reset', resetTimestamp.toString());
235
+ if (req.auth?.tier) {
236
+ res.setHeader('X-WebPeel-Plan', req.auth.tier);
237
+ }
238
+ if (!allowed) {
239
+ const retryAfterSecs = retryAfter || 60;
240
+ res.setHeader('Retry-After', retryAfterSecs.toString());
241
+ const upgradeHint = tier === 'free'
242
+ ? ' Upgrade to Pro ($9/mo) for 100/hr burst limit → https://webpeel.dev/pricing'
243
+ : tier === 'pro'
244
+ ? ' Upgrade to Max ($29/mo) for 500/hr burst limit → https://webpeel.dev/pricing'
245
+ : '';
246
+ // Determine which limit was hit
247
+ const reason = !keyResult.allowed
248
+ ? `Hourly rate limit exceeded (${limit} requests/hr on ${tier} plan)`
249
+ : `Global IP rate limit exceeded (${GLOBAL_IP_LIMIT} requests/hr)`;
250
+ res.status(429).json({
251
+ success: false,
252
+ error: {
253
+ type: 'rate_limited',
254
+ message: `${reason}. Try again in ${retryAfterSecs}s.`,
255
+ hint: `Retry after ${retryAfterSecs} seconds.${upgradeHint}`,
256
+ docs: 'https://webpeel.dev/docs/errors#rate-limited',
257
+ },
258
+ metadata: { requestId: req.requestId },
259
+ });
260
+ return;
261
+ }
262
+ next();
263
+ }
264
+ catch (_error) {
265
+ // Fail-open: if rate limiter errors, allow the request through
266
+ console.error('[rate-limit] Middleware error, failing open:', _error);
267
+ next();
268
+ }
269
+ };
270
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Scope enforcement middleware for API key permission scoping.
3
+ *
4
+ * Keys have one of three scopes:
5
+ * 'full' — all endpoints (default)
6
+ * 'read' — read/fetch operations only
7
+ * 'restricted' — /v1/scrape only (for limited sharing)
8
+ *
9
+ * JWT-authenticated requests (dashboard sessions) bypass scope enforcement:
10
+ * req.keyScope is undefined for JWT requests, which are always allowed through.
11
+ */
12
+ import { Request, Response, NextFunction } from 'express';
13
+ import { KeyScope } from '../pg-auth-store.js';
14
+ /**
15
+ * Middleware factory that enforces API key scope.
16
+ * Pass the set of scopes that are permitted to access the guarded route.
17
+ *
18
+ * @example
19
+ * // Only full-access keys may manage billing:
20
+ * router.post('/v1/billing', requireScope('full'), handler);
21
+ *
22
+ * // Read and full keys may scrape:
23
+ * app.use('/v1/scrape', requireScope('full', 'read', 'restricted'), scrapeRouter);
24
+ */
25
+ export declare function requireScope(...allowedScopes: KeyScope[]): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Scope enforcement middleware for API key permission scoping.
3
+ *
4
+ * Keys have one of three scopes:
5
+ * 'full' — all endpoints (default)
6
+ * 'read' — read/fetch operations only
7
+ * 'restricted' — /v1/scrape only (for limited sharing)
8
+ *
9
+ * JWT-authenticated requests (dashboard sessions) bypass scope enforcement:
10
+ * req.keyScope is undefined for JWT requests, which are always allowed through.
11
+ */
12
+ /**
13
+ * Middleware factory that enforces API key scope.
14
+ * Pass the set of scopes that are permitted to access the guarded route.
15
+ *
16
+ * @example
17
+ * // Only full-access keys may manage billing:
18
+ * router.post('/v1/billing', requireScope('full'), handler);
19
+ *
20
+ * // Read and full keys may scrape:
21
+ * app.use('/v1/scrape', requireScope('full', 'read', 'restricted'), scrapeRouter);
22
+ */
23
+ export function requireScope(...allowedScopes) {
24
+ return (req, res, next) => {
25
+ // JWT sessions (req.keyScope === undefined) always pass through.
26
+ // Scope enforcement only applies to API key requests.
27
+ if (req.keyScope === undefined) {
28
+ return next();
29
+ }
30
+ if (!allowedScopes.includes(req.keyScope)) {
31
+ res.status(403).json({
32
+ success: false,
33
+ error: {
34
+ type: 'insufficient_scope',
35
+ message: `This API key has '${req.keyScope}' scope. This endpoint requires: ${allowedScopes.join(' or ')}.`,
36
+ docs: 'https://webpeel.dev/docs/authentication#scopes',
37
+ hint: 'Create a new API key with the required scope in your dashboard.',
38
+ },
39
+ requestId: req.requestId,
40
+ });
41
+ return;
42
+ }
43
+ next();
44
+ };
45
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * URL validation middleware to prevent SSRF attacks
3
+ * Validates URLs BEFORE any network request is made
4
+ */
5
+ /**
6
+ * Validate URL to prevent SSRF attacks
7
+ * Blocks localhost, private IPs, link-local addresses, and non-HTTP(S) protocols
8
+ */
9
+ export declare function validateUrlForSSRF(urlString: string): void;
10
+ /**
11
+ * SSRF Error class
12
+ */
13
+ export declare class SSRFError extends Error {
14
+ constructor(message: string);
15
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * URL validation middleware to prevent SSRF attacks
3
+ * Validates URLs BEFORE any network request is made
4
+ */
5
+ /**
6
+ * Validate URL to prevent SSRF attacks
7
+ * Blocks localhost, private IPs, link-local addresses, and non-HTTP(S) protocols
8
+ */
9
+ export function validateUrlForSSRF(urlString) {
10
+ // Parse URL
11
+ let url;
12
+ try {
13
+ url = new URL(urlString);
14
+ }
15
+ catch {
16
+ throw new Error('Invalid URL format');
17
+ }
18
+ // Only allow HTTP(S)
19
+ if (!['http:', 'https:'].includes(url.protocol)) {
20
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
21
+ }
22
+ const hostname = url.hostname.toLowerCase();
23
+ // Block localhost patterns
24
+ const localhostPatterns = ['localhost', '0.0.0.0'];
25
+ if (localhostPatterns.some(pattern => hostname === pattern || hostname.endsWith('.' + pattern))) {
26
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
27
+ }
28
+ // SECURITY: Block well-known cloud metadata service hostnames.
29
+ // These hostnames resolve to link-local IPs (169.254.x.x) which are blocked
30
+ // by IP, but hostname-level blocking provides defense-in-depth against DNS
31
+ // rebinding attacks where a domain transiently resolves to a valid IP during
32
+ // validation, then resolves to a private IP for the actual fetch.
33
+ const metadataHostnames = [
34
+ 'metadata.google.internal', // GCP: resolves to 169.254.169.254
35
+ 'metadata.goog', // GCP alternate
36
+ 'metadata.internal', // Generic internal
37
+ 'instance-data.ec2.internal', // AWS alternate
38
+ 'computeMetadata', // Partial GCP hostname
39
+ ];
40
+ if (metadataHostnames.some(m => hostname === m || hostname.endsWith('.' + m))) {
41
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
42
+ }
43
+ // Parse and validate IP addresses
44
+ const ipv4Info = parseIPv4(hostname);
45
+ if (ipv4Info) {
46
+ validateIPv4ForSSRF(ipv4Info);
47
+ }
48
+ // Validate IPv6
49
+ if (hostname.includes(':')) {
50
+ validateIPv6ForSSRF(hostname);
51
+ }
52
+ }
53
+ /**
54
+ * SSRF Error class
55
+ */
56
+ export class SSRFError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = 'SSRFError';
60
+ }
61
+ }
62
+ /**
63
+ * Parse IPv4 address in any format (dotted, hex, octal, decimal)
64
+ */
65
+ function parseIPv4(hostname) {
66
+ const cleaned = hostname.replace(/^\[|\]$/g, '');
67
+ // Standard dotted notation: 192.168.1.1
68
+ const dottedRegex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
69
+ const dottedMatch = cleaned.match(dottedRegex);
70
+ if (dottedMatch) {
71
+ const octets = dottedMatch.slice(1).map(Number);
72
+ if (octets.every(o => o >= 0 && o <= 255)) {
73
+ return octets;
74
+ }
75
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
76
+ }
77
+ // Hex notation: 0x7f000001
78
+ if (/^0x[0-9a-fA-F]+$/.test(cleaned)) {
79
+ const num = parseInt(cleaned, 16);
80
+ return [
81
+ (num >>> 24) & 0xff,
82
+ (num >>> 16) & 0xff,
83
+ (num >>> 8) & 0xff,
84
+ num & 0xff,
85
+ ];
86
+ }
87
+ // Octal notation
88
+ if (/^0[0-7]/.test(cleaned)) {
89
+ if (/^0[0-7]+$/.test(cleaned)) {
90
+ const num = parseInt(cleaned, 8);
91
+ if (num <= 0xffffffff) {
92
+ return [
93
+ (num >>> 24) & 0xff,
94
+ (num >>> 16) & 0xff,
95
+ (num >>> 8) & 0xff,
96
+ num & 0xff,
97
+ ];
98
+ }
99
+ }
100
+ const parts = cleaned.split('.');
101
+ if (parts.length === 4) {
102
+ const octets = parts.map(p => parseInt(p, /^0[0-7]/.test(p) ? 8 : 10));
103
+ if (octets.every(o => o >= 0 && o <= 255)) {
104
+ return octets;
105
+ }
106
+ }
107
+ }
108
+ // Decimal notation: 2130706433
109
+ if (/^\d+$/.test(cleaned)) {
110
+ const num = parseInt(cleaned, 10);
111
+ if (num <= 0xffffffff) {
112
+ return [
113
+ (num >>> 24) & 0xff,
114
+ (num >>> 16) & 0xff,
115
+ (num >>> 8) & 0xff,
116
+ num & 0xff,
117
+ ];
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ /**
123
+ * Validate IPv4 address against private/reserved ranges
124
+ */
125
+ function validateIPv4ForSSRF(octets) {
126
+ const [a, b, c, d] = octets;
127
+ // Loopback: 127.0.0.0/8
128
+ if (a === 127) {
129
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
130
+ }
131
+ // Private: 10.0.0.0/8
132
+ if (a === 10) {
133
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
134
+ }
135
+ // Private: 172.16.0.0/12
136
+ if (a === 172 && b >= 16 && b <= 31) {
137
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
138
+ }
139
+ // Private: 192.168.0.0/16
140
+ if (a === 192 && b === 168) {
141
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
142
+ }
143
+ // Link-local: 169.254.0.0/16 (includes AWS metadata endpoint)
144
+ if (a === 169 && b === 254) {
145
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
146
+ }
147
+ // Broadcast: 255.255.255.255
148
+ if (a === 255 && b === 255 && c === 255 && d === 255) {
149
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
150
+ }
151
+ // This network: 0.0.0.0/8
152
+ if (a === 0) {
153
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
154
+ }
155
+ }
156
+ /**
157
+ * Validate IPv6 address against private/reserved ranges
158
+ */
159
+ function validateIPv6ForSSRF(hostname) {
160
+ const addr = hostname.replace(/^\[|\]$/g, '').toLowerCase();
161
+ // Loopback: ::1
162
+ if (addr === '::1' || addr === '0:0:0:0:0:0:0:1') {
163
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
164
+ }
165
+ // IPv6 mapped IPv4: ::ffff:192.168.1.1
166
+ if (addr.startsWith('::ffff:')) {
167
+ const ipv4Part = addr.substring(7);
168
+ if (ipv4Part.includes('.')) {
169
+ const parts = ipv4Part.split('.');
170
+ if (parts.length === 4) {
171
+ const octets = parts.map(p => parseInt(p, 10));
172
+ if (octets.every(o => !isNaN(o) && o >= 0 && o <= 255)) {
173
+ validateIPv4ForSSRF(octets);
174
+ }
175
+ }
176
+ }
177
+ else {
178
+ const hexStr = ipv4Part.replace(/:/g, '');
179
+ if (/^[0-9a-f]{1,8}$/.test(hexStr)) {
180
+ const num = parseInt(hexStr, 16);
181
+ const octets = [
182
+ (num >>> 24) & 0xff,
183
+ (num >>> 16) & 0xff,
184
+ (num >>> 8) & 0xff,
185
+ num & 0xff,
186
+ ];
187
+ validateIPv4ForSSRF(octets);
188
+ }
189
+ }
190
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
191
+ }
192
+ // Unique local addresses: fc00::/7
193
+ if (addr.startsWith('fc') || addr.startsWith('fd')) {
194
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
195
+ }
196
+ // Link-local: fe80::/10
197
+ if (addr.startsWith('fe8') || addr.startsWith('fe9') ||
198
+ addr.startsWith('fea') || addr.startsWith('feb')) {
199
+ throw new SSRFError('Cannot fetch localhost, private networks, or non-HTTP URLs');
200
+ }
201
+ }