
    j                        S r SSKrSSKrSSKrSSKrSSKrSSKrSSKrSSKrSSK	r	SSK
r
SSKrSSKrSSKrSSKJrJr  SSKJr  SSKJr  SSKJr  SSKJr   SSKrSSKrSr SS
KJrJr  Sr/ SQr 1 Skr!Sr"Sr#\RH                  " S\RJ                  \RL                  -  5      r' " S S\RP                  5      r)S\*S\RV                  4S jr,S\*4S jr-S r.S\/\*   S\04S jr1SFS\*S\*S\\*   4S jjr2SFS\*S\*S\/\*   4S jjr3S\*S\*4S jr4 SGS \*S!\*S"\*S#\Rj                  S$\0S\/\*   4S% jjr6 " S& S'5      r7  SHS(\/\*   S \*S)\RV                  S*\8S+\S'   S\04S, jjr9S-\*S\/\*   4S. jr: SIS \*S#\Rj                  S)\RV                  S/\*S+\S'   S\;4S0 jjr<SIS1\*S2\\8   S\/\=   4S3 jjr>S4\/\=   S\/\=   4S5 jr?S6\=S7\*4S8 jr@S9\/\=   S7\*4S: jrAS;rBS6\=S7\*4S< jrCS6\=S\*4S= jrDS>\\*   S\\E   4S? jrFS \*S@\\E   S\04SA jrGS#\Rj                  S)\RV                  4SB jrHS\R                  4SC jrJSD rK\LSE:X  a  \K" 5         gg! \ a    S	r GNf = f! \ a    S	r GNf = f)Ju  
ffuf_automation.py — Professional ffuf Web Directory Discovery Automation Tool
Author: Areeb Mehdi / Devorions
Purpose: Automate ffuf scanning across multiple authorized domains with structured reporting.

⚠️  LEGAL WARNING — READ BEFORE USE ⚠️
This tool is designed exclusively for:
  - Domains you own and control
  - Domains for which you have written/explicit authorization to test
Unauthorized use against third-party systems is illegal under the Computer Fraud
and Abuse Act (CFAA), the Computer Misuse Act (UK), and equivalent laws worldwide.
The author assumes zero liability for misuse. Use responsibly and ethically.
    N)ThreadPoolExecutoras_completed)datetime)Path)Optional)urlparseTF)Environment
BaseLoader)z/apiz/adminz/loginz
/dashboardz/backupz/configz/corez/uploadsz/devz/testz/.envz/.gitz	/wp-adminz/phpmyadminz/swaggerz/graphqlz	/internalz/debugz/secret>   -  .           z1.0.0zffuf-automationz!(\d+)\s+req/sec.*?Errors:\s*(\d+)c                   ,   ^  \ rS rSrSrU 4S jrSrU =r$ )StatsAwareHandlerH   z7Clears the live stats line before writing a log record.c                    > [         R                  R                  S5        [         R                  R                  5         [        TU ]  U5        g )N[2K)sysstdoutwriteflushsuperemit)selfrecord	__class__s     ffuf_automation.pyr   StatsAwareHandler.emitJ   s1    

%

