Check if a URL's hostname is in scope. Returns: True if the hostname matches an allowed pattern and is not excluded. False otherwise (including for malformed URLs, empty input, IP addresses).
(self, url: str)
| 36 | self.excluded_classes = [c.lower() for c in (excluded_classes or [])] |
| 37 | |
| 38 | def is_in_scope(self, url: str) -> bool: |
| 39 | """Check if a URL's hostname is in scope. |
| 40 | |
| 41 | Returns: |
| 42 | True if the hostname matches an allowed pattern and is not excluded. |
| 43 | False otherwise (including for malformed URLs, empty input, IP addresses). |
| 44 | """ |
| 45 | if not url or not isinstance(url, str): |
| 46 | return False |
| 47 | |
| 48 | # Ensure we have a scheme for urlparse |
| 49 | normalized = url if "://" in url else f"https://{url}" |
| 50 | |
| 51 | try: |
| 52 | parsed = urlparse(normalized) |
| 53 | except Exception: |
| 54 | return False |
| 55 | |
| 56 | hostname = parsed.hostname |
| 57 | if not hostname: |
| 58 | return False |
| 59 | |
| 60 | hostname = hostname.lower() |
| 61 | |
| 62 | # IP address check — not supported, return False with warning |
| 63 | if _is_ip(hostname): |
| 64 | print( |
| 65 | f"WARNING: scope checker does not support IP addresses: {hostname}", |
| 66 | file=sys.stderr, |
| 67 | ) |
| 68 | return False |
| 69 | |
| 70 | # Strip port if present (urlparse handles this, but be safe) |
| 71 | # hostname from urlparse should already exclude port |
| 72 | |
| 73 | # Check exclusion list first |
| 74 | for excluded in self.excluded_domains: |
| 75 | if _domain_matches(hostname, excluded): |
| 76 | return False |
| 77 | |
| 78 | # Check allowlist |
| 79 | for pattern in self.domains: |
| 80 | if _domain_matches(hostname, pattern): |
| 81 | return True |
| 82 | |
| 83 | return False |
| 84 | |
| 85 | def is_vuln_class_allowed(self, vuln_class: str) -> bool: |
| 86 | """Check if a vulnerability class is allowed by the program.""" |