@@ -5,9 +5,11 @@ import (
55 "crypto/tls"
66 "encoding/hex"
77 "encoding/json"
8+ "errors"
89 "flag"
910 "fmt"
1011 "io/ioutil"
12+ "net"
1113 "net/http"
1214 "net/http/cookiejar"
1315 "os"
@@ -24,6 +26,13 @@ var formatType string
2426var outputDir string
2527var rateLimiter chan struct {}
2628
29+ // Timeout handling: abort the whole scan after too many consecutive timeouts
30+ // (host likely went down), resetting the counter whenever a request succeeds.
31+ var scanTimeout time.Duration = 20 * time .Second
32+ var maxConsecutiveTimeouts int = 3
33+ var consecutiveTimeouts int = 0
34+ var scanAborted bool = false
35+
2736// baselineSignature describes the 200-response a site returns for a path that
2837// should NOT exist (soft-404 / SPA catch-all / wildcard routing).
2938type baselineSignature struct {
@@ -73,9 +82,17 @@ func main() {
7382 Allfile := flag .Bool ("all" , false , "try all lists" )
7483 success := flag .Bool ("v" , false , "show success result only" )
7584 rateLimit := flag .Int ("rate" , 0 , "rate limit: requests per second (0 = no limit)" )
85+ timeoutSec := flag .Int ("timeout" , 20 , "per-request timeout in seconds" )
86+ maxTimeouts := flag .Int ("maxtimeouts" , 3 , "abort scan after this many consecutive timeouts" )
7687 flag .Parse ()
7788 formatType = * format
7889 outputDir = * outDir
90+ if * timeoutSec > 0 {
91+ scanTimeout = time .Duration (* timeoutSec ) * time .Second
92+ }
93+ if * maxTimeouts > 0 {
94+ maxConsecutiveTimeouts = * maxTimeouts
95+ }
7996 if ! * gitfile && ! * Sensfile && ! * Envfile && ! * Shellfile {
8097
8198 * Allfile = true
@@ -162,21 +179,33 @@ func main() {
162179
163180 if * gitfile {
164181 for i := 0 ; i < len (paths .Git ); i ++ {
182+ if scanAborted {
183+ break
184+ }
165185 checkurl (* address + paths .Git [i ].Path , paths .Git [i ].Content , paths .Git [i ].Lentgh , "Git" )
166186 }
167187 }
168188 if * Sensfile {
169189 for i := 0 ; i < len (paths .Sensitive ); i ++ {
190+ if scanAborted {
191+ break
192+ }
170193 checkurl (* address + paths .Sensitive [i ].Path , paths .Sensitive [i ].Content , paths .Sensitive [i ].Lentgh , "Sensitive" )
171194 }
172195 }
173196 if * Envfile {
174197 for i := 0 ; i < len (paths .Env ); i ++ {
198+ if scanAborted {
199+ break
200+ }
175201 checkurl (* address + paths .Env [i ].Path , paths .Env [i ].Content , paths .Env [i ].Lentgh , "Env" )
176202 }
177203 }
178204 if * Shellfile {
179205 for i := 0 ; i < len (paths .Shell ); i ++ {
206+ if scanAborted {
207+ break
208+ }
180209 checkurl (* address + paths .Shell [i ].Path , paths .Shell [i ].Content , paths .Shell [i ].Lentgh , "Shell" )
181210 }
182211 }
@@ -193,13 +222,16 @@ func main() {
193222}
194223
195224func checkurl (url string , content string , len string , category string ) {
225+ if scanAborted {
226+ return
227+ }
196228 // Apply rate limiting if enabled
197229 if rateLimiter != nil {
198230 <- rateLimiter // Wait for a token
199231 }
200232
201- // Set timeout of 20 seconds
202- httpcc .Timeout = 20 * time . Second
233+ // Set the per-request timeout
234+ httpcc .Timeout = scanTimeout
203235
204236 resp , err := httpcc .Head (url )
205237
@@ -208,15 +240,22 @@ func checkurl(url string, content string, len string, category string) {
208240 if strings .Contains (err .Error (), "http: server gave HTTP response to HTTPS clien" ) {
209241 os .Exit (3 )
210242 }
211- if strings . Contains (err . Error (), "timeout" ) {
212- fmt . Printf ( "Timeout occurred while checking '%s' \n " , url )
243+ if isTimeout (err ) {
244+ registerTimeout ( url )
213245 return
214246 }
215247
216248 resp , err = httpcc .Get (url )
217-
249+ if err != nil {
250+ if isTimeout (err ) {
251+ registerTimeout (url )
252+ }
253+ return
254+ }
218255 }
219256 if err == nil {
257+ // A response came back: the host is alive, so reset the timeout counter.
258+ consecutiveTimeouts = 0
220259 if ! justsuccess {
221260 fmt .Printf ("Checking '%s', '%s',\n " , url , resp .Status )
222261 }
@@ -276,6 +315,32 @@ func checkurl(url string, content string, len string, category string) {
276315 }
277316}
278317
318+ // isTimeout reports whether an error is a request timeout. It handles both the
319+ // typed net.Error case and the client-timeout ("context deadline exceeded")
320+ // message string, which the plain lowercase "timeout" check used to miss.
321+ func isTimeout (err error ) bool {
322+ if err == nil {
323+ return false
324+ }
325+ var netErr net.Error
326+ if errors .As (err , & netErr ) && netErr .Timeout () {
327+ return true
328+ }
329+ msg := strings .ToLower (err .Error ())
330+ return strings .Contains (msg , "timeout" ) || strings .Contains (msg , "deadline exceeded" )
331+ }
332+
333+ // registerTimeout records a timeout and aborts the scan once too many happen
334+ // consecutively (the counter is reset elsewhere whenever a request succeeds).
335+ func registerTimeout (url string ) {
336+ consecutiveTimeouts ++
337+ fmt .Printf ("⏱️ Timeout while checking '%s' (%d/%d consecutive)\n " , url , consecutiveTimeouts , maxConsecutiveTimeouts )
338+ if consecutiveTimeouts >= maxConsecutiveTimeouts {
339+ scanAborted = true
340+ fmt .Printf ("🚨 %d consecutive timeouts reached — aborting scan\n " , maxConsecutiveTimeouts )
341+ }
342+ }
343+
279344// fetchBody performs a GET and returns the actual body size (in bytes) and the
280345// Content-Type header. Returns (-1, "") if the request fails.
281346func fetchBody (url string ) (int64 , string ) {
@@ -506,7 +571,7 @@ func printResults(results map[string][]string) {
506571// Add new function for site availability check
507572func checkSiteIsUp (url string ) bool {
508573 client := & http.Client {
509- Timeout : 20 * time . Second ,
574+ Timeout : scanTimeout ,
510575 Transport : & http.Transport {
511576 TLSClientConfig : & tls.Config {InsecureSkipVerify : true },
512577 },
0 commit comments