V     )__name__
__module____qualname____firstlineno____doc__r   __static_attributes____classcell__)r   s   @r   r   r   H   s    A r!   r   
output_dirreturnc                    [         R                  R                  U SS[        R                  " 5       S S35      n[         R
                  " [         R                  R                  U5      SS9  [        R                  " [        5      nUR                  [        R                  5        [        R                  " SS5      n[        [        R                  5      nUR                  [        R                   5        UR#                  U5        [        R$                  " U5      nUR                  [        R                  5        UR#                  U5        UR'                  U5        UR'                  U5        U$ )	z-Configure dual-output logger (stdout + file).logsscan_%Y%m%d_%H%M%Sz.logTexist_okz)[%(asctime)s] [%(levelname)s] %(message)s%Y-%m-%d %H:%M:%S)ospathjoinr   nowmakedirsdirnamelogging	getLogger	TOOL_NAMEsetLevelDEBUG	Formatterr   r   r   INFOsetFormatterFileHandler
addHandler)r*   log_fileloggerfmtchfhs         r   setup_loggingrH   S   s    ww||J%}7UUY0Z[HKK)D9y)F
OOGMM"


GI\
]C 
3::	&BKKOOC 
		X	&BKKOOC
b
bMr!   c                  V    [         R                  " SS/SSS9n U R                  S:X  ap  U R                  R	                  5       n[         R                  " SS/SSS9nUR                  R	                  5       =(       d    UR
                  R	                  5       nX4$ g! [         a     gf = f)z-Verify ffuf is installed and return its path.whichffufT)capture_outputtextr   z-V)NN)
subprocessrun
returncoder   stripstderrFileNotFoundError)resultr4   verversion_strs       r   check_ffuf_installedrW   o   s    & 1$TR!==&&(D..&$4PC****,B

0@0@0BK$$ s   BB 
B('B(c                  .    S[          S3n [        U 5        g)zPrint the tool banner.u   
[1;36m
╔══════════════════════════════════════════════════════════════════════╗
║          ffuf-automation vuS   — Web Discovery Toolkit           ║
║                  Built by Areeb Mehdi / Devorions                    ║
╚══════════════════════════════════════════════════════════════════════╝
[0m
[1;33m⚠️  AUTHORIZED USE ONLY — You must own or have written permission    ⚠️
⚠️  to test every domain in your input file. Unauthorized scanning    ⚠️
⚠️  is illegal. The author bears no responsibility for misuse.        ⚠️[0m
N)VERSIONprint)banners    r   print_bannerr\      s#      'i (
F 
&Mr!   domainsc                     [        S[        U 5       S35        U  H  n[        SU 35        M     [        5         [        S5      R                  5       R	                  5       nUS:H  $ )z3Ask the user to explicitly confirm before scanning.z
[1;34m[SCOPE] The following z domain(s) will be scanned:[0m     → zd[1;31mDo you confirm you own or have explicit permission to test ALL listed domains? [yes/no]: [0myes)rZ   leninputrQ   lower)r]   danswers      r   confirm_scanrf      sr    	.s7|n<^
_`qcl 	G  A  G  G  I  O  O  QFU?r!   rawschemec                     U R                  5       n U (       a  U R                  S5      (       a  gU R                  S5      (       a  U SS n OU R                  S5      (       a  U SS n U SU R                  S5       3$ )	z
Normalize a raw domain string:
- Strip whitespace / comments
- Apply chosen scheme: http, https, or both
- Remove trailing slashes
#Nzhttp://   zhttps://   z:///)rQ   
startswithrstrip)rg   rh   s     r   normalize_domainrp      sq     ))+C#..%%
~~i  !"g	
	#	#!"gXSC)**r!   filepathc                    [         R                  R                  U 5      (       d  [        SU  35      e/ n[	        5       n[        U 5       nU H  nUS:X  aH  S H@  n[        XV5      nU(       d  M  Xs;  d  M  UR                  U5        UR                  U5        MB     MQ  [        XQ5      nU(       d  Me  Xs;  d  Ml  UR                  U5        UR                  U5        M     SSS5        U$ ! , (       d  f       U$ = f)z?Read and normalize domains from a file, applying chosen scheme.zDomains file not found: both)httphttpsN)	r3   r4   isfilerS   setopenrp   appendadd)rq   rh   r]   seenflinesdomains           r   load_domainsr      s    77>>(##":8* EFFG5D	h1D*A-d6Fv&"4v.(	 + *$76f0NN6*HHV$  
 N 
 Ns$   
!C)/C)68C)2C)9&C))
C8urlc                     [        U 5      nUR                  UR                  -   n[        R                  " SSU5      R                  S5      $ )z*Convert a URL into a filesystem-safe name.z[^\w\-.]_)r   netlocr4   resubrQ   )r   parsednames      r   safe_filenamer      s;    c]F==6;;&D66+sD)//44r!   r   wordlistoutput_fileargsuse_ffuf_recursionc                     SSU S3SU  S3SSSUS	[        UR                  5      S
[        UR                  5      S/nU(       a+  UR                  S:  a  USS[        UR                  5      /-  nUR                  (       a  USUR                  /-  nUR
                  (       a  USUR
                  /-  nUR                  (       a  USUR                  /-  nUR                  (       a  USUR                  /-  nUR                  (       a  USUR                  /-  nUR                  (       a  USUR                  /-  nUR                  (       a  US[        UR                  5      /-  nUR                  (       a  USS/-  nU$ )z/Construct the ffuf command from user arguments.rK   z-wz:FUZZz-uz/FUZZz-ofjsonz-oz-tz-timeoutz-noninteractiver   z
-recursionz-recursion-depthz-ez-mcz-fcz-fwz-fsz-flz-ratez-maxtime-job0)strthreadstimeoutdepth
extensionsmatch_codesfilter_codesfilter_wordsfilter_sizesfilter_linesratemax_results)r   r   r   r   r   cmds         r   build_ffuf_commandr      ss    	
% vkc$,,C%	C djj1n0#djj/BB doo&& t''(( t(()) t(()) t(()) t(()) yyTYY(( $$Jr!   c                      \ rS rSrSrSrS\R                  4S jrS\	R                  S\4S jrS\	R                  4S	 jrS
\S\4S jr\S\4S j5       r\S\4S j5       r\S\4S j5       rS rS rS rS rS rS\4S jrSrg)PauseControlleri  uz  
Manages pause / resume / stop across ALL running ffuf sub-processes.
One instance is shared for the entire scan session.

Keyboard controls (single listener for all parallel scans):
  p  →  pause all     r  →  resume all
  q / Ctrl+C  →  stop all & save partial report

Stats displayed: total req/sec + total errors summed across every
concurrently running ffuf process.
z[[2m  Controls: [0m[1;36m[p][0m pause  [1;32m[r][0m resume  [1;31m[q][0m stop & saverD   c                     Xl         SU l        [        R                  " 5       U l        S U l        [        R                  " 5       U l        0 U l        0 U l	        g )NF)
rD   paused	threadingEventdone_old_tcLock_lock_procs_stats)r   rD   s     r   __init__PauseController.__init__#  s?    !)	!(
3524r!   proc
pty_masterc                 *   U R                      XR                  UR                  '   SU R                  UR                  '   SSS5        [        R
                  " U R                  UR                  U4SS9nUR                  5         g! , (       d  f       NK= f)z>Register a new ffuf process and start its stats reader thread.)r   r   NT)targetr   daemon)r   r   pidr   r   Thread_read_stats_forstart)r   r   r   ts       r   registerPauseController.register/  sk    ZZ$(KK!$*DKK!  ''txx.DT
 	
	 Zs   2B
Bc                     U R                      U R                  R                  UR                  S 5        U R                  R                  UR                  S 5        S S S 5        g ! , (       d  f       g = fN)r   r   popr   r   )r   r   s     r   
unregisterPauseController.unregister9  s?    ZZKKOODHHd+KKOODHHd+ ZZs   AA##
A1r   fdc                 V   [         R                  " S5      nSnU R                  R                  5       (       Gd4   [        R                  " U// / S5      u  n  nU(       d1  U R
                     XR                  ;  a
   S S S 5        g  S S S 5        Mw  [        R                  " US5      nU(       d  g XG-   SS  nUR                  SU5      R                  SSS	9n[        R                  U5      n	U	(       aW  U R
                     [        U	R                  S
5      5      [        U	R                  S5      5      4U R                  U'   S S S 5        O U R                  R                  5       (       d  GM3  g g ! , (       d  f       N= f! , (       d  f       NC= f! [          a     g ["         a     g f = f)Ns   \x1b\[[0-9;]*[a-zA-Z]|\rr!   g?i   i     utf-8replaceerrors      )r   compiler   is_setselectr   r   r3   readr   decode	_STATS_REsearchintgroupr   OSError	Exception)
r   r   r   _ansibufrr   chunkrM   ms
             r   r   PauseController._read_stats_for?  sK   

78))""$$ --b"c:1akk1! $1 $ C({EF+yys+22792M$$T*,/
OS_+MC( $  ))""$$
 $ $  sa   1F +E.<F F F /AF >AE? F .
E<8F ?
F	F F 
F(	F('F(r+   c                     U R                      [        S U R                  R                  5        5       5      sS S S 5        $ ! , (       d  f       g = f)Nc              3   *   #    U  H	  oS    v   M     g7f)r   Nr"   .0r~   s     r   	<genexpr>1PauseController._total_req_sec.<locals>.<genexpr>\       :%9t%9   r   sumr   valuesr   s    r   _total_req_secPauseController._total_req_secY  .    ZZ:T[[%7%7%9:: ZZ   *A
Ac                     U R                      [        S U R                  R                  5        5       5      sS S S 5        $ ! , (       d  f       g = f)Nc              3   *   #    U  H	  oS    v   M     g7f)r   Nr"   r   s     r   r   0PauseController._total_errors.<locals>.<genexpr>a  r   r   r   r   s    r   _total_errorsPauseController._total_errors^  r   r   c                 z    U R                      [        U R                  5      sS S S 5        $ ! , (       d  f       g = fr   )r   ra   r   r   s    r   _active_countPauseController._active_countc  s    ZZt{{# ZZs   ,
:c                     U R                   nU R                  nU R                  nU(       a  SOSn[        R                  R                  SU SUS SU SU S3	5        [        R                  R                  5         g )	N3132u&     [2m↳[0m  [2mscans:[0m [1;35mz[0m  [2mreq/sec:[0m [1;36mz<6u   [0m  [2m│  errors:[0m [1;r   z[0m          )r   r   r   r   r   r   r   )r   rpserrnerr_cols        r   _print_statsPauseController._print_statsh  s    !!    $4

../S 1003Bx 8118	3% @	
 	

r!   c                    U R                   (       a  g U R                     [        U R                  R	                  5       5      nS S S 5        W H#  n UR                  [        R                  5        M%     SU l         [        R                  R                  S5        [        SSS9  g ! , (       d  f       Nh= f! [         a     Mv  f = f)NTr   ue  
[1;33m  ┌──────────────────────────────────────┐
  │  ⏸  ALL SCANS PAUSED                  │
  │  [r] resume  │  [q] stop & save report│
  └──────────────────────────────────────┘[0mr   )r   r   listr   r   send_signalsignalSIGSTOPr   r   r   r   rZ   r   procsps      r   pausePauseController.pausew  s    ;;ZZ++-.E Afnn- 
 

%
 "	
 Z
  s   $B%B6%
B36
CCc                    U R                   (       d  g U R                     [        U R                  R	                  5       5      nS S S 5        W H#  n UR                  [        R                  5        M%     SU l         [        R                  R                  S5        [        SSS9  [        U R                  SS9  g ! , (       d  f       N|= f! [         a     M  f = f)NFr   u2   
[1;32m  ▶  RESUMED — scans continuing...[0mTr   )r   r   r   r   r   r   r   SIGCONTr   r   r   r   rZ   	_CONTROLSr   s      r   resumePauseController.resume  s    {{ZZ++-.E Afnn- 
 

%IQUVdnnD) Z
  s   $B9C
9
C

CCc                    U R                   R                  5         U R                     [        U R                  R                  5       5      nS S S 5        W HS  n U R                  (       a  UR                  [        R                  5        UR                  [        R                  5        MU     g ! , (       d  f       Nh= f! [         a     Mv  f = fr   )r   rw   r   r   r   r   r   r   r   r  SIGINTr   r   s      r   stopPauseController.stop  s    		ZZ++-.E A;;MM&..1fmm,	  Z  s   $B-AB>-
B;>
CCc                    [         (       a#  [        R                  R                  5       (       d  g [        R                  R	                  5       n [
        R                  " U5      U l        [        R                  " U5         U R                  R                  5       (       Gd  [        R                  " [        R                  // / S5      u  n  nU R                  (       d  U R                  5         U(       d  Mv  [        R                  R!                  S5      nUS;   a  U R#                  5         OfUS;   a  U R%                  5         OOUS;   aI  U R'                  5         [(        R*                  " [(        R,                  " 5       [.        R0                  5        O"U R                  R                  5       (       d  GM  U R5                  U5        g ! [
        R                   a     g f = f! [2         a     N8f = f! U R5                  U5        f = f)Ng333333?r   )r  P)r   R)qQ)HAS_TTYr   stdinisattyfilenotermios	tcgetattrr   ttysetrawerrorr   r   r   r   r   r   r  r  r  r3   killgetpidr   r
  r   _restore_terminal)r   r   r   r   keys        r   listenPauseController.listen  sc   wcii..00YY	",,R0DLJJrN	'ii&&(( --RTB1a{{%%'iinnQ'*$JJLJ&KKM..IIKGGBIIK7 ii&&(($ ""2&+ }} 		$  		 ""2&sC   1G DG! G1 G! GG!
G.+G1 -G..G1 1Hc                     U R                   b9   [        R                  " U[        R                  U R                   5        S U l         g g ! [         a     Nf = fr   )r   r  	tcsetattr	TCSADRAINr   )r   r   s     r   r  !PauseController._restore_terminal  sO    <<#!!"g&7&7F  DL $  s   0A 
AA)r   r   r   r   r   rD   r   N)r#   r$   r%   r&   r'   r  r9   Loggerr   rN   Popenr   r   r   r   propertyr   r   r   r   r  r  r  r   r  r(   r"   r!   r   r   r     s    
	+ 	5w~~ 	5Z-- 3 ,z// ,3 C 4 ; ; ; ;s ; ; $s $ $
**
'< C  r!   r   r   rD   timeout_limitctrlc           
      
  ^^ UR                  SU 35        UR                  SSR                  U 5       35        USL nSn [        R                  " 5       u  px[
        R                  " U U[
        R                  S9m[        R                  " U5        U(       aR  [        U5      n[        R                  " UR                  SS9nUR                  5         [        [        R                   SS9  UR#                  TU5        / mUU4S	 jn	[        R                  " U	SS9n
U
R                  5          TR%                  US
9   UR5                  T5        U(       a0  UR(                  R+                  5         U(       a  UR                  SS
9   [        R                  " U5        [8        R:                  R=                  S5        [8        R:                  R?                  5         U
R                  SS
9  SR                  T5      RA                  SSS9nTRB                  S:X  a  UR                  SU 35        gSURE                  5       ;   d  TRB                  S:X  a6  UR-                  STRB                   SU SURG                  5       SS  35        gUR3                  SU SURG                  5       SS  35        g! [&         a    U(       a0  UR(                  R+                  5         U(       a  UR                  SS
9  UR-                  SU 35         TR%                  SS
9  e ! [
        R.                   a#    TR1                  5         TR%                  5          e f = f[
        R.                   Ga    U(       a0  UR(                  R+                  5         U(       a  UR                  SS
9  TR1                  5         TR%                  5         UR3                  SU 35         UR5                  T5        U(       a0  UR(                  R+                  5         U(       a  UR                  SS
9   [        R                  " U5        O! [6         a     Of = f[8        R:                  R=                  S5        [8        R:                  R?                  5         gf = f! [6         a     GNf = f! UR5                  T5        U(       a0  UR(                  R+                  5         U(       a  UR                  SS
9   [        R                  " U5        O! [6         a     Of = f[8        R:                  R=                  S5        [8        R:                  R?                  5         f = f! [&         a    e [H         a!  nUR3                  SU SU 35         SnAgSnAff = f) a  
Execute ffuf as a subprocess.
Pass a shared PauseController via `ctrl` for parallel / session-wide control.
When `ctrl` is None a standalone controller + keyboard listener are created.
Returns True on success, False on failure, raises KeyboardInterrupt on stop.
[SCAN] Starting: z[CMD]  N)r   rR   Tr   r   r   c                  N   > TR                    H  n TR                  U 5        M     g r   )rR   ry   )r}   r   
stderr_bufs    r   _read_stderrrun_ffuf.<locals>._read_stderr  s    !!$' $r!   r   r   z.[INTERRUPT] Waiting for ffuf to save results:    z[TIMEOUT] Scan timed out for r   Fr!   r   r   r   r   z[DONE] Completed: z
no resultsr   z[WARN] ffuf exited z for : r   z[FAIL] ffuf error for i,  z"[ERROR] Unexpected error scanning )%infodebugr5   ptyopenptyrN   r'  PIPEr3   closer   r   r   r   r   rZ   r  r   waitKeyboardInterruptr   rw   warningTimeoutExpiredr  r  r   r   r   r   r   r   r   rP   rc   rQ   r   )r   r   rD   r)  r*  own_ctrl
key_threadr   	pty_slaver1  stderr_threadrR   er   r0  s                @@r   run_ffufrE    s\    KK#F8,-
LL6#((3-)*HJN #
??

 	"6*D"))TJJ/++48 	dJ'"$
	( "((TJ"	IImI,. OOD!		OOAO.$ JJ[)JJ1%*%,,WY,G??aKK,VH56v||~-A1E!4T__4EU6(RTU[UaUaUcdhehUiTjklLL1&FLLN4C<P;QRS[ ! 			OOAO.NNKF8TU		"	%  ,, 				 (( 			OOAO.IIKIIKLL8ABOOD!		OOAO.$ JJ[)JJ)	"   OOD!		OOAO.$ JJ[)JJ   9&A3GHs   C*U ,J. <AU R  BU +AU (U .AQ=LQ=4MQ=
MBQ=R AU P. -U .
P;8U :P;;A U <Q==R  
R
U RU A	U S21U 2
S?<U >S??AU  U U8U33U8wordlist_pathc                    / n [        U 5       nU HE  nUR                  5       nU(       d  M  UR                  S5      (       a  M4  UR                  U5        MG     SSS5        U$ ! , (       d  f       U$ = f! [         a     U$ f = f)zCRead words from a wordlist file, skipping blank lines and comments.rj   N)rx   rQ   rn   ry   IOError)rF  wordsr|   r}   ws        r   load_wordlist_wordsrK  9  s}    E- AJJL1Q\\#..LLO  ! L !  L  Ls9   A7 A%A%A%A7 %
A4/A7 4A7 7
BBraw_dirc           
      D   [        UR                  5      n[        USS5      n[        USS5      nU(       aW  [        U5       Vs/ s H  oR                  S5      PM     n	nUR	                  S[        U	5       SU S[        U5       S	35        O Sn	UR	                  S
[        U5       S35        / n
[        5       nSnU /nSn[        UR                  S-   5       GHI  nU(       d&  U(       a$  UR                  R                  5       (       a  Sn  GO/ nU Ht  n[        U5      n[        R                  R                  UU S35      n[        UUR                  UUSS9nU(       d  SR                  U5      nUR!                  UUU45        Mv     / nU H  u  nnn[        R                  R#                  U5      (       a  [        R                  R%                  U5      S:  af  UR	                  SU SU 35        ['        UUR(                  5       H3  nUS   U;  d  M  UR+                  US   5        U
R!                  U5        M5     M  UR!                  UUU45        M     U(       d  GOEUS::  a  U H  u  nnnU(       a$  UR                  R                  5       (       a  Sn  GO [-        UUX$S9nU(       d.  UU :X  a  US:X  a	  XSS4s  s  $ UR1                  SU SU 35        Ms  ['        UUR(                  5       H3  nUS   U;  d  M  UR+                  US   5        U
R!                  U5        M5     M     GOwSn[3        US9n0 n U HR  u  nnnU(       a#  UR                  R                  5       (       a  Sn  O%UR5                  [,        UUUSU5      nUU4UU'   MT     U(       d  [7        U5       H  nUU   u  nn UR9                  5       n['        UUR(                  5       H3  nUS   U;  d  M  UR+                  US   5        U
R!                  U5        M5     U(       d)  UU :X  a#  US:X  a  SnU H  nUR?                  5         M       OIU(       d  M  UR                  R                  5       (       d  M  SnU H  nUR?                  5         M       O   URA                  SS9  U(       a  XSS4s  $ U(       a    OuXR                  :  d  GM  U	b	  US:X  a  U	nOUn/ nU H"  nU H  n UR!                  U SU  35        M     M$     UnUR	                  S US-    S[        U5       S!35        GML     XSU4$ s  snf ! [.         as    ['        UUR(                  5       H3  nUS   U;  d  M  UR+                  US   5        U
R!                  U5        M5     UR	                  S[        U
5       S35        Sn   GM  f = f! [:         a$  nUR=                  SU SU 35        Sn SnAGNSnAff = f! [.         a    SnU H  nUR?                  5         M      GN~f = f! URA                  SS9  f = f)"a"  
Python-level recursion regardless of HTTP response codes (even 404).

When --force-recursion-paths is given:
  depth 0 : domain/FUZZ           (full wordlist at root)
  depth 1 : domain/forcepath/FUZZ (full wordlist inside each forced path)
  depth 2+: domain/forcepath/word/FUZZ ... (full wordlist at every level)

When no --force-recursion-paths:
  every depth uses all wordlist words as sub-path candidates.

Sub-scans at each depth level run in parallel (--parallel-scans threads).

Returns (all_results, base_cmd_str, failed, interrupted).
parallel_scansr   force_recursion_pathsNrm   z[FORCE-RECURSION] z forced entry path(s) from z, then full wordlist (z words) at each deeper levelz[FORCE-RECURSION] Using all z  wordlist word(s) at every depth FT.json)r   r-  
   z[SKIP] Already scanned (depth=z): r   r*  r_   z" partial path(s) saved (cancelled)r   z[WARN] Sub-scan failed (depth=max_workers  z$[ERROR] Parallel scan exception for r5  r<  z[FORCE-RECURSION] Depth z sub-path(s) queued)!rK  r   getattrlstripr6  ra   rw   ranger   r   r   r   r3   r4   r5   r   ry   rv   getsizeparse_ffuf_resultsr   rz   rE  r=  r>  r   submitr   rT   r   r  cancelshutdown)!r   r   rD   rL  r*  wordlist_wordsrN  recursion_paths_filerJ  forced_pathsall_results	seen_urlsbase_cmd_strcurrent_basesinterrupteddepth_level
scan_tasksbasefnamejson_outr   tasks_to_runr   successroot_failedexecutorfuturesr|   futureexc	expansion
next_baseswords!                                    r   run_force_recursive_scanrw  G  s   , *$--8Nd$4a8N"4)@$G/BCW/XY/X!/XY(\):(;;VWkVl m++.~+>*??[] 	^ 23~3F2GGghiKIL MKTZZ!^,4DII$4$4$6$6K #%
!D#D)Eww||Gwe_=H(t}}hafgC"xx}tS(34 " %'#-D#xww~~h''BGGOOH,E,J<[MTFST+Hd6F6FGAxy0!ah/#**1- H
 ##T3$9: $. q '3#c8DII,,.."&K	&sD&DG v~+*:*$EENN%CK=PSTXSY#Z[+Hd6F6FGAxy0!ah/#**1- H' (44  K,HHG&-+7'D#x		 0 0 2 2&* #tVT4PA"&!1GAJ ,8 #".w"7)0h,&,mmoG
 "4Hd>N>N!OA xy8 )ah 7 + 2 21 5 "P
  '46>kQ>N*.K%, !
 &-!4DII$4$4$6$6*.K%, !
 &-!/ #8: !!t!,"$==#'K1,<(	*	$&J%%D%%av&67 & & 'MKK2;?2C2c-FXEYYlmno -r e[88M Zf ) /$:J:JKU894%MM!E(3'..q1 L KK&[)9(::\ ]^"&KJ  ) ,"LL+OPTvUWX[W\)]^&+G,( % " AHHJ !
 !!t!,s   T,T1 A5W"6V1 W"*AW"XW"W"-W"	X
W"1*V.A	V.-V.1
W;WW"WW""%XX
XXX	json_filer   c                    [         R                  R                  U 5      (       d  / $  [        U 5       n[        R
                  " U5      nSSS5        WR                  S/ 5      nU(       a  USU n/ n[        5       nU GH  nUR                  SS5      nX;   a  M  UR                  U5        Sn	UR                  S0 5      =(       d    0 n
[        U
[        5      (       a)  U
R                  S5      =(       d    U
R                  S5      n	UR                  U[        U5      R                  UR                  SS	5      UR                  S
S	5      UR                  SS	5      UR                  SS	5      U	=(       d    SUR                  S0 5      R                  SS5      S.5        GM"     U$ ! , (       d  f       GNa= f! [        R                  [        4 a    / s $ f = f)zf
Parse ffuf JSON output into a list of result dicts.
Returns empty list if file missing or malformed.
Nresultsr   rP  headerslocationLocationstatusr   lengthrI  linesrb   FUZZ)r   r4   r~  r  rI  r  redirectrb   )r3   r4   rv   rx   r   loadJSONDecodeErrorrH  getrw   rz   
isinstancedictry   r   )rx  r   r|   dataresults_rawr   rd  r   r   r  r{  s              r   r\  r\    s   
 77>>)$$	)_99Q<D 
 ((9b)K!,;/FIeeE2c %%	2&,"gt$${{:.I'++j2IH"3-,,%%!,%%!,%%+%%+".b%%,00<	
 		 . MG _  '* 	s.   F6 F$
F6 $
F3.F6 3F6 6GGrz  c                     / nU  HK  nUS   R                  5       n[         H+  nXC;   d  M
  [        U5      nXES'   UR                  U5          MI     MM     U$ )z:Flag results whose paths match known interesting patterns.r4   match_reason)rc   INTERESTING_PATHSr  ry   )rz  interestingr   
path_lowerpatternr_copys         r   find_interestingr    s[    KvY__&
(G$a)0~&""6* )  r!   	scan_dataoutput_pathc                     [        US5       n[        R                  " XS[        S9  SSS5        g! , (       d  f       g= f)zWrite a full JSON report.rJ  r   )indentdefaultN)rx   r   dumpr   )r  r  r|   s      r   write_json_reportr  .  s*    	k3	1		)q#6 
 		s   1
?rc  c                    / SQn[        USSS9 n[        R                  " X2S9nUR                  5         U  H5  nUR	                  U Vs0 s H  ofUR                  US5      _M     sn5        M7     SSS5        gs  snf ! , (       d  f       g= f)z0Write a flat CSV report of all discovered paths.)	r   r   r4   r~  r  rI  r  r  r  rJ  rP  )newline)
fieldnamesN)rx   csv
DictWriterwriteheaderwriterowr  )rc  r  r  r|   writerrowks          r   write_csv_reportr  4  sx    kJ	k3	+q9COO
C
12.
CD  
,	+ D	 
,	+s   9A>	A9%A>9A>>
Bu.  <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ffuf-automation Report — {{ scan_date }}</title>
<style>
  :root {
    --bg: #0d1117; --surface: #161b22; --border: #30363d;
    --text: #e6edf3; --muted: #8b949e; --accent: #58a6ff;
    --green: #3fb950; --yellow: #d29922; --red: #f85149;
    --orange: #d18616; --purple: #bc8cff;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.6; }
  header { background: var(--surface); border-bottom: 1px solid var(--border); padding: 24px 40px; }
  header h1 { font-size: 22px; font-weight: 700; color: var(--accent); }
  header .meta { color: var(--muted); font-size: 12px; margin-top: 4px; }
  .warning { background: #2d1f00; border: 1px solid var(--yellow); border-radius: 6px; padding: 12px 16px; margin: 20px 40px; color: var(--yellow); font-size: 13px; }
  .container { padding: 20px 40px; }
  .section { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 24px; overflow: hidden; }
  .section-header { padding: 14px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
  .section-header h2 { font-size: 15px; font-weight: 600; }
  .badge { font-size: 11px; padding: 2px 10px; border-radius: 20px; font-weight: 600; }
  .badge-blue { background: #1f3050; color: var(--accent); }
  .badge-green { background: #1a3328; color: var(--green); }
  .badge-red { background: #3d1c1c; color: var(--red); }
  .badge-yellow { background: #2d2208; color: var(--yellow); }
  .badge-muted { background: #1c2128; color: var(--muted); }
  .meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; padding: 16px 20px; }
  .meta-item label { display: block; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
  .meta-item span { font-size: 13px; color: var(--text); font-family: monospace; }
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
  th { background: #1c2128; color: var(--muted); text-align: left; padding: 10px 14px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border); }
  td { padding: 9px 14px; border-bottom: 1px solid #1c2128; vertical-align: middle; }
  tr:last-child td { border-bottom: none; }
  tr:hover td { background: #1c2128; }
  .url-cell { font-family: monospace; max-width: 400px; word-break: break-all; }
  .url-cell a { color: var(--accent); text-decoration: none; }
  .url-cell a:hover { text-decoration: underline; }
  .status { display: inline-block; padding: 2px 8px; border-radius: 4px; font-family: monospace; font-weight: 700; font-size: 12px; }
  .s200 { background: #1a3328; color: var(--green); }
  .s301,.s302 { background: #1f3050; color: var(--accent); }
  .s401 { background: #2d2208; color: var(--yellow); }
  .s403 { background: #2d1f00; color: var(--orange); }
  .s500 { background: #3d1c1c; color: var(--red); }
  .s-other { background: #1c2128; color: var(--muted); }
  .interesting-row td { background: #1e1a0e !important; }
  .interesting-tag { font-size: 10px; background: #2d2208; color: var(--yellow); padding: 1px 6px; border-radius: 4px; margin-left: 6px; }
  .summary-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 16px; padding: 20px; }
  .stat-card { background: #1c2128; border-radius: 8px; padding: 16px; text-align: center; border: 1px solid var(--border); }
  .stat-card .num { font-size: 28px; font-weight: 700; color: var(--accent); }
  .stat-card .lbl { font-size: 11px; color: var(--muted); margin-top: 4px; }
  .cmd-block { font-family: monospace; font-size: 12px; background: #1c2128; border-radius: 6px; padding: 14px 16px; margin: 16px 20px; color: #7ee787; border: 1px solid var(--border); white-space: pre-wrap; word-break: break-all; }
  .no-results { padding: 24px; text-align: center; color: var(--muted); font-size: 13px; }
  footer { text-align: center; padding: 24px; color: var(--muted); font-size: 12px; border-top: 1px solid var(--border); margin-top: 24px; }
  .domain-anchor { color: var(--accent); font-size: 12px; text-decoration: none; }
  .toc { padding: 16px 20px; }
  .toc ul { list-style: none; padding: 0; }
  .toc li { padding: 4px 0; }
  .toc a { color: var(--accent); text-decoration: none; font-size: 13px; }
  .toc a:hover { text-decoration: underline; }
</style>
</head>
<body>
<header>
  <h1>🔍 ffuf-automation — Web Discovery Report</h1>
  <div class="meta">Generated: {{ scan_date }} &nbsp;|&nbsp; Total domains: {{ domains|length }} &nbsp;|&nbsp; Total paths found: {{ total_paths }} &nbsp;|&nbsp; Interesting findings: {{ total_interesting }}</div>
</header>

<div class="warning">
  ⚠️ <strong>AUTHORIZED USE ONLY</strong> — This report was generated by ffuf-automation. Scanning must only be performed against domains you own or have explicit written authorization to test.
</div>

<div class="container">

  <!-- Summary Stats -->
  <div class="section">
    <div class="section-header"><h2>📊 Scan Summary</h2></div>
    <div class="summary-grid">
      <div class="stat-card"><div class="num">{{ domains|length }}</div><div class="lbl">Domains Scanned</div></div>
      <div class="stat-card"><div class="num">{{ total_paths }}</div><div class="lbl">Paths Discovered</div></div>
      <div class="stat-card"><div class="num">{{ total_interesting }}</div><div class="lbl">Interesting Paths</div></div>
      <div class="stat-card"><div class="num">{{ failed_domains|length }}</div><div class="lbl">Failed Domains</div></div>
      <div class="stat-card"><div class="num">{{ scan_config.depth }}</div><div class="lbl">Recursion Depth</div></div>
      <div class="stat-card"><div class="num">{{ scan_config.threads }}</div><div class="lbl">Threads</div></div>
    </div>
  </div>

  <!-- Scan Config -->
  <div class="section">
    <div class="section-header"><h2>⚙️ Scan Configuration</h2></div>
    <div class="meta-grid">
      <div class="meta-item"><label>Wordlist</label><span>{{ scan_config.wordlist }}</span></div>
      <div class="meta-item"><label>Extensions</label><span>{{ scan_config.extensions or "None" }}</span></div>
      <div class="meta-item"><label>Match Codes</label><span>{{ scan_config.match_codes or "Default" }}</span></div>
      <div class="meta-item"><label>Filter Codes</label><span>{{ scan_config.filter_codes or "None" }}</span></div>
      <div class="meta-item"><label>Filter Words</label><span>{{ scan_config.filter_words or "None" }}</span></div>
      <div class="meta-item"><label>Filter Sizes</label><span>{{ scan_config.filter_sizes or "None" }}</span></div>
      <div class="meta-item"><label>Threads</label><span>{{ scan_config.threads }}</span></div>
      <div class="meta-item"><label>Timeout</label><span>{{ scan_config.timeout }}s</span></div>
      <div class="meta-item"><label>Recursion Depth</label><span>{{ scan_config.depth }}</span></div>
    </div>
  </div>

  <!-- Table of Contents -->
  <div class="section">
    <div class="section-header"><h2>📋 Domains Index</h2></div>
    <div class="toc">
      <ul>
        {% for d in domains %}
        <li><a href="#{{ d.domain | replace('https://', '') | replace('http://', '') | replace('/', '_') }}">{{ d.domain }}</a>
        &nbsp;
        {% if d.pending %}<span class="badge badge-muted">NOT SCANNED</span>
        {% else %}<span class="badge badge-blue">{{ d.results|length }} paths</span>
        {% if d.interesting %}<span class="badge badge-yellow">{{ d.interesting|length }} interesting</span>{% endif %}
        {% if d.failed %}<span class="badge badge-red">FAILED</span>{% endif %}
        {% endif %}
        </li>
        {% endfor %}
      </ul>
    </div>
  </div>

  <!-- Per-Domain Results -->
  {% for d in domains %}
  {% set anchor = d.domain | replace('https://', '') | replace('http://', '') | replace('/', '_') %}
  <div class="section" id="{{ anchor }}">
    <div class="section-header">
      <h2>🌐 {{ d.domain }}</h2>
      <div>
        {% if d.pending %}<span class="badge badge-muted">NOT SCANNED</span>
        {% else %}
        <span class="badge badge-blue">{{ d.results|length }} paths</span>
        {% if d.interesting %}<span class="badge badge-yellow">{{ d.interesting|length }} interesting</span>{% endif %}
        {% if d.failed %}<span class="badge badge-red">SCAN FAILED</span>{% endif %}
        {% endif %}
      </div>
    </div>

    {% if d.pending %}
    <div class="no-results" style="color:var(--muted)">⏸ Scan was cancelled before this domain was reached.</div>
    {% elif d.command %}
    <div class="cmd-block">$ {{ d.command }}</div>
    {% endif %}

    {% if d.results and not d.pending %}
    <table>
      <thead>
        <tr>
          <th>URL</th>
          <th>Status</th>
          <th>Size</th>
          <th>Words</th>
          <th>Lines</th>
          <th>Redirect</th>
        </tr>
      </thead>
      <tbody>
        {% for r in d.results %}
        {% set is_interesting = r.url in (d.interesting | map(attribute='url') | list) %}
        <tr {% if is_interesting %}class="interesting-row"{% endif %}>
          <td class="url-cell">
            <a href="{{ r.url }}" target="_blank" rel="noopener">{{ r.url }}</a>
            {% if is_interesting %}<span class="interesting-tag">⚠ interesting</span>{% endif %}
          </td>
          <td>
            {% set sc = r.status | string %}
            <span class="status
              {% if sc == '200' %}s200
              {% elif sc in ['301','302'] %}s301
              {% elif sc == '401' %}s401
              {% elif sc == '403' %}s403
              {% elif sc == '500' %}s500
              {% else %}s-other{% endif %}">
              {{ r.status }}
            </span>
          </td>
          <td>{{ r.length }}</td>
          <td>{{ r.words }}</td>
          <td>{{ r.lines }}</td>
          <td style="font-family:monospace;font-size:11px;color:var(--muted)">{{ r.redirect or "—" }}</td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
    {% elif d.failed and not d.pending %}
    <div class="no-results">❌ Scan failed for this domain. Check logs for details.</div>
    {% elif not d.pending and not d.results %}
    <div class="no-results">No results found for this domain.</div>
    {% endif %}

    {% if d.interesting %}
    <div style="border-top:1px solid var(--border);padding:14px 20px;">
      <strong style="color:var(--yellow);font-size:13px;">⚠ Interesting Findings ({{ d.interesting|length }})</strong>
      <table style="margin-top:10px;">
        <thead><tr><th>URL</th><th>Match Reason</th><th>Status</th></tr></thead>
        <tbody>
          {% for i in d.interesting %}
          <tr>
            <td class="url-cell"><a href="{{ i.url }}" target="_blank">{{ i.url }}</a></td>
            <td style="font-family:monospace;color:var(--yellow)">{{ i.match_reason }}</td>
            <td><span class="status s-other">{{ i.status }}</span></td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
    </div>
    {% endif %}
  </div>
  {% endfor %}

  {% if failed_domains %}
  <div class="section">
    <div class="section-header"><h2>❌ Failed Domains</h2><span class="badge badge-red">{{ failed_domains|length }}</span></div>
    <table>
      <thead><tr><th>Domain</th><th>Reason</th></tr></thead>
      <tbody>
        {% for fd in failed_domains %}
        <tr><td>{{ fd.domain }}</td><td style="color:var(--muted)">{{ fd.reason }}</td></tr>
        {% endfor %}
      </tbody>
    </table>
  </div>
  {% endif %}

</div>
<footer>
  ffuf-automation v1.0.0 — Devorions &nbsp;|&nbsp; Report generated {{ scan_date }} &nbsp;|&nbsp; Authorized use only
</footer>
</body>
</html>
c                    [         (       a9  [        [        5       S9nUR                  [        5      nUR
                  " S0 U D6nO[        U 5      n[        US5       nUR                  U5        SSS5        g! , (       d  f       g= f)zERender the HTML report using either Jinja2 or a simple string format.)loaderrJ  Nr"   )	
HAS_JINJA2r	   r
   from_stringHTML_TEMPLATErender_render_html_fallbackrx   r   )r  r  envtmplhtmlr|   s         r   write_html_reportr  )  sa    z.}-{{'Y' %Y/	k3	1	 
 		s   A77
Bc           
          SnU R                  S/ 5       H@  nUSUS    S3-  nUR                  S/ 5       H  nUSUS    S	US
    S	US    S3-  nM     MB     SU R                  SS5       SU S3$ )z.Very basic HTML fallback if Jinja2 is missing.rP  r]   z<h2>r   z</h2>rz  z<p>r   u    — r~  r  zB</p>u   <!DOCTYPE html><html><head><title>ffuf Report</title></head>
<body style="font-family:monospace;background:#0d1117;color:#e6edf3;padding:24px">
<h1>ffuf-automation Report — 	scan_datezK</h1>
<p>Install Jinja2 for the full styled report: pip install jinja2</p>
z
</body></html>)r  )r  rowsrd   r   s       r   r  r  7  s    D]]9b)$q{m5))y"%Ac!E(58U1X;-uMMD & *  )k" => ? 	 r!   
scope_filec                 6   U (       d  g[         R                  R                  U 5      (       d  [        SU  35      e[	        5       n[        U 5       nU H(  n[        U5      nU(       d  M  UR                  U5        M*     SSS5        U$ ! , (       d  f       U$ = f)z2Load a scope whitelist file (one domain per line).NzScope file not found: )r3   r4   rv   rS   rw   rx   rp   rz   )r  scoper|   r}   rd   s        r   
load_scoper  I  s    77>>*%%"8 EFFEE	j	QD &Aq		!  

 L 
	
 Ls   B	*B		
Br  c                     Uc  gX;   $ )z=Return True if domain is in scope (or scope is unrestricted).Tr"   )r   r  s     r   in_scoper  X  s    }?r!   c                   ^ ^^)^* [        T R                  T R                  S9nU(       d'  TR                  S5        [        R
                  " S5        [        [        T SS5      5      nU Vs/ s H  n[        XC5      (       d  M  UPM     nnU Vs/ s H  n[        XC5      (       a  M  UPM     nnU(       a  TR                  S[        U5       S35        UnU(       d'  TR                  S5        [        R
                  " S5        [        U5      (       d'  TR                  S	5        [        R
                  " S
5        [        R                  R                  T R                  S5      m*[        R                  R                  T R                  S5      n[        R                   " T*SS9  [        R                   " USS9  [        R                   " [        R                  R                  T R                  S5      SS9  ["        R$                  " 5       R'                  S5      n/ n	/ n
/ nS
nS
nT R(                  T R*                  T R,                  T R.                  T R0                  T R2                  [        T SS5      T R4                  T R6                  T R8                  T R                  [        T SS5      S.n[;        T5      m)[<        R>                  " T)R@                  SS9nURC                  5         [E        [:        RF                  SS9  [        T SS5      nSnS[H        4U U)UU*4S jjn[K        US9nU Vs0 s H  nURM                  UU5      U_M     nnTRO                  S[        U5       SU S35         [Q        U5       GH[  nUU   n URS                  5       u  nnnnnnUS:X  a,  URW                  US!S".5        U	RW                  US/ / US#.5        OUS$:X  a  U	RW                  USS/ / S S%.5        OU[        U5      -  nU[        U5      -  nU H@  nUS&   U Vs1 s H  nUS&   iM
     sn;   nU
RW                  0 UEUU(       a  S'OS S(.E5        MB     U	RW                  USUUUS#.5        TRO                  S)[        U5       S*[        U5       S+U S,35        U(       d"  T)RX                  R[                  5       (       d  GMA  SnU H  n U R]                  5         M       O    URa                  SS-9  T)RX                  Rc                  5         UR                  S.S/9  U(       ae  TR                  S05        U	 Vs1 s H  oDS   iM	     snU Vs1 s H  oDS   iM	     sn-  n!U H#  nUU!;  d  M  U	RW                  USS/ / S S%.5        M%     U	(       d'  TR                  S15        [        R
                  " S
5        US2[d         3UU	UUUS3.n"["        R$                  " 5       R'                  S45      n#U(       a  S5OS n$[        R                  R                  US6U# U$ S735      n%[        R                  R                  US6U# U$ S835      n&[        R                  R                  US6U# U$ S935      n'[g        U"U%5        TRO                  S:U% 35        [i        U
U&5        TRO                  S;U& 35        [k        U"U'5        TRO                  S<U' 35        U(       a  S=OS>n([E        S?S@ SAU( SBS@ SC[        U	5       SDU SEU SF[        U5       SGU S?S@ SH35        gs  snf s  snf s  snf ! [T         a/  nTR                  SU SU 35        S/ / S S4u  nnnnn SnAGNSnAff = fs  snf ! [^         a    SnU H  n U R]                  5         M      GNf = f! URa                  SS-9  T)RX                  Rc                  5         UR                  S.S/9  f = fs  snf s  snf )IuA   Main orchestration loop — scan each domain and collect results.)rh   z%No valid domains found in input file.r   r  Nz[SCOPE] Skipping z domain(s) not in scope file.z&All domains are out of scope. Exiting.zScan cancelled by user.r   rg   reportsTr0   r-   r2   r   rN  )r   r   r   r   r   r   r   r   r   r   rh   rN  r.  r   Fr   c                 T  > TR                   R                  5       (       a  U S/ / SS4$ TR                  SU  35        [        T
SS5      (       a0  [	        U T
TTTS9u  pp4U(       a  U S/ / US4$ [        U5      nU SXX$4$ [        U 5      n[        R                  R                  TU S	35      n[        U T
R                  UT
5      nS
R                  U5      n[        R                  R                  U5      (       a8  [        R                  R                  U5      S:  a  TR                  SU  35        O7[        XTTS9n	[        UT
R                   5      nU	(       d  U(       d  U S/ / US4$ [        UT
R                   5      n[        U5      nTR                   R                  5       nU SXX$4$ )u	  
Worker: run a complete scan for one domain.
Runs in a thread-pool worker — no KeyboardInterrupt is raised here.
Stopping is signalled via ctrl.done.
Returns (domain, status, results, interesting, cmd_str, was_interrupted).
status is one of: 'success' | 'failed'
pendingrP  Fr,  force_recursionrS  failedrn  rQ  r-  rR  z*[SKIP] Already scanned (raw file exists): )r   r   r6  rX  rw  r  r   r3   r4   r5   r   r   rv   r[  rE  r\  r   )r   rz  cmd_strscan_failedwas_interruptedr  rk  rl  r   rn  r   r*  rD   rL  s             r   _scan_domain_task#run_scan.<locals>._scan_domain_task  s    999b"b%77'x014*E22=UfgD>:Gk xR%??*73K9gGTT %V,Eww||Gwe_=H)&$--4PCxx}Gww~~h''BGGOOH,E,JHQR"3TB,Xt7G7GHw!8RWeCC,Xt7G7GHG*73K"ii..0O9gGTTr!   rT  z[PARALLEL] z domain(s) queued across z parallel worker(s)z![ERROR] Unexpected exception for r5  r  rP  zffuf exited with error)r   reason)r   r  rz  r  commandr  )r   r  r  rz  r  r  r   YES)r   r  r_   z path(s) found, z interesting []rW     r3  u>   
[INTERRUPTED] Scan cancelled — generating partial report...u7   [SKIPPED] No domains completed — no report generated.zffuf-automation v)r  toolscan_configr]   failed_domainstotal_pathstotal_interestingr/   _partialreport_rQ  z.csvz.htmlu   [REPORT] JSON → u   [REPORT] CSV  → u   [REPORT] HTML → zPARTIAL REPORT (cancelled)zSCAN COMPLETEz
[1;36mu   ────────────────────────────────────────────────────────────z
 
z[0m
 Domains scanned : z
 Paths found     : z
 Interesting     : z
 Failed          : z
 Reports         : [0m
)6r   r]   rh   r  r   exitr  rX  r  r>  ra   rf   r3   r4   r5   outputr7   r   r6   strftimer   r   r   r   r   r   r   r   r   r   r   r   r   r   rZ   r  r   r   r]  r6  r   rT   r   ry   r   r   r^  r=  r_  rw   rY   r  r  r  )+r   rD   r]   r  rd   domains_in_scopeout_of_scopereports_dirr  all_domain_dataall_flat_resultsr  r  r  r  rA  rN  	cancelledr  domain_executorr   domain_futuresrr  r~  rz  r  r  r  rs  r   xis_intr|   scannedr  tssuffixjson_report
csv_reporthtml_reportstatus_labelr*  rL  s+   ``                                       @@r   run_scanr  b  sm    4<<<G<= wtWd34E#*A7ahq.@7A&Aw!hq.@AwLA*3|+<*==Z[\G=>   01 ggll4;;.G'',,t{{I6KKK$'KKd+KKT[[&1DA''(;<IONK  ==??++,,,,,,"4><<<<::;;"4)91=K  !(D!!TBJ	/
#
#40 T#3Q7NI(U# (U (UV *nEO F 	0&96A   KK
c'l^#<^<LL_`1#">2F#F+FeQWQ^Q^Q`Ng
 !%%C[&\]&&$!"( 
 9$&&$$!"(  S\1!S%55! AuXK)HKq!E(K)HHF$++,kq,kF\bSXhj,kl !  &&$&{w(  fS\N2B3{CSBTTbcibjjklm $))"2"2"4"4 	'AHHJ (M 3Z 	  d +		"XY(781X;8Q_;`Q_AhKQ_;``FW$&&#)#(#'#%#%#%(   PQ &/y9'+*'.I 
	 	 	1B$Z"F'',,{gbT&,GHK'',,{gbT&,FGJ'',,{gbT&,GHKi-
KK$[M23%z2
KK$ZL12i-
KK$[M23 4=//L	 *  	
 () *= !%& ''( )= !* 
 
] BAB  e@3%PQIQSUWY[]_dIdFge* *I  	AHHJ  
 	  d +		" 9;`s   +\\\'\,\-] \A:] ]
"B] 9] ]> ] ]> -^;_ 
]
$]?] ]

] %];7]> :];;]> >:^8c                     [         R                  " SS[         R                  SS9n U R                  SSSS9  U R                  S	SS
S9  U R                  SSSS9  U R                  S[        SSS9  U R                  SSSSSS9  U R                  SSSSS9  U R                  SSSS9  U R                  S S!SS"S9  U R                  S#S$S%S&S9  U R                  S'S(S)S*S9  U R                  S+S,SS-S9  U R                  S.S/SS0S9  U R                  S1S2SS3S9  U R                  S4[        S5S6S9  U R                  S7[        S8S9S9  U R                  S:[        S;S<S9  U R                  S=[
        S;S>S9  U R                  S?S@[        S;SASB9  U R                  SCSSDS9  U R                  SESFSSGS9  U R                  SHSI/ SJQSKSL9  U R                  SMSN[        SOSPSB9  U R                  SQSRSS[         3ST9  U $ )UNzffuf_automation.pyuP   Professional ffuf web directory discovery automation — AUTHORIZED DOMAINS ONLYa  
Examples:
  python3 ffuf_automation.py --domains domains.txt --wordlist wordlist.txt --output results
  python3 ffuf_automation.py --domains domains.txt --wordlist /path/to/wl.txt \
      --depth 2 --extensions .php,.txt,.bak \
      --match-codes 200,204,301,302,403 --filter-codes 404 \
      --threads 50 --timeout 10 --output results

LEGAL: Only scan domains you own or have explicit written authorization to test.
        )progdescriptionformatter_classepilogz	--domainsTz*Path to domains file (one domain per line))requiredhelpz
--wordlistzPath to wordlist for ffufz--outputrz  z#Output directory (default: results))r  r  z--depthr   z(Recursion depth (default: 1, 0=disabled))typer  r  z--force-recursionr  
store_trueFzXCustom Python-level recursion: recurse into paths regardless of HTTP response (even 404))destactionr  r  z--force-recursion-pathsrO  rP  zcFile with specific paths to force-recurse into (one per line). If omitted, uses all wordlist words.)r  r  r  z--extensionsz(Extensions to append e.g. .php,.txt,.bakz--extensions-fileextensions_filezBFile with extensions to append, one per line (e.g. extensions.txt)z--match-codesr   z200,204,301,302,403,401zMatch HTTP status codesz--filter-codesr   404zFilter out HTTP status codesz--filter-wordsr   zFilter by word count e.g. 10,20z--filter-sizesr   z&Filter by response size e.g. 1234,5678z--filter-linesr   zFilter by line count e.g. 0,1z	--threads(   z$Number of ffuf threads (default: 40)z	--timeoutrR  z,Per-request timeout in seconds (default: 10)z--rater   z%Max requests per second (0=unlimited)z--delayz-Delay in seconds between domains (default: 0)z--max-resultsr   z$Max results per domain (0=unlimited))r  r  r  r  z--scopezOptional scope whitelist filez--interesting-fileinteresting_filez1File with interesting path patterns, one per linez--schemeru   )rt   ru   rs   z6Protocol to use: http, https, or both (default: https))r  choicesr  z--parallel-scansrN  r  zVNumber of parallel sub-scans per depth level when --force-recursion is on (default: 3)z	--versionversionzffuf-automation )r  r  )argparseArgumentParserRawDescriptionHelpFormatteradd_argumentr   floatrY   )parsers    r   build_parserr  K  s   $$!f <<		F  D@lm
D@[\

9Chi
	QEop
+2CLbgw  y
18OY[ C  D
2@jk
+2CRa  c
Ha  iB  C
(Trs
(Qrs
(Qyz
(Qpq
RFlm
RFtu
QFmn
	qGvw
CQRY  A
	2?^_
,3ErP  R

WF_U  W
*1AUVu  w
9HXY`XaFbcMr!   c            	         [        5         [        5       n U R                  5       nUR                  (       d  S Ul        UR                  (       d  S Ul        UR
                  (       d  S Ul        UR                  (       d  S Ul        UR                  S:X  a  S Ul        [        5       u  p#U(       d7  [        S5        [        S5        [        S5        [        R                  " S5        [        SU SU S35        UR                  (       Ga[  [        R                  R                  UR                  5      (       d/  [        S	UR                   S
35        [        R                  " S5        [!        UR                  5       nU Vs/ s HP  nUR#                  5       (       d  M  UR#                  5       R%                  S5      (       a  M@  UR#                  5       PMR     nnS S S 5        W Vs/ s H  owR%                  S5      (       a  UOSU 3PM!     nnU Vs/ s H  ow[&        ;  d  M  UPM     nn[&        R)                  U5        [        S[+        U5       SUR                   S35        UR,                  (       Ga  [        R                  R                  UR,                  5      (       d/  [        SUR,                   S
35        [        R                  " S5        [!        UR,                  5       nU Vs/ s HP  nUR#                  5       (       d  M  UR#                  5       R%                  S5      (       a  M@  UR#                  5       PMR     n	nS S S 5        W	 V
s/ s H  oR%                  S5      (       a  U
OSU
 3PM!     n	n
UR.                  (       a4  UR.                  R1                  S5       V
s/ s H  o(       d  M  U
PM     sn
O/ nX V
s/ s H  oU;  d  M
  U
PM     sn
-   nSR3                  U5      Ul        [        S[+        U	5       SUR,                   SUR.                   S35        UR4                  (       a^  [        R                  R                  UR4                  5      (       d/  [        SUR4                   S
35        [        R                  " S5        OS Ul        [        R                  R                  UR6                  5      (       d/  [        SUR6                   S
35        [        R                  " S5        [9        UR:                  5      n [=        X5        g s  snf ! , (       d  f       GNM= fs  snf s  snf s  snf ! , (       d  f       GN= fs  sn
f s  sn
f s  sn
f ! [>         a*    URA                  S5        [        R                  " S5         g [B         a3  n
URE                  SU
 3SS9  [        R                  " S5         S n
A
g S n
A
ff = f)Nr   z8[1;31m[ERROR] ffuf is not installed or not in PATH.[0mz5Install it: go install github.com/ffuf/ffuf/v2@latestz"Or: apt install ffuf  (Kali Linux)r   z[1;32m[OK] ffuf found: z  r  z1[1;31m[ERROR] Interesting paths file not found: z[0mrj   rm   z[1;32m[OK] Loaded z" interesting path pattern(s) from z*[1;31m[ERROR] Extensions file not found: .,z extension(s) from r5  z5[1;31m[ERROR] Force-recursion paths file not found: z#[1;31m[ERROR] Wordlist not found: z(
[INTERRUPTED] Scan interrupted by user.z[FATAL] Unexpected error: T)exc_info)#r\   r  
parse_argsr  r   r   r   r   rW   rZ   r   r  r  r3   r4   rv   rx   rQ   rn   r  extendra   r  r   splitr5   rO  r   rH   r  r  r=  r>  r   r  )r  r   	ffuf_pathffuf_verr|   r}   customr  added	file_extsrD  existingmergedrD   s                 r   mainr    s   N^FD ::
   1 /0INOEF23	'	{"XJi
HI ww~~d3344HI^I^H__fghHHQK$''(A)*)*::< (,

(?(?(D 

   )
 @FFv!||C((!!g5vF"AFq/@&@FA  '&s6{m3UVZVkVkUlluvw ww~~d2233A$BVBVAWW^_`HHQK$&&'1)*)*::< (,

(?(?(D 

   ( CLL)Q,,s++Q1QC8)	LCG??t44S9?9!QA9?XZ	G	1h5FQ	GG((6*&s9~&66I$J^J^I__abfbqbqarr{|} !!ww~~d8899LTMgMgLhhopqHHQK%)" 77>>$--((6t}}oWMN 4;;'F[ )(
 GA (' M?G.  BC 1!5Es   <T T"TTT &&T2T7#T7)U.T<"T<1T<U&U)
U7U	UUU" T  
T/<U
U"1W	W)WW__main__)ru   )T)rV  Nr   )Mr'   r  r  r   r9   r3   r8  r   r   r   rN   r   r   timeconcurrent.futuresr   r   r   pathlibr   typingr   urllib.parser   r  r  r  ImportErrorjinja2r	   r
   r  r  HIGHLIGHT_CODESrY   r;   r   
IGNORECASEDOTALLr   StreamHandlerr   r   r&  rH   rW   r\   r   boolrf   rp   r   r   	Namespacer   r   r   rE  rK  tuplerw  r  r\  r  r  r  r  r  r  rw   r  r  r  r  r  r  r#   r"   r!   r   <module>r     s    
   	 
 	    
   ?    !G.J  1
	 JJ;R]]RYY=VW	
-- c gnn 8c   $s)  +# +s +# +$3  $s) .5s 5s 5  $777 7 

	7
 7 
#Y7@}  } N (,a	caa NNa 	a
 $
%a 
aNs tCy & )-a9a9


a9 NNa9 	a9
 $
%a9 a9N,# ,HSM ,TRVZ ,^d4j T$Z "7 7C 7E$t* E3 EhV C T c $8C= Xc] S # 4 c8%% cw~~ cR/h-- /jTn zF a-  G  Js$   
H9 #
I 9IIII