| 228 | } |
| 229 | |
| 230 | def _run_scan( |
| 231 | self, |
| 232 | scan_id: str, |
| 233 | target: str, |
| 234 | recon_types: List[ReconType], |
| 235 | timeout: int, |
| 236 | ) -> None: |
| 237 | state = self.get_scan_state(scan_id) |
| 238 | if not state: |
| 239 | return |
| 240 | |
| 241 | state.status = "running" |
| 242 | state.started_at = datetime.now() |
| 243 | |
| 244 | runner_map = { |
| 245 | ReconType.TLS_AUDIT: self._run_tls_audit, |
| 246 | ReconType.DNS_PASSIVE: self._run_dns_passive, |
| 247 | ReconType.CONTENT_DISCOVERY: self._run_content_discovery, |
| 248 | } |
| 249 | |
| 250 | try: |
| 251 | with ThreadPoolExecutor(max_workers=len(recon_types)) as pool: |
| 252 | futures = { |
| 253 | pool.submit(runner_map[rt], target): rt for rt in recon_types if rt in runner_map |
| 254 | } |
| 255 | for future in as_completed(futures, timeout=timeout): |
| 256 | rt = futures[future] |
| 257 | try: |
| 258 | result = future.result() |
| 259 | except Exception as exc: |
| 260 | import traceback |
| 261 | logger.error(f"Recon runner {rt.value} raised in {scan_id}: {exc}\n{traceback.format_exc()}") |
| 262 | result = ReconResult( |
| 263 | recon_type=rt, |
| 264 | target=target, |
| 265 | status="error", |
| 266 | error_message=str(exc), |
| 267 | ) |
| 268 | state.results[rt] = result |
| 269 | except TimeoutError: |
| 270 | state.error_message = f"engine timeout after {timeout}s" |
| 271 | logger.warning(f"Recon scan {scan_id} hit engine timeout") |
| 272 | except Exception as exc: |
| 273 | import traceback |
| 274 | state.error_message = str(exc) |
| 275 | logger.error(f"Recon scan {scan_id} engine failed: {exc}\n{traceback.format_exc()}") |
| 276 | |
| 277 | state.status = "completed" |
| 278 | state.completed_at = datetime.now() |
| 279 | self._scan_history.append(scan_id) |
| 280 | |
| 281 | def _run_tls_audit(self, target: str) -> ReconResult: |
| 282 | result = ReconResult(recon_type=ReconType.TLS_AUDIT, target=target) |