MCPcopy Create free account
hub / github.com/TheSecuredAnalyst/security-suite / WebCrawler

Class WebCrawler

modules/webscanner/crawler.py:15–170  ·  view source on GitHub ↗

Crawl website to discover pages, forms, and endpoints.

Source from the content-addressed store, hash-verified

13class WebCrawler(WebScannerModule):
14 """Crawl website to discover pages, forms, and endpoints."""
15
16 name = "crawler"
17 description = "Crawl website to discover pages, forms, and API endpoints"
18
19 def __init__(self, max_pages: int = 100, max_depth: int = 3):
20 super().__init__()
21 self.max_pages = max_pages
22 self.max_depth = max_depth
23
24 async def run(self, target: Target) -> ScanResult:
25 """Crawl target website."""
26 result = self.create_result(target)
27
28 base_url = self._build_url(target)
29 base_domain = urlparse(base_url).netloc
30 self.logger.info(f"Starting crawl of {base_url}")
31
32 visited: set[str] = set()
33 to_visit: list[tuple[str, int]] = [(base_url, 0)] # (url, depth)
34 pages: list[dict] = []
35 forms: list[dict] = []
36 endpoints: set[str] = set()
37 external_links: set[str] = set()
38
39 try:
40 async with HTTPClient() as client:
41 while to_visit and len(visited) < self.max_pages:
42 url, depth = to_visit.pop(0)
43
44 if url in visited or depth > self.max_depth:
45 continue
46
47 visited.add(url)
48
49 try:
50 response = await client.get(url)
51
52 if "text/html" not in response.headers.get("content-type", ""):
53 continue
54
55 page_info = {
56 "url": str(response.url),
57 "status": response.status_code,
58 "title": None,
59 "depth": depth,
60 }
61
62 soup = BeautifulSoup(response.text, "lxml")
63
64 # Get title
65 title_tag = soup.find("title")
66 if title_tag:
67 page_info["title"] = title_tag.text.strip()
68
69 pages.append(page_info)
70
71 # Extract links
72 for link in soup.find_all("a", href=True):

Callers 3

scan_crawlFunction · 0.90
_collect_scan_resultsFunction · 0.90
execute_scanFunction · 0.90

Calls

no outgoing calls

Tested by

no test coverage detected