recker 1.0.43 → 1.0.44

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 (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,877 @@
1
+ import { createResult } from './types.js';
2
+ export const securityRules = [
3
+ {
4
+ id: 'https-required',
5
+ name: 'HTTPS',
6
+ category: 'security',
7
+ severity: 'error',
8
+ description: 'Page must be served over HTTPS',
9
+ check: (ctx) => {
10
+ if (ctx.isHttps === false) {
11
+ return createResult({ id: 'https-required', name: 'HTTPS', category: 'security', severity: 'error' }, 'fail', 'Page is not served over HTTPS', {
12
+ recommendation: 'Enable HTTPS for all pages',
13
+ evidence: {
14
+ found: 'HTTP',
15
+ expected: 'HTTPS',
16
+ impact: 'Browsers show "Not Secure" warning, affects SEO ranking',
17
+ learnMore: 'https://web.dev/why-https-matters/',
18
+ },
19
+ });
20
+ }
21
+ if (ctx.isHttps === true) {
22
+ return createResult({ id: 'https-required', name: 'HTTPS', category: 'security', severity: 'error' }, 'pass', 'Page is served over HTTPS');
23
+ }
24
+ return createResult({ id: 'https-required', name: 'HTTPS', category: 'security', severity: 'error' }, 'info', 'Not applicable (HTTPS status unavailable)', { recommendation: 'This rule checks if the page is served over HTTPS when protocol information is available' });
25
+ },
26
+ },
27
+ {
28
+ id: 'mixed-content',
29
+ name: 'Mixed Content',
30
+ category: 'security',
31
+ severity: 'error',
32
+ description: 'HTTPS pages should not load HTTP resources',
33
+ check: (ctx) => {
34
+ if (ctx.hasMixedContent) {
35
+ return createResult({ id: 'mixed-content', name: 'Mixed Content', category: 'security', severity: 'error' }, 'fail', 'Page has mixed content (HTTP resources on HTTPS page)', {
36
+ recommendation: 'Update all resources to use HTTPS',
37
+ evidence: {
38
+ found: 'Mixed content detected',
39
+ expected: 'All resources over HTTPS',
40
+ impact: 'Browsers may block HTTP resources, breaking functionality',
41
+ learnMore: 'https://web.dev/what-is-mixed-content/',
42
+ },
43
+ });
44
+ }
45
+ return createResult({ id: 'mixed-content', name: 'Mixed Content', category: 'security', severity: 'error' }, 'info', 'Not applicable (no mixed content detected or HTTPS not used)', { recommendation: 'This rule checks for HTTP resources on HTTPS pages when mixed content is detected' });
46
+ },
47
+ },
48
+ {
49
+ id: 'http-redirect',
50
+ name: 'HTTP to HTTPS Redirect',
51
+ category: 'security',
52
+ severity: 'warning',
53
+ description: 'HTTP traffic should redirect to HTTPS',
54
+ check: (ctx) => {
55
+ if (ctx.httpRedirectsToHttps === undefined) {
56
+ return createResult({ id: 'http-redirect', name: 'HTTP to HTTPS Redirect', category: 'security', severity: 'warning' }, 'info', 'Not applicable (HTTP redirect status unavailable)', { recommendation: 'This rule checks if HTTP traffic redirects to HTTPS when redirect information is available' });
57
+ }
58
+ if (!ctx.httpRedirectsToHttps) {
59
+ return createResult({ id: 'http-redirect', name: 'HTTP to HTTPS Redirect', category: 'security', severity: 'warning' }, 'warn', 'HTTP does not redirect to HTTPS', {
60
+ recommendation: 'Configure server to redirect all HTTP traffic to HTTPS',
61
+ evidence: {
62
+ expected: '301/302 redirect from HTTP to HTTPS',
63
+ impact: 'Users accessing via HTTP may stay on insecure connection',
64
+ learnMore: 'https://web.dev/redirect-http-to-https/',
65
+ },
66
+ });
67
+ }
68
+ return createResult({ id: 'http-redirect', name: 'HTTP to HTTPS Redirect', category: 'security', severity: 'warning' }, 'pass', 'HTTP redirects to HTTPS');
69
+ },
70
+ },
71
+ {
72
+ id: 'internal-links-https',
73
+ name: 'Internal Links Use HTTPS',
74
+ category: 'security',
75
+ severity: 'warning',
76
+ description: 'Internal links should use HTTPS to avoid mixed content and redirect chains',
77
+ check: (ctx) => {
78
+ if (ctx.internalHttpLinks === undefined) {
79
+ return createResult({ id: 'internal-links-https', name: 'Internal Links Use HTTPS', category: 'security', severity: 'warning' }, 'info', 'Not applicable (internal link data unavailable)', { recommendation: 'This rule checks internal links for HTTPS usage when link analysis is available' });
80
+ }
81
+ if (ctx.internalHttpLinks > 0) {
82
+ const examples = ctx.internalHttpLinkUrls?.slice(0, 3) || [];
83
+ return createResult({ id: 'internal-links-https', name: 'Internal Links Use HTTPS', category: 'security', severity: 'warning' }, 'warn', `${ctx.internalHttpLinks} internal link(s) use HTTP instead of HTTPS`, {
84
+ value: ctx.internalHttpLinks,
85
+ recommendation: 'Update all internal links to use HTTPS URLs',
86
+ evidence: {
87
+ found: `${ctx.internalHttpLinks} HTTP internal links${examples.length > 0 ? `: ${examples.join(', ')}` : ''}`,
88
+ expected: 'All internal links should use HTTPS',
89
+ impact: 'HTTP links cause unnecessary redirects, slow page loads, and may trigger mixed content warnings. Search engines prefer sites with consistent HTTPS usage.',
90
+ learnMore: 'https://web.dev/why-https-matters/',
91
+ },
92
+ });
93
+ }
94
+ return createResult({ id: 'internal-links-https', name: 'Internal Links Use HTTPS', category: 'security', severity: 'warning' }, 'pass', 'All internal links use HTTPS');
95
+ },
96
+ },
97
+ {
98
+ id: 'security-csp-exists',
99
+ name: 'Content Security Policy (CSP)',
100
+ category: 'security',
101
+ severity: 'warning',
102
+ description: 'Content Security Policy header should be present to mitigate XSS attacks.',
103
+ check: (ctx) => {
104
+ if (!ctx.responseHeaders) {
105
+ return createResult({ id: 'security-csp-exists', name: 'Content Security Policy', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for CSP headers when HTTP response headers are available' });
106
+ }
107
+ const cspHeader = ctx.responseHeaders['content-security-policy'] || ctx.responseHeaders['Content-Security-Policy'];
108
+ if (!cspHeader) {
109
+ return createResult({ id: 'security-csp-exists', name: 'Content Security Policy', category: 'security', severity: 'warning' }, 'warn', 'Content-Security-Policy header is missing', {
110
+ recommendation: 'Implement a strong Content-Security-Policy to prevent XSS attacks',
111
+ evidence: {
112
+ expected: 'Content-Security-Policy header',
113
+ impact: 'Page is vulnerable to XSS and data injection attacks',
114
+ learnMore: 'https://web.dev/csp/',
115
+ },
116
+ });
117
+ }
118
+ return createResult({ id: 'security-csp-exists', name: 'Content Security Policy', category: 'security', severity: 'warning' }, 'pass', 'Content-Security-Policy header is present');
119
+ },
120
+ },
121
+ {
122
+ id: 'security-csp-xss-effective',
123
+ name: 'CSP XSS Effectiveness',
124
+ category: 'security',
125
+ severity: 'warning',
126
+ description: 'CSP should be effective against XSS attacks',
127
+ check: (ctx) => {
128
+ if (!ctx.responseHeaders) {
129
+ return createResult({ id: 'security-csp-xss-effective', name: 'CSP XSS Effectiveness', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks CSP effectiveness when HTTP response headers are available' });
130
+ }
131
+ const cspHeader = ctx.responseHeaders['content-security-policy'] || ctx.responseHeaders['Content-Security-Policy'];
132
+ if (!cspHeader) {
133
+ return createResult({ id: 'security-csp-xss-effective', name: 'CSP XSS Effectiveness', category: 'security', severity: 'warning' }, 'info', 'Not applicable (CSP header not present)', { recommendation: 'This rule checks CSP effectiveness when a Content-Security-Policy header exists' });
134
+ }
135
+ const csp = String(cspHeader).toLowerCase();
136
+ const weaknesses = [];
137
+ if (csp.includes("'unsafe-inline'") && !csp.includes("'strict-dynamic'") && !csp.includes("'nonce-")) {
138
+ weaknesses.push("unsafe-inline without nonce/strict-dynamic");
139
+ }
140
+ if (csp.includes("'unsafe-eval'")) {
141
+ weaknesses.push("unsafe-eval allows code execution");
142
+ }
143
+ if (csp.includes('data:') && (csp.includes('script-src') || !csp.includes('default-src'))) {
144
+ weaknesses.push("data: URIs can be exploited for XSS");
145
+ }
146
+ if (csp.match(/script-src[^;]*\*/)) {
147
+ weaknesses.push("Wildcard in script-src");
148
+ }
149
+ if (weaknesses.length > 0) {
150
+ return createResult({ id: 'security-csp-xss-effective', name: 'CSP XSS Effectiveness', category: 'security', severity: 'warning' }, 'warn', `CSP may not be effective against XSS: ${weaknesses.join(', ')}`, {
151
+ recommendation: 'Use nonce-based CSP or strict-dynamic for better XSS protection',
152
+ evidence: {
153
+ found: weaknesses,
154
+ expected: 'No unsafe-inline, unsafe-eval, or wildcards',
155
+ impact: 'Attackers may be able to execute malicious scripts',
156
+ learnMore: 'https://web.dev/strict-csp/',
157
+ },
158
+ });
159
+ }
160
+ return createResult({ id: 'security-csp-xss-effective', name: 'CSP XSS Effectiveness', category: 'security', severity: 'warning' }, 'pass', 'CSP appears effective against XSS attacks');
161
+ },
162
+ },
163
+ {
164
+ id: 'security-csp-directives',
165
+ name: 'CSP Required Directives',
166
+ category: 'security',
167
+ severity: 'error',
168
+ description: 'CSP should have script-src and object-src directives to prevent unsafe script execution',
169
+ check: (ctx) => {
170
+ if (!ctx.responseHeaders) {
171
+ return createResult({ id: 'security-csp-directives', name: 'CSP Required Directives', category: 'security', severity: 'error' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks CSP directives when HTTP response headers are available' });
172
+ }
173
+ const cspHeader = ctx.responseHeaders['content-security-policy'] || ctx.responseHeaders['Content-Security-Policy'];
174
+ if (!cspHeader) {
175
+ return createResult({ id: 'security-csp-directives', name: 'CSP Required Directives', category: 'security', severity: 'error' }, 'info', 'Not applicable (CSP header not present)', { recommendation: 'This rule checks CSP directives when a Content-Security-Policy header exists' });
176
+ }
177
+ const csp = String(cspHeader).toLowerCase();
178
+ const missingDirectives = [];
179
+ if (!csp.includes('script-src') && !csp.includes('default-src')) {
180
+ missingDirectives.push({
181
+ directive: 'script-src',
182
+ severity: 'High',
183
+ impact: 'Allows execution of unsafe scripts from any source',
184
+ });
185
+ }
186
+ if (!csp.includes('object-src') && !csp.includes('default-src')) {
187
+ missingDirectives.push({
188
+ directive: 'object-src',
189
+ severity: 'High',
190
+ impact: 'Allows injection of plugins that execute unsafe scripts',
191
+ });
192
+ }
193
+ if (!csp.includes('base-uri')) {
194
+ missingDirectives.push({
195
+ directive: 'base-uri',
196
+ severity: 'Medium',
197
+ impact: 'Allows attackers to change the base URL for relative links',
198
+ });
199
+ }
200
+ if (missingDirectives.length > 0) {
201
+ const highSeverity = missingDirectives.filter((d) => d.severity === 'High');
202
+ if (highSeverity.length > 0) {
203
+ return createResult({ id: 'security-csp-directives', name: 'CSP Required Directives', category: 'security', severity: 'error' }, 'fail', `Missing critical CSP directives: ${highSeverity.map((d) => d.directive).join(', ')}`, {
204
+ recommendation: "Add script-src and object-src directives. Consider setting object-src to 'none' if plugins are not needed.",
205
+ evidence: {
206
+ found: missingDirectives.map((d) => `${d.directive} (${d.severity}): ${d.impact}`),
207
+ expected: "script-src 'self'; object-src 'none'; base-uri 'self'",
208
+ impact: 'Page is vulnerable to script injection attacks',
209
+ learnMore: 'https://web.dev/csp/',
210
+ },
211
+ });
212
+ }
213
+ return createResult({ id: 'security-csp-directives', name: 'CSP Required Directives', category: 'security', severity: 'error' }, 'warn', `Missing recommended CSP directives: ${missingDirectives.map((d) => d.directive).join(', ')}`, {
214
+ recommendation: 'Add base-uri directive to prevent base tag hijacking',
215
+ evidence: {
216
+ found: missingDirectives.map((d) => `${d.directive}: ${d.impact}`),
217
+ expected: "base-uri 'self'",
218
+ },
219
+ });
220
+ }
221
+ return createResult({ id: 'security-csp-directives', name: 'CSP Required Directives', category: 'security', severity: 'error' }, 'pass', 'CSP has required script-src and object-src directives');
222
+ },
223
+ },
224
+ {
225
+ id: 'security-hsts-exists',
226
+ name: 'Strict-Transport-Security (HSTS)',
227
+ category: 'security',
228
+ severity: 'warning',
229
+ description: 'HSTS header forces secure connections and improves SEO indirectly.',
230
+ check: (ctx) => {
231
+ if (!ctx.responseHeaders) {
232
+ return createResult({ id: 'security-hsts-exists', name: 'HSTS Header', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for HSTS headers when HTTP response headers are available' });
233
+ }
234
+ const hstsHeader = ctx.responseHeaders['strict-transport-security'] || ctx.responseHeaders['Strict-Transport-Security'];
235
+ if (!hstsHeader) {
236
+ return createResult({ id: 'security-hsts-exists', name: 'HSTS Header', category: 'security', severity: 'warning' }, 'warn', 'Strict-Transport-Security header is missing', {
237
+ recommendation: 'Implement HSTS to force secure connections',
238
+ evidence: {
239
+ expected: 'Strict-Transport-Security header',
240
+ impact: 'Users may connect over insecure HTTP on first visit',
241
+ learnMore: 'https://web.dev/security-headers/#hsts',
242
+ },
243
+ });
244
+ }
245
+ return createResult({ id: 'security-hsts-exists', name: 'HSTS Header', category: 'security', severity: 'warning' }, 'pass', `Strict-Transport-Security header is present: ${hstsHeader}`);
246
+ },
247
+ },
248
+ {
249
+ id: 'security-hsts-strong',
250
+ name: 'Strong HSTS Policy',
251
+ category: 'security',
252
+ severity: 'info',
253
+ description: 'HSTS should have a strong policy with long max-age and includeSubDomains',
254
+ check: (ctx) => {
255
+ if (!ctx.responseHeaders) {
256
+ return createResult({ id: 'security-hsts-strong', name: 'Strong HSTS Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks HSTS policy strength when HTTP response headers are available' });
257
+ }
258
+ const hstsHeader = ctx.responseHeaders['strict-transport-security'] || ctx.responseHeaders['Strict-Transport-Security'];
259
+ if (!hstsHeader) {
260
+ return createResult({ id: 'security-hsts-strong', name: 'Strong HSTS Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (HSTS header not present)', { recommendation: 'This rule checks HSTS policy strength when a Strict-Transport-Security header exists' });
261
+ }
262
+ const hsts = String(hstsHeader).toLowerCase();
263
+ const weaknesses = [];
264
+ const maxAgeMatch = hsts.match(/max-age\s*=\s*(\d+)/);
265
+ const maxAge = maxAgeMatch ? parseInt(maxAgeMatch[1], 10) : 0;
266
+ const oneYear = 31536000;
267
+ if (maxAge < oneYear) {
268
+ weaknesses.push(`max-age is ${maxAge}s (recommended: ${oneYear}s / 1 year)`);
269
+ }
270
+ if (!hsts.includes('includesubdomains')) {
271
+ weaknesses.push('Missing includeSubDomains');
272
+ }
273
+ if (!hsts.includes('preload')) {
274
+ weaknesses.push('Missing preload (optional but recommended)');
275
+ }
276
+ if (weaknesses.length > 0) {
277
+ return createResult({ id: 'security-hsts-strong', name: 'Strong HSTS Policy', category: 'security', severity: 'info' }, 'info', `HSTS policy could be stronger: ${weaknesses.join('; ')}`, {
278
+ recommendation: 'Use max-age=31536000; includeSubDomains; preload',
279
+ evidence: {
280
+ found: hstsHeader,
281
+ expected: 'max-age=31536000; includeSubDomains; preload',
282
+ learnMore: 'https://hstspreload.org/',
283
+ },
284
+ });
285
+ }
286
+ return createResult({ id: 'security-hsts-strong', name: 'Strong HSTS Policy', category: 'security', severity: 'info' }, 'pass', 'Strong HSTS policy with long max-age and includeSubDomains');
287
+ },
288
+ },
289
+ {
290
+ id: 'security-coop',
291
+ name: 'Cross-Origin-Opener-Policy (COOP)',
292
+ category: 'security',
293
+ severity: 'info',
294
+ description: 'COOP ensures proper origin isolation for security',
295
+ check: (ctx) => {
296
+ if (!ctx.responseHeaders) {
297
+ return createResult({ id: 'security-coop', name: 'Cross-Origin-Opener-Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for COOP headers when HTTP response headers are available' });
298
+ }
299
+ const coopHeader = ctx.responseHeaders['cross-origin-opener-policy'] || ctx.responseHeaders['Cross-Origin-Opener-Policy'];
300
+ if (!coopHeader) {
301
+ return createResult({ id: 'security-coop', name: 'Cross-Origin-Opener-Policy', category: 'security', severity: 'info' }, 'info', 'Cross-Origin-Opener-Policy header is missing', {
302
+ recommendation: 'Add COOP header to isolate your origin from attackers',
303
+ evidence: {
304
+ expected: 'Cross-Origin-Opener-Policy: same-origin',
305
+ impact: 'Lack of origin isolation may enable cross-origin attacks',
306
+ learnMore: 'https://web.dev/coop-coep/',
307
+ },
308
+ });
309
+ }
310
+ const coop = String(coopHeader).toLowerCase();
311
+ if (coop === 'same-origin') {
312
+ return createResult({ id: 'security-coop', name: 'Cross-Origin-Opener-Policy', category: 'security', severity: 'info' }, 'pass', 'COOP: same-origin (full isolation)');
313
+ }
314
+ if (coop === 'same-origin-allow-popups') {
315
+ return createResult({ id: 'security-coop', name: 'Cross-Origin-Opener-Policy', category: 'security', severity: 'info' }, 'pass', 'COOP: same-origin-allow-popups');
316
+ }
317
+ const coopValue = Array.isArray(coopHeader) ? coopHeader.join(', ') : String(coopHeader);
318
+ return createResult({ id: 'security-coop', name: 'Cross-Origin-Opener-Policy', category: 'security', severity: 'info' }, 'info', `COOP: ${coopValue}`, { value: coopValue });
319
+ },
320
+ },
321
+ {
322
+ id: 'security-coep',
323
+ name: 'Cross-Origin-Embedder-Policy (COEP)',
324
+ category: 'security',
325
+ severity: 'info',
326
+ description: 'COEP prevents loading cross-origin resources without explicit permission',
327
+ check: (ctx) => {
328
+ if (!ctx.responseHeaders) {
329
+ return createResult({ id: 'security-coep', name: 'Cross-Origin-Embedder-Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for COEP headers when HTTP response headers are available' });
330
+ }
331
+ const coepHeader = ctx.responseHeaders['cross-origin-embedder-policy'] || ctx.responseHeaders['Cross-Origin-Embedder-Policy'];
332
+ if (!coepHeader) {
333
+ return createResult({ id: 'security-coep', name: 'Cross-Origin-Embedder-Policy', category: 'security', severity: 'info' }, 'info', 'Cross-Origin-Embedder-Policy header is missing', {
334
+ recommendation: 'Add COEP header for cross-origin isolation',
335
+ evidence: {
336
+ expected: 'Cross-Origin-Embedder-Policy: require-corp',
337
+ impact: 'Required for SharedArrayBuffer and high-resolution timers',
338
+ learnMore: 'https://web.dev/coop-coep/',
339
+ },
340
+ });
341
+ }
342
+ return createResult({ id: 'security-coep', name: 'Cross-Origin-Embedder-Policy', category: 'security', severity: 'info' }, 'pass', `COEP: ${coepHeader}`);
343
+ },
344
+ },
345
+ {
346
+ id: 'security-trusted-types',
347
+ name: 'Trusted Types',
348
+ category: 'security',
349
+ severity: 'info',
350
+ description: 'Trusted Types help prevent DOM-based XSS attacks',
351
+ check: (ctx) => {
352
+ if (!ctx.responseHeaders) {
353
+ return createResult({ id: 'security-trusted-types', name: 'Trusted Types', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for Trusted Types in CSP when HTTP response headers are available' });
354
+ }
355
+ const cspHeader = ctx.responseHeaders['content-security-policy'] || ctx.responseHeaders['Content-Security-Policy'];
356
+ if (!cspHeader) {
357
+ return createResult({ id: 'security-trusted-types', name: 'Trusted Types', category: 'security', severity: 'info' }, 'info', 'Not applicable (CSP header not present)', { recommendation: 'This rule checks for Trusted Types when a Content-Security-Policy header exists' });
358
+ }
359
+ const csp = String(cspHeader).toLowerCase();
360
+ if (csp.includes('require-trusted-types-for')) {
361
+ return createResult({ id: 'security-trusted-types', name: 'Trusted Types', category: 'security', severity: 'info' }, 'pass', 'Trusted Types policy enabled via CSP');
362
+ }
363
+ return createResult({ id: 'security-trusted-types', name: 'Trusted Types', category: 'security', severity: 'info' }, 'info', 'Trusted Types not enabled', {
364
+ recommendation: 'Add require-trusted-types-for to CSP for DOM XSS protection',
365
+ evidence: {
366
+ expected: "Content-Security-Policy: require-trusted-types-for 'script'",
367
+ impact: 'DOM manipulation may be vulnerable to XSS attacks',
368
+ learnMore: 'https://web.dev/trusted-types/',
369
+ },
370
+ });
371
+ },
372
+ },
373
+ {
374
+ id: 'security-xfo-exists',
375
+ name: 'X-Frame-Options',
376
+ category: 'security',
377
+ severity: 'warning',
378
+ description: 'X-Frame-Options header should be present to prevent clickjacking.',
379
+ check: (ctx) => {
380
+ if (!ctx.responseHeaders) {
381
+ return createResult({ id: 'security-xfo-exists', name: 'X-Frame-Options', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for X-Frame-Options headers when HTTP response headers are available' });
382
+ }
383
+ const xfoHeader = ctx.responseHeaders['x-frame-options'] || ctx.responseHeaders['X-Frame-Options'];
384
+ if (!xfoHeader) {
385
+ return createResult({ id: 'security-xfo-exists', name: 'X-Frame-Options', category: 'security', severity: 'warning' }, 'warn', 'X-Frame-Options header is missing', {
386
+ recommendation: 'Implement X-Frame-Options to prevent clickjacking attacks',
387
+ evidence: {
388
+ expected: 'X-Frame-Options: DENY or SAMEORIGIN',
389
+ impact: 'Page can be embedded in malicious iframes for clickjacking',
390
+ learnMore: 'https://web.dev/security-headers/#xfo',
391
+ },
392
+ });
393
+ }
394
+ return createResult({ id: 'security-xfo-exists', name: 'X-Frame-Options', category: 'security', severity: 'warning' }, 'pass', `X-Frame-Options header is present: ${xfoHeader}`);
395
+ },
396
+ },
397
+ {
398
+ id: 'security-frame-ancestors',
399
+ name: 'CSP frame-ancestors',
400
+ category: 'security',
401
+ severity: 'info',
402
+ description: 'CSP frame-ancestors is the modern replacement for X-Frame-Options',
403
+ check: (ctx) => {
404
+ if (!ctx.responseHeaders) {
405
+ return createResult({ id: 'security-frame-ancestors', name: 'CSP frame-ancestors', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for CSP frame-ancestors when HTTP response headers are available' });
406
+ }
407
+ const cspHeader = ctx.responseHeaders['content-security-policy'] || ctx.responseHeaders['Content-Security-Policy'];
408
+ const xfoHeader = ctx.responseHeaders['x-frame-options'] || ctx.responseHeaders['X-Frame-Options'];
409
+ if (!cspHeader && !xfoHeader) {
410
+ return createResult({ id: 'security-frame-ancestors', name: 'CSP frame-ancestors', category: 'security', severity: 'info' }, 'info', 'Not applicable (neither CSP nor X-Frame-Options present)', { recommendation: 'This rule checks CSP frame-ancestors when clickjacking protection headers are present' });
411
+ }
412
+ if (cspHeader && String(cspHeader).toLowerCase().includes('frame-ancestors')) {
413
+ return createResult({ id: 'security-frame-ancestors', name: 'CSP frame-ancestors', category: 'security', severity: 'info' }, 'pass', 'CSP frame-ancestors directive present (modern clickjacking protection)');
414
+ }
415
+ if (xfoHeader && !cspHeader) {
416
+ return createResult({ id: 'security-frame-ancestors', name: 'CSP frame-ancestors', category: 'security', severity: 'info' }, 'info', 'Using X-Frame-Options; consider migrating to CSP frame-ancestors', {
417
+ recommendation: 'Use CSP frame-ancestors for better control over framing',
418
+ evidence: {
419
+ found: `X-Frame-Options: ${xfoHeader}`,
420
+ expected: "Content-Security-Policy: frame-ancestors 'self'",
421
+ learnMore: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors',
422
+ },
423
+ });
424
+ }
425
+ return createResult({ id: 'security-frame-ancestors', name: 'CSP frame-ancestors', category: 'security', severity: 'info' }, 'info', 'Not applicable (clickjacking protection configured without frame-ancestors)', { recommendation: 'This rule checks modern frame-ancestors directive in CSP for enhanced clickjacking protection' });
426
+ },
427
+ },
428
+ {
429
+ id: 'security-cors-config',
430
+ name: 'CORS Configuration',
431
+ category: 'security',
432
+ severity: 'warning',
433
+ description: 'Review Access-Control-Allow-Origin header for proper CORS configuration.',
434
+ check: (ctx) => {
435
+ if (!ctx.responseHeaders) {
436
+ return createResult({ id: 'security-cors-config', name: 'CORS Configuration', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks CORS configuration when HTTP response headers are available' });
437
+ }
438
+ const acaoHeader = ctx.responseHeaders['access-control-allow-origin'] || ctx.responseHeaders['Access-Control-Allow-Origin'];
439
+ if (acaoHeader === '*') {
440
+ return createResult({ id: 'security-cors-config', name: 'CORS Configuration', category: 'security', severity: 'warning' }, 'warn', 'Access-Control-Allow-Origin is set to "*"', {
441
+ recommendation: 'Avoid wildcard (*) in Access-Control-Allow-Origin for sensitive content',
442
+ evidence: {
443
+ found: '*',
444
+ expected: 'Specific origins only',
445
+ impact: 'Any website can make requests to your API',
446
+ },
447
+ });
448
+ }
449
+ if (!acaoHeader) {
450
+ return createResult({ id: 'security-cors-config', name: 'CORS Configuration', category: 'security', severity: 'warning' }, 'info', 'Access-Control-Allow-Origin header is missing', { recommendation: 'Configure CORS if resources are consumed cross-origin' });
451
+ }
452
+ return createResult({ id: 'security-cors-config', name: 'CORS Configuration', category: 'security', severity: 'warning' }, 'pass', `Access-Control-Allow-Origin: ${acaoHeader}`);
453
+ },
454
+ },
455
+ {
456
+ id: 'security-xcto-exists',
457
+ name: 'X-Content-Type-Options',
458
+ category: 'security',
459
+ severity: 'warning',
460
+ description: 'X-Content-Type-Options header prevents MIME sniffing attacks.',
461
+ check: (ctx) => {
462
+ if (!ctx.responseHeaders) {
463
+ return createResult({ id: 'security-xcto-exists', name: 'X-Content-Type-Options', category: 'security', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for X-Content-Type-Options headers when HTTP response headers are available' });
464
+ }
465
+ const xctoHeader = ctx.responseHeaders['x-content-type-options'] || ctx.responseHeaders['X-Content-Type-Options'];
466
+ if (!xctoHeader) {
467
+ return createResult({ id: 'security-xcto-exists', name: 'X-Content-Type-Options', category: 'security', severity: 'warning' }, 'warn', 'X-Content-Type-Options header is missing', {
468
+ recommendation: 'Implement X-Content-Type-Options: nosniff',
469
+ evidence: {
470
+ expected: 'X-Content-Type-Options: nosniff',
471
+ impact: 'Browser may interpret files incorrectly, leading to security issues',
472
+ },
473
+ });
474
+ }
475
+ return createResult({ id: 'security-xcto-exists', name: 'X-Content-Type-Options', category: 'security', severity: 'warning' }, 'pass', `X-Content-Type-Options: ${xctoHeader}`);
476
+ },
477
+ },
478
+ {
479
+ id: 'security-rp-exists',
480
+ name: 'Referrer-Policy',
481
+ category: 'security',
482
+ severity: 'info',
483
+ description: 'Referrer-Policy controls how much referrer information is sent with requests.',
484
+ check: (ctx) => {
485
+ if (!ctx.responseHeaders) {
486
+ return createResult({ id: 'security-rp-exists', name: 'Referrer-Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for Referrer-Policy headers when HTTP response headers are available' });
487
+ }
488
+ const rpHeader = ctx.responseHeaders['referrer-policy'] || ctx.responseHeaders['Referrer-Policy'];
489
+ if (!rpHeader) {
490
+ return createResult({ id: 'security-rp-exists', name: 'Referrer-Policy', category: 'security', severity: 'info' }, 'info', 'Referrer-Policy header is missing', {
491
+ recommendation: 'Implement Referrer-Policy for privacy (e.g., strict-origin-when-cross-origin)',
492
+ evidence: {
493
+ expected: 'Referrer-Policy: strict-origin-when-cross-origin',
494
+ impact: 'Full URLs may leak in referrer headers',
495
+ },
496
+ });
497
+ }
498
+ return createResult({ id: 'security-rp-exists', name: 'Referrer-Policy', category: 'security', severity: 'info' }, 'pass', `Referrer-Policy: ${rpHeader}`);
499
+ },
500
+ },
501
+ {
502
+ id: 'security-pp-exists',
503
+ name: 'Permissions-Policy',
504
+ category: 'security',
505
+ severity: 'info',
506
+ description: 'Permissions-Policy controls browser features available to the page.',
507
+ check: (ctx) => {
508
+ if (!ctx.responseHeaders) {
509
+ return createResult({ id: 'security-pp-exists', name: 'Permissions-Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for Permissions-Policy headers when HTTP response headers are available' });
510
+ }
511
+ const ppHeader = ctx.responseHeaders['permissions-policy'] || ctx.responseHeaders['Permissions-Policy'];
512
+ if (!ppHeader) {
513
+ return createResult({ id: 'security-pp-exists', name: 'Permissions-Policy', category: 'security', severity: 'info' }, 'info', 'Permissions-Policy header is missing', {
514
+ recommendation: 'Disable unused browser features (e.g., camera=(), microphone=())',
515
+ evidence: {
516
+ expected: 'Permissions-Policy header',
517
+ impact: 'Third-party code may access browser features unnecessarily',
518
+ },
519
+ });
520
+ }
521
+ return createResult({ id: 'security-pp-exists', name: 'Permissions-Policy', category: 'security', severity: 'info' }, 'pass', 'Permissions-Policy header is present');
522
+ },
523
+ },
524
+ {
525
+ id: 'security-xxss',
526
+ name: 'X-XSS-Protection',
527
+ category: 'security',
528
+ severity: 'info',
529
+ description: 'X-XSS-Protection is deprecated but may still provide protection in older browsers',
530
+ check: (ctx) => {
531
+ if (!ctx.responseHeaders) {
532
+ return createResult({ id: 'security-xxss', name: 'X-XSS-Protection', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for deprecated X-XSS-Protection headers when HTTP response headers are available' });
533
+ }
534
+ const xxssHeader = ctx.responseHeaders['x-xss-protection'] || ctx.responseHeaders['X-XSS-Protection'];
535
+ if (xxssHeader && String(xxssHeader).includes('1')) {
536
+ return createResult({ id: 'security-xxss', name: 'X-XSS-Protection', category: 'security', severity: 'info' }, 'info', 'X-XSS-Protection is enabled but deprecated', {
537
+ recommendation: 'Use Content-Security-Policy instead; X-XSS-Protection can be disabled',
538
+ evidence: {
539
+ found: xxssHeader,
540
+ expected: 'CSP for XSS protection',
541
+ impact: 'X-XSS-Protection can introduce vulnerabilities in some cases',
542
+ learnMore: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection',
543
+ },
544
+ });
545
+ }
546
+ return createResult({ id: 'security-xxss', name: 'X-XSS-Protection', category: 'security', severity: 'info' }, 'info', 'Not applicable (X-XSS-Protection not present or disabled)', { recommendation: 'This rule checks for deprecated X-XSS-Protection headers when present' });
547
+ },
548
+ },
549
+ {
550
+ id: 'security-corp',
551
+ name: 'Cross-Origin-Resource-Policy (CORP)',
552
+ category: 'security',
553
+ severity: 'info',
554
+ description: 'CORP restricts which origins can load your resources',
555
+ check: (ctx) => {
556
+ if (!ctx.responseHeaders) {
557
+ return createResult({ id: 'security-corp', name: 'Cross-Origin-Resource-Policy', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for CORP headers when HTTP response headers are available' });
558
+ }
559
+ const corpHeader = ctx.responseHeaders['cross-origin-resource-policy'] || ctx.responseHeaders['Cross-Origin-Resource-Policy'];
560
+ if (!corpHeader) {
561
+ return createResult({ id: 'security-corp', name: 'Cross-Origin-Resource-Policy', category: 'security', severity: 'info' }, 'info', 'Cross-Origin-Resource-Policy header is missing', {
562
+ recommendation: 'Consider adding CORP to control resource loading',
563
+ evidence: {
564
+ expected: 'Cross-Origin-Resource-Policy: same-origin or same-site',
565
+ impact: 'Resources may be loaded by any origin',
566
+ learnMore: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy',
567
+ },
568
+ });
569
+ }
570
+ return createResult({ id: 'security-corp', name: 'Cross-Origin-Resource-Policy', category: 'security', severity: 'info' }, 'pass', `CORP: ${corpHeader}`);
571
+ },
572
+ },
573
+ {
574
+ id: 'security-ssl-valid',
575
+ name: 'SSL Certificate Valid',
576
+ category: 'security',
577
+ severity: 'error',
578
+ description: 'SSL certificate must be valid and not expired',
579
+ check: (ctx) => {
580
+ if (ctx.sslCertificate === undefined) {
581
+ return createResult({ id: 'security-ssl-valid', name: 'SSL Certificate Valid', category: 'security', severity: 'error' }, 'info', 'Not applicable (SSL certificate information unavailable)', { recommendation: 'This rule checks SSL certificate validity when certificate information is available' });
582
+ }
583
+ if (!ctx.sslCertificate.valid) {
584
+ return createResult({ id: 'security-ssl-valid', name: 'SSL Certificate Valid', category: 'security', severity: 'error' }, 'fail', 'SSL certificate is invalid', {
585
+ recommendation: 'Renew or replace the SSL certificate immediately',
586
+ evidence: {
587
+ found: ctx.sslCertificate.error || 'Invalid certificate',
588
+ impact: 'Browsers will show security warnings, users may not trust the site',
589
+ },
590
+ });
591
+ }
592
+ return createResult({ id: 'security-ssl-valid', name: 'SSL Certificate Valid', category: 'security', severity: 'error' }, 'pass', 'SSL certificate is valid');
593
+ },
594
+ },
595
+ {
596
+ id: 'security-ssl-expiry',
597
+ name: 'SSL Certificate Expiry',
598
+ category: 'security',
599
+ severity: 'warning',
600
+ description: 'SSL certificate should not expire within 30 days',
601
+ check: (ctx) => {
602
+ if (!ctx.sslCertificate?.expiryDate) {
603
+ return createResult({ id: 'security-ssl-expiry', name: 'SSL Certificate Expiry', category: 'security', severity: 'warning' }, 'info', 'Not applicable (SSL certificate expiry date unavailable)', { recommendation: 'This rule checks SSL certificate expiration when certificate expiry information is available' });
604
+ }
605
+ const expiryDate = new Date(ctx.sslCertificate.expiryDate);
606
+ const now = new Date();
607
+ const daysUntilExpiry = Math.floor((expiryDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
608
+ if (daysUntilExpiry < 0) {
609
+ return createResult({ id: 'security-ssl-expiry', name: 'SSL Certificate Expiry', category: 'security', severity: 'warning' }, 'fail', 'SSL certificate has expired', {
610
+ recommendation: 'Renew the SSL certificate immediately',
611
+ evidence: {
612
+ found: `Expired ${Math.abs(daysUntilExpiry)} days ago`,
613
+ impact: 'Site is showing security warnings to all visitors',
614
+ },
615
+ });
616
+ }
617
+ if (daysUntilExpiry < 7) {
618
+ return createResult({ id: 'security-ssl-expiry', name: 'SSL Certificate Expiry', category: 'security', severity: 'warning' }, 'fail', `SSL certificate expires in ${daysUntilExpiry} days`, {
619
+ recommendation: 'Renew the SSL certificate urgently',
620
+ evidence: {
621
+ found: `Expires: ${expiryDate.toISOString().split('T')[0]}`,
622
+ impact: 'Certificate will expire very soon',
623
+ },
624
+ });
625
+ }
626
+ if (daysUntilExpiry < 30) {
627
+ return createResult({ id: 'security-ssl-expiry', name: 'SSL Certificate Expiry', category: 'security', severity: 'warning' }, 'warn', `SSL certificate expires in ${daysUntilExpiry} days`, {
628
+ recommendation: 'Plan to renew the SSL certificate soon',
629
+ evidence: {
630
+ found: `Expires: ${expiryDate.toISOString().split('T')[0]}`,
631
+ },
632
+ });
633
+ }
634
+ return createResult({ id: 'security-ssl-expiry', name: 'SSL Certificate Expiry', category: 'security', severity: 'warning' }, 'pass', `SSL certificate valid for ${daysUntilExpiry} days`);
635
+ },
636
+ },
637
+ {
638
+ id: 'security-ssl-name-match',
639
+ name: 'SSL Certificate Name Match',
640
+ category: 'security',
641
+ severity: 'error',
642
+ description: 'SSL certificate CN/SAN must match the domain',
643
+ check: (ctx) => {
644
+ if (ctx.sslCertificate?.nameMismatch === undefined) {
645
+ return createResult({ id: 'security-ssl-name-match', name: 'SSL Certificate Name Match', category: 'security', severity: 'error' }, 'info', 'Not applicable (SSL certificate name match information unavailable)', { recommendation: 'This rule checks SSL certificate name matching when certificate domain information is available' });
646
+ }
647
+ if (ctx.sslCertificate.nameMismatch) {
648
+ return createResult({ id: 'security-ssl-name-match', name: 'SSL Certificate Name Match', category: 'security', severity: 'error' }, 'fail', 'SSL certificate name mismatch', {
649
+ recommendation: 'Get a certificate that matches your domain name',
650
+ evidence: {
651
+ found: ctx.sslCertificate.commonName || 'Unknown',
652
+ expected: ctx.sslCertificate.expectedDomain || 'Domain name',
653
+ impact: 'Browsers will show certificate warning',
654
+ },
655
+ });
656
+ }
657
+ return createResult({ id: 'security-ssl-name-match', name: 'SSL Certificate Name Match', category: 'security', severity: 'error' }, 'pass', 'SSL certificate matches domain');
658
+ },
659
+ },
660
+ {
661
+ id: 'security-tls-version',
662
+ name: 'TLS Version',
663
+ category: 'security',
664
+ severity: 'error',
665
+ description: 'Server should use TLS 1.2 or higher',
666
+ check: (ctx) => {
667
+ if (!ctx.tlsVersion) {
668
+ return createResult({ id: 'security-tls-version', name: 'TLS Version', category: 'security', severity: 'error' }, 'info', 'Not applicable (TLS version information unavailable)', { recommendation: 'This rule checks TLS version when TLS connection information is available' });
669
+ }
670
+ const version = ctx.tlsVersion;
671
+ const insecureVersions = ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1.0', 'TLSv1.1'];
672
+ if (insecureVersions.includes(version)) {
673
+ return createResult({ id: 'security-tls-version', name: 'TLS Version', category: 'security', severity: 'error' }, 'fail', `Insecure TLS version: ${version}`, {
674
+ recommendation: 'Upgrade to TLS 1.2 or TLS 1.3',
675
+ evidence: {
676
+ found: version,
677
+ expected: 'TLSv1.2 or TLSv1.3',
678
+ impact: 'Old TLS versions have known vulnerabilities',
679
+ },
680
+ });
681
+ }
682
+ return createResult({ id: 'security-tls-version', name: 'TLS Version', category: 'security', severity: 'error' }, 'pass', `Using ${version}`);
683
+ },
684
+ },
685
+ {
686
+ id: 'security-ssl-issuer',
687
+ name: 'SSL Certificate Issuer',
688
+ category: 'security',
689
+ severity: 'info',
690
+ description: 'SSL certificate should be from a trusted CA',
691
+ check: (ctx) => {
692
+ if (!ctx.sslCertificate?.issuer) {
693
+ return createResult({ id: 'security-ssl-issuer', name: 'SSL Certificate Issuer', category: 'security', severity: 'info' }, 'info', 'Not applicable (SSL certificate issuer information unavailable)', { recommendation: 'This rule checks SSL certificate issuer when certificate issuer information is available' });
694
+ }
695
+ const issuer = ctx.sslCertificate.issuer;
696
+ const selfSigned = issuer.toLowerCase().includes('self-signed') ||
697
+ ctx.sslCertificate.selfSigned === true;
698
+ if (selfSigned) {
699
+ return createResult({ id: 'security-ssl-issuer', name: 'SSL Certificate Issuer', category: 'security', severity: 'info' }, 'warn', 'Self-signed SSL certificate detected', {
700
+ recommendation: 'Use a certificate from a trusted Certificate Authority',
701
+ evidence: {
702
+ found: issuer,
703
+ impact: 'Self-signed certificates show warnings in browsers',
704
+ },
705
+ });
706
+ }
707
+ return createResult({ id: 'security-ssl-issuer', name: 'SSL Certificate Issuer', category: 'security', severity: 'info' }, 'pass', `Certificate issued by: ${issuer}`);
708
+ },
709
+ },
710
+ {
711
+ id: 'security-password-on-http',
712
+ name: 'Password Fields on HTTP',
713
+ category: 'security',
714
+ severity: 'error',
715
+ description: 'Password fields must only appear on HTTPS pages',
716
+ check: (ctx) => {
717
+ if (ctx.hasPasswordField === undefined) {
718
+ return createResult({ id: 'security-password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'info', 'Not applicable (password field information unavailable)', { recommendation: 'This rule checks for password fields on insecure pages when password field data is available' });
719
+ }
720
+ if (ctx.hasPasswordField && ctx.isHttps === false) {
721
+ return createResult({ id: 'security-password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'fail', 'Password field on insecure HTTP page', {
722
+ recommendation: 'Enable HTTPS for all pages with password fields',
723
+ evidence: {
724
+ impact: 'Passwords sent over HTTP can be intercepted',
725
+ },
726
+ });
727
+ }
728
+ if (ctx.hasPasswordField && ctx.isHttps === true) {
729
+ return createResult({ id: 'security-password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'pass', 'Password fields are on HTTPS');
730
+ }
731
+ return createResult({ id: 'security-password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'info', 'Not applicable (no password fields detected)', { recommendation: 'This rule checks if password fields are only used on HTTPS pages when password fields are present' });
732
+ },
733
+ },
734
+ {
735
+ id: 'security-forms-on-http',
736
+ name: 'Forms on HTTP',
737
+ category: 'security',
738
+ severity: 'warning',
739
+ description: 'Forms should only submit to HTTPS endpoints',
740
+ check: (ctx) => {
741
+ if (ctx.formsOnHttp === undefined) {
742
+ return createResult({ id: 'security-forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'info', 'Not applicable (form submission data unavailable)', { recommendation: 'This rule checks if forms submit to HTTPS endpoints when form data is available' });
743
+ }
744
+ if (ctx.formsOnHttp > 0) {
745
+ return createResult({ id: 'security-forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'warn', `${ctx.formsOnHttp} form(s) submit to HTTP`, {
746
+ value: ctx.formsOnHttp,
747
+ recommendation: 'Update form actions to use HTTPS',
748
+ evidence: {
749
+ impact: 'Form data sent over HTTP can be intercepted',
750
+ },
751
+ });
752
+ }
753
+ return createResult({ id: 'security-forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'pass', 'All forms submit to HTTPS');
754
+ },
755
+ },
756
+ {
757
+ id: 'security-server-disclosure',
758
+ name: 'Server Version Disclosure',
759
+ category: 'security',
760
+ severity: 'info',
761
+ description: 'Server header should not reveal detailed version information',
762
+ check: (ctx) => {
763
+ if (!ctx.responseHeaders) {
764
+ return createResult({ id: 'security-server-disclosure', name: 'Server Version Disclosure', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for server version disclosure when HTTP response headers are available' });
765
+ }
766
+ const serverHeader = ctx.responseHeaders['server'] || ctx.responseHeaders['Server'];
767
+ if (!serverHeader) {
768
+ return createResult({ id: 'security-server-disclosure', name: 'Server Version Disclosure', category: 'security', severity: 'info' }, 'info', 'Not applicable (Server header not present)', { recommendation: 'This rule checks for server version disclosure when Server header is present' });
769
+ }
770
+ const server = String(serverHeader);
771
+ if (/\d+\.\d+/.test(server)) {
772
+ return createResult({ id: 'security-server-disclosure', name: 'Server Version Disclosure', category: 'security', severity: 'info' }, 'info', `Server header reveals version: ${server}`, {
773
+ recommendation: 'Configure server to hide version information',
774
+ evidence: {
775
+ found: server,
776
+ impact: 'Attackers can target known vulnerabilities',
777
+ },
778
+ });
779
+ }
780
+ return createResult({ id: 'security-server-disclosure', name: 'Server Version Disclosure', category: 'security', severity: 'info' }, 'info', 'Not applicable (Server header not present or does not disclose version)', { recommendation: 'This rule checks if the Server header reveals version information' });
781
+ },
782
+ },
783
+ {
784
+ id: 'security-x-powered-by',
785
+ name: 'X-Powered-By Header',
786
+ category: 'security',
787
+ severity: 'info',
788
+ description: 'X-Powered-By header reveals technology stack',
789
+ check: (ctx) => {
790
+ if (!ctx.responseHeaders) {
791
+ return createResult({ id: 'security-x-powered-by', name: 'X-Powered-By Header', category: 'security', severity: 'info' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for X-Powered-By header disclosure when HTTP response headers are available' });
792
+ }
793
+ const xPoweredBy = ctx.responseHeaders['x-powered-by'] || ctx.responseHeaders['X-Powered-By'];
794
+ if (xPoweredBy) {
795
+ return createResult({ id: 'security-x-powered-by', name: 'X-Powered-By Header', category: 'security', severity: 'info' }, 'info', `X-Powered-By header present: ${xPoweredBy}`, {
796
+ recommendation: 'Remove X-Powered-By header to reduce attack surface',
797
+ evidence: {
798
+ found: String(xPoweredBy),
799
+ impact: 'Reveals technology stack to potential attackers',
800
+ },
801
+ });
802
+ }
803
+ return createResult({ id: 'security-x-powered-by', name: 'X-Powered-By Header', category: 'security', severity: 'info' }, 'info', 'Not applicable (X-Powered-By header not present)', { recommendation: 'This rule checks for X-Powered-By header disclosure when the header is present' });
804
+ },
805
+ },
806
+ {
807
+ id: 'ssl-sni-support',
808
+ name: 'SNI Support',
809
+ category: 'security',
810
+ severity: 'info',
811
+ description: 'Server should support Server Name Indication (SNI)',
812
+ check: (ctx) => {
813
+ if (ctx.sniSupported === undefined) {
814
+ return createResult({ id: 'ssl-sni-support', name: 'SNI Support', category: 'security', severity: 'info' }, 'info', 'Not applicable (SNI support information unavailable)', { recommendation: 'This rule checks for SNI support when SNI information is available' });
815
+ }
816
+ if (!ctx.sniSupported) {
817
+ return createResult({ id: 'ssl-sni-support', name: 'SNI Support', category: 'security', severity: 'info' }, 'info', 'Server may not support SNI', {
818
+ recommendation: 'Ensure web server supports SNI for proper HTTPS functionality',
819
+ evidence: {
820
+ impact: 'Some older browsers may have issues with SSL certificates without SNI support'
821
+ }
822
+ });
823
+ }
824
+ return createResult({ id: 'ssl-sni-support', name: 'SNI Support', category: 'security', severity: 'info' }, 'info', 'Not applicable (SNI is supported)', { recommendation: 'This rule checks for SNI support issues when SNI is not supported' });
825
+ },
826
+ },
827
+ {
828
+ id: 'sitemap-https-urls',
829
+ name: 'HTTPS URLs in Sitemap',
830
+ category: 'security',
831
+ severity: 'warning',
832
+ description: 'Sitemap should only contain HTTPS URLs',
833
+ check: (ctx) => {
834
+ if (ctx.sitemapHttpUrls === undefined) {
835
+ return createResult({ id: 'sitemap-https-urls', name: 'HTTPS URLs in Sitemap', category: 'security', severity: 'warning' }, 'info', 'Not applicable (sitemap URL data unavailable)', { recommendation: 'This rule checks sitemap URLs for HTTPS usage when sitemap data is available' });
836
+ }
837
+ if (ctx.sitemapHttpUrls > 0) {
838
+ return createResult({ id: 'sitemap-https-urls', name: 'HTTPS URLs in Sitemap', category: 'security', severity: 'warning' }, 'warn', `Sitemap contains ${ctx.sitemapHttpUrls} HTTP URLs`, {
839
+ value: ctx.sitemapHttpUrls,
840
+ recommendation: 'Replace all HTTP URLs in sitemap.xml with HTTPS versions',
841
+ evidence: {
842
+ found: `${ctx.sitemapHttpUrls} HTTP URLs`,
843
+ expected: 'All URLs should use HTTPS',
844
+ impact: 'HTTP URLs in sitemap can cause mixed content issues and indexing confusion'
845
+ }
846
+ });
847
+ }
848
+ return createResult({ id: 'sitemap-https-urls', name: 'HTTPS URLs in Sitemap', category: 'security', severity: 'warning' }, 'pass', 'All sitemap URLs use HTTPS');
849
+ },
850
+ },
851
+ {
852
+ id: 'security-hsts',
853
+ name: 'HSTS Header',
854
+ category: 'security',
855
+ severity: 'info',
856
+ description: 'HTTPS sites should implement HSTS for security',
857
+ check: (ctx) => {
858
+ if (!ctx.isHttps) {
859
+ return createResult({ id: 'security-hsts', name: 'HSTS Header', category: 'security', severity: 'info' }, 'info', 'Not applicable (page is not served over HTTPS)', { recommendation: 'This rule checks HSTS implementation on HTTPS sites only' });
860
+ }
861
+ if (ctx.hasHsts === undefined) {
862
+ return createResult({ id: 'security-hsts', name: 'HSTS Header', category: 'security', severity: 'info' }, 'info', 'Not applicable (HSTS status unavailable)', { recommendation: 'This rule checks for HSTS headers when HSTS information is available' });
863
+ }
864
+ if (!ctx.hasHsts) {
865
+ return createResult({ id: 'security-hsts', name: 'HSTS Header', category: 'security', severity: 'info' }, 'info', 'Missing Strict-Transport-Security header', {
866
+ recommendation: 'Implement HSTS to enforce HTTPS connections',
867
+ evidence: {
868
+ expected: 'Strict-Transport-Security: max-age=31536000; includeSubDomains',
869
+ impact: 'Without HSTS, browsers may still attempt insecure HTTP connections',
870
+ learnMore: 'https://web.dev/strict-transport-security/'
871
+ }
872
+ });
873
+ }
874
+ return createResult({ id: 'security-hsts', name: 'HSTS Header', category: 'security', severity: 'info' }, 'pass', 'HSTS header is present');
875
+ },
876
+ },
877
+ ];