@@ -364,11 +364,57 @@ def run(self, args=None) -> Dict[str, Any]:
364364
365365 start_time = time .time ()
366366
367+ # Calculate global timeout: (timeout + retry_delay) * max_retries * estimated_requests
368+ # Estimated requests: 6 engines + 9 detectors = ~15 components, each making ~2-3 requests
369+ estimated_requests = 45 # Conservative estimate
370+ timeout_per_request = self .config .get ('timeout' , 10 )
371+ max_retries = self .config .get ('max_retries' , 3 )
372+ retry_delay = min (0.5 , timeout_per_request / 2 )
373+
374+ # Global timeout: (timeout + retry_delay) * (max_retries + 1) * estimated_requests
375+ global_timeout = (timeout_per_request + retry_delay ) * (max_retries + 1 ) * estimated_requests
376+
377+ # Add a reasonable cap to prevent extremely long scans
378+ global_timeout = min (global_timeout , 300 ) # Max 5 minutes
379+
367380 summary_only = self .config .get ('summary_only' , False )
368381 verbose_mode = self .config .get ('verbose' , False )
369382
370383 if not summary_only and hasattr (self .presenter , 'print_status' ):
371384 self .presenter .print_status (f"Starting analysis of { self .request_manager .target_url } " )
385+ if verbose_mode :
386+ self .presenter .print_status (f"Global timeout set to { global_timeout :.1f} seconds" )
387+
388+ # Early connectivity check
389+ if not summary_only and hasattr (self .presenter , 'print_status' ):
390+ self .presenter .print_status (" Performing connectivity check..." )
391+
392+ connectivity_response = self .request_manager .make_request ()
393+ if not connectivity_response :
394+ if not summary_only and hasattr (self .presenter , 'print_status' ):
395+ self .presenter .print_status (" Target is unreachable, stopping scan" )
396+
397+ # Return early with minimal results
398+ elapsed = time .time () - start_time
399+ results = {
400+ 'target_url' : self .request_manager .target_url ,
401+ 'scan_time' : elapsed ,
402+ 'frameworks' : [],
403+ 'security_headers' : [],
404+ 'server_info' : {},
405+ 'error' : 'Target is unreachable'
406+ }
407+
408+ # Register the scan in the database
409+ self ._record_scan (results , elapsed )
410+
411+ if not summary_only :
412+ self .presenter .print_results (results )
413+
414+ return results
415+
416+ if not summary_only and hasattr (self .presenter , 'print_status' ):
417+ self .presenter .print_status (" Target is reachable, proceeding with analysis..." )
372418
373419 try :
374420 from rich .progress import Progress , SpinnerColumn , TextColumn , BarColumn , TimeElapsedColumn
@@ -378,12 +424,26 @@ def run(self, args=None) -> Dict[str, Any]:
378424 engine_name = engine .__class__ .__name__
379425 if not summary_only and hasattr (self .presenter , 'print_status' ):
380426 self .presenter .print_status (f" Running { engine_name } " )
427+
428+ # Check global timeout before each engine
429+ if time .time () - start_time > global_timeout :
430+ if not summary_only and hasattr (self .presenter , 'print_status' ):
431+ self .presenter .print_status (f" Global timeout reached, stopping scan" )
432+ break
433+
381434 engine .analyze ()
382435
383436 for detector in self .detectors :
384437 detector_name = detector .__class__ .__name__
385438 if not summary_only and hasattr (self .presenter , 'print_status' ):
386439 self .presenter .print_status (f" Running { detector_name } " )
440+
441+ # Check global timeout before each detector
442+ if time .time () - start_time > global_timeout :
443+ if not summary_only and hasattr (self .presenter , 'print_status' ):
444+ self .presenter .print_status (f" Global timeout reached, stopping scan" )
445+ break
446+
387447 detector .detect ()
388448 else :
389449 progress = Progress (
@@ -399,6 +459,11 @@ def run(self, args=None) -> Dict[str, Any]:
399459 engine_task = progress .add_task (f"Running engines" , total = len (self .engines ))
400460
401461 for i , engine in enumerate (self .engines ):
462+ # Check global timeout
463+ if time .time () - start_time > global_timeout :
464+ progress .update (engine_task , description = "Global timeout reached" )
465+ break
466+
402467 engine_name = engine .__class__ .__name__
403468 progress .update (engine_task , description = f"Running engine: { engine_name } " )
404469 engine .analyze ()
@@ -409,6 +474,11 @@ def run(self, args=None) -> Dict[str, Any]:
409474 detector_task = progress .add_task (f"Running detectors" , total = len (self .detectors ))
410475
411476 for i , detector in enumerate (self .detectors ):
477+ # Check global timeout
478+ if time .time () - start_time > global_timeout :
479+ progress .update (detector_task , description = "Global timeout reached" )
480+ break
481+
412482 detector_name = detector .__class__ .__name__
413483 framework_name = detector .FRAMEWORK if hasattr (detector , 'FRAMEWORK' ) else "Unknown"
414484 progress .update (detector_task , description = f"Running { framework_name } Detector" )
@@ -419,12 +489,24 @@ def run(self, args=None) -> Dict[str, Any]:
419489
420490 except ImportError :
421491 for i , engine in enumerate (self .engines , 1 ):
492+ # Check global timeout
493+ if time .time () - start_time > global_timeout :
494+ if not summary_only :
495+ print ("Global timeout reached, stopping scan" )
496+ break
497+
422498 engine_name = engine .__class__ .__name__
423499 if not summary_only :
424500 print (f"Running engine: { engine_name } ({ i } /{ len (self .engines )} )" )
425501 engine .analyze ()
426502
427503 for i , detector in enumerate (self .detectors , 1 ):
504+ # Check global timeout
505+ if time .time () - start_time > global_timeout :
506+ if not summary_only :
507+ print ("Global timeout reached, stopping scan" )
508+ break
509+
428510 print (detector )
429511 detector_name = detector .__class__ .__name__
430512 framework_name = detector .FRAMEWORK if hasattr (detector , 'FRAMEWORK' ) else "Unknown"
0 commit comments