Repository: hahwul/dalfox

Issue:
Performance improvements
`dalfox`'s CPU usage seems quite high: it can go up to 400% for a single URL ! 

Should we open a thread for perf improvements maybe by profiling the code to see where it's spending time exactly: most of `dalfox` functionality should be I/O-bound, not CPU-bound, since the main goal is to make requests.

Discussion:
@hahwul: @ocervell 
Thank you for your feedback on dalfox's CPU usage. I completely see where you're coming from, and I agree that this area could use improvement. I'll prioritize performance enhancements and start addressing this right away!@iamroh4an: +1
@iamroh4an: > `dalfox`'s CPU usage seems quite high: it can go up to 400% for a single URL !
> 
> Should we open a thread for perf improvements maybe by profiling the code to see where it's spending time exactly: most of `dalfox` functionality should be I/O-bound, not CPU-bound, since the main goal is to make requests.

for now you can use "cpulimit" until this is fixed
@hahwul: @ocervell @iamroh4an 
Hi there!

I’m here to share some updates on the performance improvements I’ve been working on. I’ve optimized the parameter analysis, particularly the Param mining feature, and the XSS scanning to reduce potential bottlenecks. Additionally, I’ve set the default CPU usage to one. My tests show that even though fewer CPUs are being used, the scan time has actually decreased. If you’d like to use more CPUs, you can adjust this setting using the `--max-cpu` flag.

```
--max-cpu int                 Set the maximum number of CPUs to use. Example: --max-cpu 1 (default 1)
```

These changes will be included in the upcoming release, version 2.10.0, which I plan to roll out by the end of March. Throughout March, I’ll be continuously working on code improvements and exploring further ways to enhance performance.

Thank you very much for reporting the issue and sharing your ideas! Have a great day!

Solution patch:
diff --git a/cmd/args.go b/cmd/args.go
index f40e633..bffd2b4 100644
--- a/cmd/args.go
+++ b/cmd/args.go
@@ -31,6 +31,7 @@ type Args struct {
 	Timeout           int
 	Delay             int
 	Concurrence       int
+	MaxCPU            int
 	OnlyDiscovery     bool
 	Silence           bool
 	Mining            bool
diff --git a/cmd/root.go b/cmd/root.go
index 54b9050..bf99246 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"runtime"
 	"time"
 
 	"github.com/hahwul/dalfox/v2/pkg/har"
@@ -78,6 +79,7 @@ func init() {
 	rootCmd.PersistentFlags().IntVar(&args.Timeout, "timeout", 10, "Set the request timeout in seconds. Example: --timeout 10")
 	rootCmd.PersistentFlags().IntVar(&args.Delay, "delay", 0, "Set the delay between requests to the same host in milliseconds. Example: --delay 1000")
 	rootCmd.PersistentFlags().IntVarP(&args.Concurrence, "worker", "w", 100, "Set the number of concurrent workers. Example: -w 100")
+	rootCmd.PersistentFlags().IntVar(&args.MaxCPU, "max-cpu", 1, "Set the maximum number of CPUs to use. Example: --max-cpu 1")
 
 	// Bool
 	rootCmd.PersistentFlags().BoolVar(&args.OnlyDiscovery, "only-discovery", false, "Only perform parameter analysis, skip XSS scanning. Example: --only-discovery")
@@ -129,6 +131,7 @@ func initConfig() {
 		IgnoreParams:      args.IgnoreParams,
 		Timeout:           args.Timeout,
 		Concurrence:       args.Concurrence,
+		MaxCPU:            args.MaxCPU,
 		Delay:             args.Delay,
 		OnlyDiscovery:     args.OnlyDiscovery,
 		OnlyCustomPayload: args.OnlyCustomPayload,
@@ -184,6 +187,10 @@ func initConfig() {
 		options.OnlyDiscovery = true
 	}
 
+	if args.MaxCPU > 1 {
+		runtime.GOMAXPROCS(args.MaxCPU)
+	}
+
 	if args.Grep != "" {
 		loadFile(args.Grep, "grepping")
 	}
diff --git a/dalfox.go b/dalfox.go
index 588661d..373ca33 100755
--- a/dalfox.go
+++ b/dalfox.go
@@ -4,8 +4,14 @@ Happy hacking :D
 */
 package main
 
-import "github.com/hahwul/dalfox/v2/cmd"
+import (
+	"runtime"
+
+	"github.com/hahwul/dalfox/v2/cmd"
+)
 
 func main() {
+	// Default setting
+	runtime.GOMAXPROCS(1)
 	cmd.Execute()
 }
diff --git a/pkg/model/options.go b/pkg/model/options.go
index 0094bda..97a4878 100644
--- a/pkg/model/options.go
+++ b/pkg/model/options.go
@@ -34,6 +34,7 @@ type Options struct {
 	Trigger           string   `json:"trigger,omitempty"`
 	Timeout           int      `json:"timeout,omitempty"`
 	Concurrence       int      `json:"worker,omitempty"`
+	MaxCPU            int      `json:"maxcpu,omitempty"`
 	Delay             int      `json:"delay,omitempty"`
 	AllURLS           int
 	NowURL            int
@@ -73,6 +74,7 @@ type Options struct {
 	OnlyPoC           string `json:"only-poc,omitempty"`
 	OutputAll         bool   `json:"output-all,omitempty"`
 	WAF               bool
+	WAFName           string `json:"waf-name,omitempty"`
 	WAFEvasion        bool
 	PoCType           string `json:"poc-type,omitempty"`
 	Mutex             *sync.Mutex
diff --git a/pkg/model/result.go b/pkg/model/result.go
index d39bab0..a97b92f 100644
--- a/pkg/model/result.go
+++ b/pkg/model/result.go
@@ -39,4 +39,5 @@ type ParamResult struct {
 	ReflectedPoint string
 	ReflectedCode  string
 	Chars          []string
+	Code           string
 }
diff --git a/pkg/scanning/headless.go b/pkg/scanning/headless.go
index 849de30..982aa00 100644
--- a/pkg/scanning/headless.go
+++ b/pkg/scanning/headless.go
@@ -2,6 +2,7 @@ package scanning
 
 import (
 	"context"
+	"log"
 	"time"
 
 	"github.com/chromedp/cdproto/network"
@@ -10,18 +11,20 @@ import (
 	"github.com/hahwul/dalfox/v2/pkg/model"
 )
 
+var chromeCtx context.Context
+var chromeCancel context.CancelFunc
+
+func init() {
+	chromeCtx, chromeCancel = chromedp.NewContext(context.Background())
+	if chromeCtx == nil {
+		log.Println("Failed to create chrome context")
+	}
+}
+
 // CheckXSSWithHeadless is XSS Testing with headless browser
 func CheckXSSWithHeadless(url string, options model.Options) bool {
-	// create chrome instance
 	check := false
-	ctx, cancel := chromedp.NewContext(
-		context.Background(),
-		//chromedp.WithLogf(log.Printf),
-	)
-	defer cancel()
-
-	// create a timeout
-	ctx, cancel = context.WithTimeout(ctx, 8*time.Second)
+	ctx, cancel := context.WithTimeout(chromeCtx, 8*time.Second)
 	defer cancel()
 
 	chromedp.ListenTarget(ctx, func(ev interface{}) {
@@ -30,49 +33,16 @@ func CheckXSSWithHeadless(url string, options model.Options) bool {
 				check = true
 				cancel()
 			} else {
-				go func() {
-					chromedp.Run(ctx, page.HandleJavaScriptDialog(true))
-				}()
+				chromedp.Run(ctx, page.HandleJavaScriptDialog(true))
 			}
 		}
 	})
 
-	/*
-		var headers map[string]interface{}
-
-
-		if options.Header != "" {
-			h := strings.Split(options.Header, ": ")
-			if len(h) > 1 {
-				headers[h[0]] = h[1]
-			}
-		}
-
-		if options.Cookie != "" {
-			headers["Cookie"] = options.Cookie
-		}
-
-		if options.UserAgent != "" {
-			headers["User-Agent"] = options.UserAgent
-		}
-	*/
-
-	/*
-		var res string
-		err := chromedp.Run(ctx, setheaders(
-			url,
-			headers,
-			&res,
-		))
-	*/
-
 	err := chromedp.Run(ctx,
 		chromedp.Navigate(url),
-		// wait for footer element is visible (ie, page is loaded)
-		// chromedp.WaitVisible(`body > footer`),
 	)
 	if err != nil {
-		//
+		// handle error
 	}
 	return check
 }
diff --git a/pkg/scanning/parameterAnalysis.go b/pkg/scanning/parameterAnalysis.go
new file mode 100644
index 0000000..39973cc
--- /dev/null
+++ b/pkg/scanning/parameterAnalysis.go
@@ -0,0 +1,347 @@
+package scanning
+
+import (
+	"compress/gzip"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/hahwul/dalfox/v2/pkg/model"
+	"github.com/hahwul/dalfox/v2/pkg/optimization"
+	"github.com/hahwul/dalfox/v2/pkg/printing"
+	"github.com/hahwul/dalfox/v2/pkg/verification"
+	voltFile "github.com/hahwul/volt/file"
+	vlogger "github.com/hahwul/volt/logger"
+	voltUtils "github.com/hahwul/volt/util"
+	"github.com/sirupsen/logrus"
+)
+
+var clientPool = sync.Pool{
+	New: func() interface{} {
+		return &http.Client{
+			Timeout: time.Duration(30) * time.Second,
+		}
+	},
+}
+
+func setP(p, dp url.Values, name string, options model.Options) (url.Values, url.Values) {
+	if p.Get(name) == "" {
+		p.Set(name, "")
+	}
+	if options.Data != "" {
+		if dp.Get(name) == "" {
+			dp.Set(name, "")
+		}
+	}
+	return p, dp
+}
+
+func parseURL(target string) (*url.URL, url.Values, url.Values, error) {
+	u, err := url.Parse(target)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+	p, _ := url.ParseQuery(u.RawQuery)
+	dp := url.Values{}
+	return u, p, dp, nil
+}
+
+func addParamsFromWordlist(p, dp url.Values, wordlist []string, options model.Options) (url.Values, url.Values) {
+	for _, param := range wordlist {
+		if param != "" {
+			p, dp = setP(p, dp, param, options)
+		}
+	}
+	return p, dp
+}
+
+func addParamsFromRemoteWordlists(p, dp url.Values, options model.Options) (url.Values, url.Values) {
+	rw := strings.Split(options.RemoteWordlists, ",")
+	for _, endpoint := range rw {
+		var wordlist []string
+		var line, size string
+		if endpoint == "burp" {
+			wordlist, line, size = getBurpWordlist()
+		} else if endpoint == "assetnote" {
+			wordlist, line, size = getAssetnoteWordlist()
+		}
+		if line != "" {
+			printing.DalLog("INFO", "A '"+endpoint+"' wordlist has been loaded ["+line+"L / "+size+"]", options)
+			p, dp = addParamsFromWordlist(p, dp, wordlist, options)
+		}
+	}
+	return p, dp
+}
+
+func findDOMParams(target string, p, dp url.Values, options model.Options) (url.Values, url.Values) {
+	treq := optimization.GenerateNewRequest(target, "", options)
+	if treq != nil {
+		client := &http.Client{
+			Timeout:   time.Duration(options.Timeout) * time.Second,
+			Transport: getTransport(options),
+		}
+		if !options.FollowRedirect {
+			client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+				return nil
+			}
+		}
+		tres, err := client.Do(treq)
+		if err == nil {
+			defer tres.Body.Close()
+			var reader io.ReadCloser
+			switch tres.Header.Get("Content-Encoding") {
+			case "gzip":
+				reader, err = gzip.NewReader(tres.Body)
+				if err != nil {
+					reader = tres.Body
+				}
+				defer reader.Close()
+			default:
+				reader = tres.Body
+			}
+			bodyString, err := io.ReadAll(reader)
+			if err == nil {
+				body := ioutil.NopCloser(strings.NewReader(string(bodyString)))
+				defer body.Close()
+				doc, err := goquery.NewDocumentFromReader(body)
+				if err == nil {
+					count := 0
+					doc.Find("input, textarea, select, form, a").Each(func(i int, s *goquery.Selection) {
+						name, _ := s.Attr("name")
+						if name == "" {
+							name, _ = s.Attr("action")
+						}
+						if name == "" {
+							name, _ = s.Attr("href")
+						}
+						if name != "" {
+							p, dp = setP(p, dp, name, options)
+							count++
+						}
+					})
+					printing.DalLog("INFO", "Found "+strconv.Itoa(count)+" testing point in DOM base parameter mining", options)
+				}
+			}
+		}
+	}
+	return p, dp
+}
+
+func processParams(target string, paramsQue chan string, results chan model.ParamResult, options model.Options, rl *rateLimiter, miningCheckerLine int, pLog *logrus.Entry) {
+	client := clientPool.Get().(*http.Client)
+	defer clientPool.Put(client)
+	for k := range paramsQue {
+		if optimization.CheckInspectionParam(options, k) {
+			printing.DalLog("DEBUG", "Mining URL scan to "+k, options)
+			tempURL, _ := optimization.MakeRequestQuery(target, k, "DalFox", "PA", "toAppend", "NaN", options)
+			var code string
+			rl.Block(tempURL.Host)
+			resbody, resp, _, vrs, err := SendReq(tempURL, "DalFox", options)
+			if err == nil {
+				wafCheck, wafN := checkWAF(resp.Header, resbody)
+				if wafCheck {
+					options.WAF = true
+					options.WAFName = wafN
+					if options.WAFEvasion {
+						options.Concurrence = 1
+						options.Delay = 3
+						printing.DalLog("INFO", "Set worker=1, delay=3s for WAF-Evasion", options)
+					}
+				}
+			}
+			_, lineSum := verification.VerifyReflectionWithLine(resbody, "DalFox")
+			if miningCheckerLine == lineSum {
+				pLog.Debug("Hit linesum")
+				pLog.Debug(lineSum)
+			}
+			if vrs {
+				code = CodeView(resbody, "DalFox")
+				code = code[:len(code)-5]
+				pointer := optimization.Abstraction(resbody, "DalFox")
+				smap := "Injected: "
+				tempSmap := make(map[string]int)
+				for _, v := range pointer {
+					tempSmap[v]++
+				}
+				for k, v := range tempSmap {
+					smap += "/" + k + "(" + strconv.Itoa(v) + ")"
+				}
+				paramResult := model.ParamResult{
+					Name:           k,
+					Type:           "URL",
+					Reflected:      true,
+					ReflectedPoint: smap,
+					ReflectedCode:  code,
+				}
+				var wg sync.WaitGroup
+				chars := GetSpecialChar()
+				for _, c := range chars {
+					wg.Add(1)
+					char := c
+					go func() {
+						defer wg.Done()
+						encoders := []string{
+							"NaN",
+							"urlEncode",
+							"urlDoubleEncode",
+							"htmlEncode",
+						}
+						for _, encoder := range encoders {
+							turl, _ := optimization.MakeRequestQuery(target, k, "dalfox"+char, "PA-URL", "toAppend", encoder, options)
+							rl.Block(tempURL.Host)
+							_, _, _, vrs, _ := SendReq(turl, "dalfox"+char, options)
+							if vrs {
+								paramResult.Chars = append(paramResult.Chars, char)
+							}
+						}
+					}()
+				}
+				wg.Wait()
+				paramResult.Chars = voltUtils.UniqueStringSlice(paramResult.Chars)
+				results <- paramResult
+			}
+		}
+	}
+}
+
+func ParameterAnalysis(target string, options model.Options, rl *rateLimiter) map[string]model.ParamResult {
+	miningCheckerLine := 0
+	vLog := vlogger.GetLogger(options.Debug)
+	pLog := vLog.WithField("data1", "PA")
+	_, p, dp, err := parseURL(target)
+	params := make(map[string]model.ParamResult)
+	if err != nil {
+		return params
+	}
+
+	for tempP := range p {
+		params[tempP] = model.ParamResult{}
+	}
+
+	if options.Mining {
+		tempURL, _ := optimization.MakeRequestQuery(target, "pleasedonthaveanamelikethis_plz_plz", "DalFox", "PA", "toAppend", "NaN", options)
+		rl.Block(tempURL.Host)
+		resBody, _, _, vrs, _ := SendReq(tempURL, "DalFox", options)
+		if vrs {
+			_, lineSum := verification.VerifyReflectionWithLine(resBody, "DalFox")
+			miningCheckerLine = lineSum
+		}
+
+		if len(options.UniqParam) > 0 {
+			p, dp = addParamsFromWordlist(p, dp, options.UniqParam, options)
+		}
+
+		if options.MiningWordlist == "" {
+			p, dp = addParamsFromWordlist(p, dp, GetGfXSS(), options)
+		} else {
+			ff, err := voltFile.ReadLinesOrLiteral(options.MiningWordlist)
+			if err != nil {
+				printing.DalLog("SYSTEM", "Mining wordlist load fail..", options)
+			} else {
+				p, dp = addParamsFromWordlist(p, dp, ff, options)
+			}
+		}
+
+		if options.RemoteWordlists != "" {
+			p, dp = addParamsFromRemoteWordlists(p, dp, options)
+		}
+	}
+
+	if options.FindingDOM {
+		p, dp = findDOMParams(target, p, dp, options)
+	}
+
+	var wgg sync.WaitGroup
+	concurrency := options.Concurrence
+	paramsQue := make(chan string, concurrency)
+	results := make(chan model.ParamResult, concurrency)
+	miningDictCount := 0
+	mutex := &sync.Mutex{}
+
+	go func() {
+		for result := range results {
+			mutex.Lock()
+			params[result.Name] = result
+			mutex.Unlock()
+		}
+	}()
+
+	for i := 0; i < concurrency; i++ {
+		wgg.Add(1)
+		go func() {
+			processParams(target, paramsQue, results, options, rl, miningCheckerLine, pLog)
+			wgg.Done()
+		}()
+	}
+
+	for v := range p {
+		if len(options.UniqParam) > 0 {
+			for _, selectedParam := range options.UniqParam {
+				if selectedParam == v {
+					paramsQue <- v
+				}
+			}
+		} else if len(options.IgnoreParams) > 0 {
+			for _, ignoreParam := range options.IgnoreParams {
+				if ignoreParam != v {
+					paramsQue <- v
+				}
+			}
+		} else {
+			paramsQue <- v
+		}
+	}
+
+	close(paramsQue)
+	wgg.Wait()
+	close(results)
+
+	var wggg sync.WaitGroup
+	paramsDataQue := make(chan string, concurrency)
+	for j := 0; j < concurrency; j++ {
+		wggg.Add(1)
+		go func() {
+			processParams(target, paramsDataQue, results, options, rl, miningCheckerLine, pLog)
+			wggg.Done()
+		}()
+	}
+
+	for v := range dp {
+		if len(options.UniqParam) > 0 {
+			for _, selectedParam := range options.UniqParam {
+				if selectedParam == v {
+					paramsDataQue <- v
+				}
+			}
+		} else {
+			paramsDataQue <- v
+		}
+	}
+
+	close(paramsDataQue)
+	wggg.Wait()
+	if miningDictCount != 0 {
+		printing.DalLog("INFO", "Found "+strconv.Itoa(miningDictCount)+" testing point in Dictionary base parameter mining", options)
+	}
+	if options.WAF {
+		printing.DalLog("INFO", "Found WAF: "+options.WAFName, options)
+	}
+	return params
+}
+
+// GetPType is Get Parameter Type
+func GetPType(av string) string {
+	if strings.Contains(av, "PTYPE: URL") {
+		return "-URL"
+	}
+	if strings.Contains(av, "PTYPE: FORM") {
+		return "-FORM"
+	}
+	return ""
+}
diff --git a/pkg/scanning/parameterAnlaysis.go b/pkg/scanning/parameterAnlaysis.go
deleted file mode 100644
index 094da92..0000000
--- a/pkg/scanning/parameterAnlaysis.go
+++ /dev/null
@@ -1,442 +0,0 @@
-package scanning
-
-import (
-	"compress/gzip"
-	"io"
-	"io/ioutil"
-	"net/http"
-	"net/url"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/PuerkitoBio/goquery"
-	"github.com/hahwul/dalfox/v2/pkg/model"
-	"github.com/hahwul/dalfox/v2/pkg/optimization"
-	"github.com/hahwul/dalfox/v2/pkg/printing"
-	"github.com/hahwul/dalfox/v2/pkg/verification"
-	voltFile "github.com/hahwul/volt/file"
-	vlogger "github.com/hahwul/volt/logger"
-	voltUtils "github.com/hahwul/volt/util"
-)
-
-func setP(p, dp url.Values, name string, options model.Options) (url.Values, url.Values) {
-	if p.Get(name) == "" {
-		p.Set(name, "")
-	}
-	if options.Data != "" {
-		if dp.Get(name) == "" {
-			dp.Set(name, "")
-		}
-	}
-	return p, dp
-}
-
-// ParameterAnalysis is check reflected and mining params
-func ParameterAnalysis(target string, options model.Options, rl *rateLimiter) map[string][]string {
-	//miningCheckerSize := 0
-	miningCheckerLine := 0
-	vLog := vlogger.GetLogger(options.Debug)
-	pLog := vLog.WithField("data1", "PA")
-	u, err := url.Parse(target)
-	params := make(map[string][]string)
-	if err != nil {
-		return params
-	}
-	var p url.Values
-	var dp url.Values
-
-	if options.Data == "" {
-		p, _ = url.ParseQuery(u.RawQuery)
-	} else {
-		p, _ = url.ParseQuery(u.RawQuery)
-		dp, _ = url.ParseQuery(options.Data)
-	}
-
-	for tempP := range p {
-		params[tempP] = []string{}
-	}
-
-	if options.Mining {
-		tempURL, _ := optimization.MakeRequestQuery(target, "pleasedonthaveanamelikethis_plz_plz", "DalFox", "PA", "toAppend", "NaN", options)
-		rl.Block(tempURL.Host)
-		resBody, _, _, vrs, _ := SendReq(tempURL, "DalFox", options)
-		if vrs {
-			_, lineSum := verification.VerifyReflectionWithLine(resBody, "DalFox")
-			miningCheckerLine = lineSum
-		}
-
-		// Add UniqParam to Mining output
-		if len(options.UniqParam) > 0 {
-			for _, selectedParam := range options.UniqParam {
-				p, dp = setP(p, dp, selectedParam, options)
-			}
-		}
-
-		// Param mining with Gf-Patterins
-		if options.MiningWordlist == "" {
-			for _, gfParam := range GetGfXSS() {
-				if gfParam != "" {
-					p, dp = setP(p, dp, gfParam, options)
-				}
-			}
-		} else {
-			// Param mining with wordlist fil --mining-dict-word
-			ff, err := voltFile.ReadLinesOrLiteral(options.MiningWordlist)
-			if err != nil {
-				printing.DalLog("SYSTEM", "Mining wordlist load fail..", options)
-			} else {
-				for _, wdParam := range ff {
-					if wdParam != "" {
-						p, dp = setP(p, dp, wdParam, options)
-					}
-				}
-			}
-		}
-
-		if options.RemoteWordlists != "" {
-			rw := strings.Split(options.RemoteWordlists, ",")
-			for _, endpoint := range rw {
-				var wordlist []string
-				var line string
-				var size string
-				if endpoint == "burp" {
-					wordlist, line, size = getBurpWordlist()
-
-				}
-				if endpoint == "assetnote" {
-					wordlist, line, size = getAssetnoteWordlist()
-				}
-
-				if line != "" {
-					printing.DalLog("INFO", "A '"+endpoint+"' wordlist has been loaded ["+line+"L / "+size+"]                   ", options)
-					for _, remoteWord := range wordlist {
-						if remoteWord != "" {
-							p, dp = setP(p, dp, remoteWord, options)
-						}
-					}
-				}
-			}
-		}
-	}
-	if options.FindingDOM {
-		treq := optimization.GenerateNewRequest(target, "", options)
-		//treq, terr := http.NewRequest("GET", target, nil)
-		if treq != nil {
-			transport := getTransport(options)
-			t := options.Timeout
-			client := &http.Client{
-				Timeout:   time.Duration(t) * time.Second,
-				Transport: transport,
-			}
-
-			if !options.FollowRedirect {
-				client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-					//return errors.New("Follow redirect") // or maybe the error from the request
-					return nil
-				}
-			}
-
-			tres, err := client.Do(treq)
-			if err == nil {
-				defer tres.Body.Close()
-				var reader io.ReadCloser
-				switch tres.Header.Get("Content-Encoding") {
-				case "gzip":
-					reader, err = gzip.NewReader(tres.Body)
-					if err != nil {
-						reader = tres.Body
-					}
-					defer reader.Close()
-				default:
-					reader = tres.Body
-				}
-				bodyString, err := io.ReadAll(reader)
-				if err == nil {
-					body := ioutil.NopCloser(strings.NewReader(string(bodyString)))
-					defer body.Close()
-					doc, err := goquery.NewDocumentFromReader(body)
-					if err == nil {
-						count := 0
-						doc.Find("input").Each(func(i int, s *goquery.Selection) {
-							name, _ := s.Attr("name")
-							p, dp = setP(p, dp, name, options)
-							count = count + 1
-						})
-						doc.Find("textarea").Each(func(i int, s *goquery.Selection) {
-							name, _ := s.Attr("name")
-							p, dp = setP(p, dp, name, options)
-							count = count + 1
-						})
-						doc.Find("select").Each(func(i int, s *goquery.Selection) {
-							name, _ := s.Attr("name")
-							p, dp = setP(p, dp, name, options)
-							count = count + 1
-						})
-						doc.Find("form").Each(func(i int, s *goquery.Selection) {
-							action, _ := s.Attr("action")
-							if strings.HasPrefix(action, "/") || strings.HasPrefix(action, "?") { // assuming this is a relative URL
-								url, err := url.Parse(action)
-								if err == nil {
-									query := url.Query()
-									for aParam := range query {
-										p, dp = setP(p, dp, aParam, options)
-										count = count + 1
-									}
-								}
-							}
-						})
-						doc.Find("a").Each(func(i int, s *goquery.Selection) {
-							href, _ := s.Attr("href")
-							if strings.HasPrefix(href, "/") || strings.HasPrefix(href, "?") { // assuming this is a relative URL
-								url, err := url.Parse(href)
-								if err == nil {
-									query := url.Query()
-									for aParam := range query {
-										p, dp = setP(p, dp, aParam, options)
-										count = count + 1
-									}
-								}
-							}
-						})
-						printing.DalLog("INFO", "Found "+strconv.Itoa(count)+" testing point in DOM base parameter mining", options)
-					}
-				}
-			}
-		}
-	}
-
-	// Testing URL Params
-	var wgg sync.WaitGroup
-	concurrency := options.Concurrence
-	paramsQue := make(chan string)
-	miningDictCount := 0
-	waf := false
-	wafName := ""
-	mutex := &sync.Mutex{}
-	for i := 0; i < concurrency; i++ {
-		wgg.Add(1)
-		go func() {
-			for k := range paramsQue {
-				if optimization.CheckInspectionParam(options, k) {
-					printing.DalLog("DEBUG", "Mining URL scan to "+k, options)
-					tempURL, _ := optimization.MakeRequestQuery(target, k, "DalFox", "PA", "toAppend", "NaN", options)
-					var code string
-					rl.Block(tempURL.Host)
-					resbody, resp, _, vrs, err := SendReq(tempURL, "DalFox", options)
-					if err == nil {
-						wafCheck, wafN := checkWAF(resp.Header, resbody)
-						if wafCheck {
-							mutex.Lock()
-							if !waf {
-								waf = true
-								wafName = wafN
-								if options.WAFEvasion {
-									options.Concurrence = 1
-									options.Delay = 3
-									printing.DalLog("INFO", "Set worker=1, delay=3s for WAF-Evasion", options)
-								}
-							}
-							mutex.Unlock()
-						}
-					}
-					_, lineSum := verification.VerifyReflectionWithLine(resbody, "DalFox")
-					if miningCheckerLine == lineSum {
-						pLog.Debug("Hit linesum")
-						pLog.Debug(lineSum)
-						//vrs = false
-						//(#354) It can cause a lot of misconceptions. removed it.
-					}
-					if vrs {
-						code = CodeView(resbody, "DalFox")
-						code = code[:len(code)-5]
-						pointer := optimization.Abstraction(resbody, "DalFox")
-						smap := "Injected: "
-						tempSmap := make(map[string]int)
-
-						for _, v := range pointer {
-							if tempSmap[v] == 0 {
-								tempSmap[v] = 1
-							} else {
-								tempSmap[v] = tempSmap[v] + 1
-							}
-						}
-						for k, v := range tempSmap {
-							smap = smap + "/" + k + "(" + strconv.Itoa(v) + ")"
-						}
-						mutex.Lock()
-						miningDictCount = miningDictCount + 1
-						params[k] = append(params[k], "PTYPE: URL")
-						params[k] = append(params[k], smap)
-						mutex.Unlock()
-						var wg sync.WaitGroup
-						chars := GetSpecialChar()
-						for _, c := range chars {
-							wg.Add(1)
-							char := c
-							go func() {
-								defer wg.Done()
-								encoders := []string{
-									"NaN",
-									"urlEncode",
-									"urlDoubleEncode",
-									"htmlEncode",
-								}
-
-								for _, encoder := range encoders {
-									turl, _ := optimization.MakeRequestQuery(target, k, "dalfox"+char, "PA-URL", "toAppend", encoder, options)
-									rl.Block(tempURL.Host)
-									_, _, _, vrs, _ := SendReq(turl, "dalfox"+char, options)
-									if vrs {
-										mutex.Lock()
-										params[k] = append(params[k], char)
-										mutex.Unlock()
-									}
-								}
-							}()
-						}
-						wg.Wait()
-						params[k] = voltUtils.UniqueStringSlice(params[k])
-						params[k] = append(params[k], code)
-					}
-				}
-			}
-			wgg.Done()
-		}()
-	}
-
-	for v := range p {
-		if len(options.UniqParam) > 0 {
-			for _, selectedParam := range options.UniqParam {
-				if selectedParam == v {
-					paramsQue <- v
-				}
-			}
-		} else if len(options.IgnoreParams) > 0 {
-			for _, ignoreParam := range options.IgnoreParams {
-				if ignoreParam != v {
-					paramsQue <- v
-				}
-			}
-		} else {
-			paramsQue <- v
-		}
-	}
-
-	close(paramsQue)
-	wgg.Wait()
-
-	// Testing Form Params
-	var wggg sync.WaitGroup
-	paramsDataQue := make(chan string)
-	for j := 0; j < concurrency; j++ {
-		wggg.Add(1)
-		go func() {
-			for k := range paramsDataQue {
-				printing.DalLog("DEBUG", "Mining FORM scan to "+k, options)
-				if optimization.CheckInspectionParam(options, k) {
-					tempURL, _ := optimization.MakeRequestQuery(target, k, "DalFox", "PA-FORM", "toAppend", "NaN", options)
-					var code string
-					rl.Block(tempURL.Host)
-					resbody, resp, _, vrs, _ := SendReq(tempURL, "DalFox", options)
-					_, lineSum := verification.VerifyReflectionWithLine(resbody, "DalFox")
-					//fmt.Printf("%s => %d : %d\n", k, miningCheckerLine, lineSum)
-					if miningCheckerLine == lineSum {
-
-						vrs = false
-					}
-					if vrs {
-						code = CodeView(resbody, "DalFox")
-						code = code[:len(code)-5]
-						pointer := optimization.Abstraction(resbody, "DalFox")
-						smap := "Injected: "
-						tempSmap := make(map[string]int)
-
-						for _, v := range pointer {
-							if tempSmap[v] == 0 {
-								tempSmap[v] = 1
-							} else {
-								tempSmap[v] = tempSmap[v] + 1
-							}
-						}
-						for k, v := range tempSmap {
-							smap = smap + "/" + k + "(" + strconv.Itoa(v) + ")"
-						}
-						mutex.Lock()
-						miningDictCount = miningDictCount + 1
-						params[k] = append(params[k], "PTYPE: FORM")
-						params[k] = append(params[k], smap)
-						mutex.Unlock()
-						var wg sync.WaitGroup
-						chars := GetSpecialChar()
-						for _, c := range chars {
-							wg.Add(1)
-							char := c
-
-							go func() {
-								defer wg.Done()
-								encoders := []string{
-									"NaN",
-									"urlEncode",
-									"urlDoubleEncode",
-									"htmlEncode",
-								}
-
-								for _, encoder := range encoders {
-									turl, _ := optimization.MakeRequestQuery(target, k, "dalfox"+char, "PA", "toAppend", encoder, options)
-									rl.Block(tempURL.Host)
-									_, _, _, vrs, _ := SendReq(turl, "dalfox"+char, options)
-									_ = resp
-									if vrs {
-										mutex.Lock()
-										params[k] = append(params[k], char)
-										mutex.Unlock()
-									}
-								}
-							}()
-						}
-						wg.Wait()
-						params[k] = voltUtils.UniqueStringSlice(params[k])
-						params[k] = append(params[k], code)
-					}
-				}
-			}
-			wggg.Done()
-		}()
-	}
-
-	for v := range dp {
-		if len(options.UniqParam) > 0 {
-			for _, selectedParam := range options.UniqParam {
-				if selectedParam == v {
-					paramsDataQue <- v
-				}
-			}
-		} else {
-			paramsDataQue <- v
-		}
-	}
-
-	close(paramsDataQue)
-	wggg.Wait()
-	if miningDictCount != 0 {
-		printing.DalLog("INFO", "Found "+strconv.Itoa(miningDictCount)+" testing point in Dictionary base parameter mining", options)
-	}
-	if waf {
-		printing.DalLog("INFO", "Found WAF: "+wafName, options)
-		options.WAF = true
-	}
-	return params
-}
-
-// GetPType is Get Parameter Type
-func GetPType(av string) string {
-	if strings.Contains(av, "PTYPE: URL") {
-		return "-URL"
-	}
-	if strings.Contains(av, "PTYPE: FORM") {
-		return "-FORM"
-	}
-	return ""
-}
diff --git a/pkg/scanning/queries.go b/pkg/scanning/queries.go
index c79dd0f..cdbc4b8 100644
--- a/pkg/scanning/queries.go
+++ b/pkg/scanning/queries.go
@@ -12,11 +12,7 @@ type Queries struct {
 
 func checkVStatus(vStatus map[string]bool) bool {
 	for k, v := range vStatus {
-		if k != "pleasedonthaveanamelikethis_plz_plz" {
-			if !v {
-				return false
-			}
-		} else {
+		if k == "pleasedonthaveanamelikethis_plz_plz" || !v {
 			return false
 		}
 	}
diff --git a/pkg/scanning/scan.go b/pkg/scanning/scan.go
index feedfd2..12986c7 100644
--- a/pkg/scanning/scan.go
+++ b/pkg/scanning/scan.go
@@ -26,6 +26,13 @@ import (
 	voltFile "github.com/hahwul/volt/file"
 )
 
+const (
+	NaN             = "NaN"
+	urlEncode       = "urlEncode"
+	urlDoubleEncode = "urlDoubleEncode"
+	htmlEncode      = "htmlEncode"
+)
+
 var (
 	scanObject model.Scan
 	s          = spinner.New(spinner.CharSets[14], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
@@ -34,22 +41,10 @@ var (
 // Scan is main scanning function
 func Scan(target string, options model.Options, sid string) (model.Result, error) {
 	var scanResult model.Result
-	mutex := &sync.Mutex{}
 	options.ScanResult = scanResult
 	scanResult.StartTime = time.Now()
 	if !(options.Silence || options.NoSpinner) {
-		time.Sleep(1 * time.Second) // Waiting log
-		s.Prefix = " "
-		s.Suffix = ""
-		if !options.NoColor {
-			s.Color("red", "bold")
-		}
-		if options.SpinnerObject != nil {
-			s = options.SpinnerObject
-		} else {
-			options.SpinnerObject = s
-		}
-		s.Start()
+		initializeSpinner(options)
 	}
 
 	scanObject := model.Scan{
@@ -57,15 +52,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 		URL:    target,
 	}
 	if !(options.Silence && options.MulticastMode) {
-		printing.DalLog("SYSTEM", "Start Scan 🦊", options)
-		//printing.DalLog("SYSTEM-M", "Start Scan 🦊", options)
-		if options.AllURLS > 0 {
-			snow, _ := strconv.Atoi(sid)
-			percent := fmt.Sprintf("%0.2f%%", float64(snow)/float64(options.AllURLS)*100)
-			printing.DalLog("SYSTEM-M", "🦊 Start scan [SID:"+sid+"]["+sid+"/"+strconv.Itoa(options.AllURLS)+"]["+percent+"%] / URL: "+target, options)
-		} else {
-			printing.DalLog("SYSTEM-M", "🦊 Start scan [SID:"+sid+"] / URL: "+target, options)
-		}
+		logStartScan(target, options, sid)
 	}
 
 	// query is XSS payloads
@@ -73,7 +60,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 
 	// params is "param name":true  (reflected?)
 	// 1: non-reflected , 2: reflected , 3: reflected-with-sc
-	params := make(map[string][]string)
+	params := make(map[string]model.ParamResult)
 
 	// durls is url for dom xss
 	var durls []string
@@ -93,22 +80,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 		return scanResult, err
 	}
 	treq := optimization.GenerateNewRequest(target, "", options)
-	//treq, terr := http.NewRequest("GET", target, nil)
 	if treq == nil {
 	} else {
-		transport := getTransport(options)
-		t := options.Timeout
-		client := &http.Client{
-			Timeout:   time.Duration(t) * time.Second,
-			Transport: transport,
-		}
-
-		if !options.FollowRedirect {
-			client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-				return http.ErrUseLastResponse
-			}
-		}
-
+		client := createHTTPClient(options)
 		tres, err := client.Do(treq)
 		if err != nil {
 			msg := fmt.Sprintf("not running %v", err)
@@ -116,13 +90,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 			return scanResult, err
 		}
 		if options.IgnoreReturn != "" {
-			rcode := strings.Split(options.IgnoreReturn, ",")
-			tcode := strconv.Itoa(tres.StatusCode)
-			for _, v := range rcode {
-				if tcode == v {
-					printing.DalLog("SYSTEM", "Not running "+target+" url from --ignore-return option", options)
-					return scanResult, nil
-				}
+			if shouldIgnoreReturn(tres.StatusCode, options.IgnoreReturn) {
+				printing.DalLog("SYSTEM", "Not running "+target+" url from --ignore-return option", options)
+				return scanResult, nil
 			}
 		}
 
@@ -158,32 +128,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 	if options.UseBAV {
 		go func() {
 			defer wait.Done()
-			var bavWaitGroup sync.WaitGroup
-			bavTask := 5
-			bavWaitGroup.Add(bavTask)
-			go func() {
-				defer bavWaitGroup.Done()
-				ESIIAnalysis(target, options, rl)
-			}()
-			go func() {
-				defer bavWaitGroup.Done()
-				SqliAnalysis(target, options, rl)
-			}()
-			go func() {
-				defer bavWaitGroup.Done()
-				SSTIAnalysis(target, options, rl)
-			}()
-			go func() {
-				defer bavWaitGroup.Done()
-				CRLFAnalysis(target, options, rl)
-			}()
-			go func() {
-				defer bavWaitGroup.Done()
-				OpenRedirectorAnalysis(target, options, rl)
-			}()
-			bavWaitGroup.Wait()
-			bav = options.AuroraObject.Green(bav).String()
-			printing.DalLog("SYSTEM", "["+sa+pa+bav+"] Waiting for analysis 🔍", options)
+			runBAVAnalysis(target, options, rl, &bav)
 		}()
 	}
 
@@ -194,64 +139,18 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 	if !(options.Silence || options.NoSpinner) {
 		time.Sleep(1 * time.Second) // Waiting log
 		s.Start()                   // Start the spinner
-		//time.Sleep(3 * time.Second) // Run for some time to simulate work
 	}
 	wait.Wait()
 
 	if !(options.Silence || options.NoSpinner) {
 		s.Stop()
 	}
-	for k, v := range policy {
-		if len(v) != 0 {
-			if k == "BypassCSP" {
-				printing.DalLog("WEAK", k+": "+v, options)
-			} else {
-				printing.DalLog("INFO", k+" is "+v, options)
-			}
-		}
-	}
-	for k, v := range options.PathReflection {
-		if len(parsedURL.Path) == 0 {
-			str := options.AuroraObject.Yellow("dalfoxpathtest").String()
-			printing.DalLog("INFO", "Reflected PATH '/"+str+"' => "+v+"]", options)
-		} else {
-			split := strings.Split(parsedURL.Path, "/")
-			if len(split) > k+1 {
-				split[k+1] = options.AuroraObject.Yellow("dalfoxpathtest").String()
-				tempURL := strings.Join(split, "/")
-				printing.DalLog("INFO", "Reflected PATH '"+tempURL+"' => "+v+"]", options)
-			}
-		}
-	}
+	logPolicyAndPathReflection(policy, options, parsedURL)
 
 	for k, v := range params {
-		temp := model.ParamResult{
-			Name:      k,
-			Reflected: false,
-		}
-
-		if len(v) != 0 {
-			code, vv := v[len(v)-1], v[:len(v)-1]
-			char := strings.Join(vv, "  ")
-			//x, a = a[len(a)-1], a[:len(a)-1]
-			printing.DalLog("INFO", "Reflected "+k+" param => "+char, options)
-			printing.DalLog("CODE", code, options)
-			arr := strings.Split(char, "  ")
-			for _, value := range arr {
-				if strings.Contains(value, "PTYPE:") {
-					splitedValue := strings.Split(value, " ")
-					temp.Type = splitedValue[1]
-				} else if strings.Contains(value, "Injected:") {
-					splitedValue := strings.Split(value, " ")
-					temp.ReflectedPoint = splitedValue[1]
-				} else {
-					temp.Chars = append(temp.Chars, value)
-				}
-			}
-			temp.ReflectedCode = code
-			temp.Reflected = true
-		}
-		scanResult.Params = append(scanResult.Params, temp)
+		printing.DalLog("INFO", "Reflected "+k+" param => "+strings.Join(v.Chars, "  "), options)
+		printing.DalLog("CODE", v.ReflectedCode, options)
+		scanResult.Params = append(scanResult.Params, v)
 	}
 
 	if !options.OnlyDiscovery {
@@ -259,14 +158,8 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 		printing.DalLog("SYSTEM", "Generate XSS payload and optimization.Optimization.. 🛠", options)
 		// optimization.Optimization..
 
-		/*
-			k: parama name
-			v: pattern [injs, inhtml, ' < > ]
-			av: reflected type, valid char
-		*/
-
 		// set vStatus
-		for k, _ := range params {
+		for k := range params {
 			vStatus[k] = false
 		}
 
@@ -318,16 +211,16 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 						for k, v := range params {
 							if optimization.CheckInspectionParam(options, k) {
 								ptype := ""
-								for _, av := range v {
+								for _, av := range v.Chars {
 									if strings.Contains(av, "PTYPE:") {
 										ptype = GetPType(av)
 									}
 								}
 								encoders := []string{
-									"NaN",
-									"urlEncode",
-									"urlDoubleEncode",
-									"htmlEncode",
+									NaN,
+									urlEncode,
+									urlDoubleEncode,
+									htmlEncode,
 								}
 								for _, encoder := range encoders {
 									tq, tm := optimization.MakeRequestQuery(target, k, customPayload, "inHTML"+ptype, "toAppend", encoder, options)
@@ -368,10 +261,10 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 					arc := optimization.SetPayloadValue(getCommonPayload(), options)
 					for _, avv := range arc {
 						encoders := []string{
-							"NaN",
-							"urlEncode",
-							"urlDoubleEncode",
-							"htmlEncode",
+							NaN,
+							urlEncode,
+							urlDoubleEncode,
+							htmlEncode,
 						}
 						for _, encoder := range encoders {
 							tq, tm := optimization.MakeRequestQuery(target, v, avv, "inHTML-URL", "toAppend", encoder, options)
@@ -387,10 +280,10 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 					arc := optimization.SetPayloadValue(getCommonPayload(), options)
 					for _, avv := range arc {
 						encoders := []string{
-							"NaN",
-							"urlEncode",
-							"urlDoubleEncode",
-							"htmlEncode",
+							NaN,
+							urlEncode,
+							urlDoubleEncode,
+							htmlEncode,
 						}
 						for _, encoder := range encoders {
 							tq, tm := optimization.MakeRequestQuery(target, v, avv, "inHTML-FORM", "toAppend", encoder, options)
@@ -412,7 +305,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 				for v := range cp {
 					if optimization.CheckInspectionParam(options, v) {
 						// loop payload list
-						if len(params[v]) == 0 {
+						if len(params[v].Chars) == 0 {
 							for _, dpayload := range dpayloads {
 								var durl string
 								u, _ := url.Parse(target)
@@ -434,7 +327,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 				for v := range cpd {
 					if optimization.CheckInspectionParam(options, v) {
 						// loop payload list
-						if len(params[v]) == 0 {
+						if len(params[v].Chars) == 0 {
 							for _, dpayload := range dpayloads {
 								var durl string
 								u, _ := url.Parse(target)
@@ -462,7 +355,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 					chars := GetSpecialChar()
 					var badchars []string
 
-					for _, av := range v {
+					for _, av := range v.Chars {
 						if indexOf(av, chars) == -1 {
 							badchars = append(badchars, av)
 						}
@@ -474,7 +367,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 							// Injected pattern
 							injectedPoint := strings.Split(av, "/")
 							injectedPoint = injectedPoint[1:]
-							injectedChars := params[k][:len(params[k])-1]
+							injectedChars := params[k].Chars[:len(params[k].Chars)-1]
 							for _, ip := range injectedPoint {
 								var arr []string
 								if strings.Contains(ip, "inJS") {
@@ -508,10 +401,10 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 								for _, avv := range arr {
 									if optimization.Optimization(avv, badchars) {
 										encoders := []string{
-											"NaN",
-											"urlEncode",
-											"urlDoubleEncode",
-											"htmlEncode",
+											NaN,
+											urlEncode,
+											urlDoubleEncode,
+											htmlEncode,
 										}
 										for _, encoder := range encoders {
 											tq, tm := optimization.MakeRequestQuery(target, k, avv, ip+ptype, "toAppend", encoder, options)
@@ -528,10 +421,10 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 						if !containsFromArray(cpArr, k) {
 							if optimization.Optimization(avv, badchars) {
 								encoders := []string{
-									"NaN",
-									"urlEncode",
-									"urlDoubleEncode",
-									"htmlEncode",
+									NaN,
+									urlEncode,
+									urlDoubleEncode,
+									htmlEncode,
 								}
 								for _, encoder := range encoders {
 									tq, tm := optimization.MakeRequestQuery(target, k, avv, "inHTML"+ptype, "toAppend", encoder, options)
@@ -572,7 +465,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 			for k, v := range params {
 				if optimization.CheckInspectionParam(options, k) {
 					ptype := ""
-					for _, av := range v {
+					for _, av := range v.Chars {
 						if strings.Contains(av, "PTYPE:") {
 							ptype = GetPType(av)
 						}
@@ -582,10 +475,10 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 						// Add plain XSS Query
 						bp := strings.Replace(bpayload, "CALLBACKURL", bcallback, 10)
 						encoders := []string{
-							"NaN",
-							"urlEncode",
-							"urlDoubleEncode",
-							"htmlEncode",
+							NaN,
+							urlEncode,
+							urlDoubleEncode,
+							htmlEncode,
 						}
 						for _, encoder := range encoders {
 							tq, tm := optimization.MakeRequestQuery(target, k, bp, "toBlind"+ptype, "toAppend", encoder, options)
@@ -618,16 +511,16 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 							for k, v := range params {
 								if optimization.CheckInspectionParam(options, k) {
 									ptype := ""
-									for _, av := range v {
+									for _, av := range v.Chars {
 										if strings.Contains(av, "PTYPE:") {
 											ptype = GetPType(av)
 										}
 									}
 									encoders := []string{
-										"NaN",
-										"urlEncode",
-										"urlDoubleEncode",
-										"htmlEncode",
+										NaN,
+										urlEncode,
+										urlDoubleEncode,
+										htmlEncode,
 									}
 									for _, encoder := range encoders {
 										tq, tm := optimization.MakeRequestQuery(target, k, remotePayload, "inHTML"+ptype, "toAppend", encoder, options)
@@ -668,6 +561,18 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 		// make reqeust channel
 		queries := make(chan Queries)
 
+		resultsChan := make(chan model.PoC)
+		doneChan := make(chan bool)
+
+		// Collect results from the channel
+		go func() {
+			for result := range resultsChan {
+				scanObject.Results = append(scanObject.Results, result)
+				scanResult.PoCs = append(scanResult.PoCs, result)
+			}
+			doneChan <- true
+		}()
+
 		if options.UseHeadless {
 			// start DOM XSS checker
 			wg.Add(1)
@@ -686,7 +591,6 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 					go func() {
 						for v := range dchan {
 							if CheckXSSWithHeadless(v, options) {
-								mutex.Lock()
 								printing.DalLog("VULN", "Triggered XSS Payload (found dialog in headless)", options)
 								poc := model.PoC{
 									Type:       "V",
@@ -715,9 +619,7 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 								if options.FoundAction != "" {
 									foundAction(options, target, v, "VULN")
 								}
-								scanObject.Results = append(scanObject.Results, poc)
-								scanResult.PoCs = append(scanResult.PoCs, poc)
-								mutex.Unlock()
+								resultsChan <- poc
 							}
 							queryCount = queryCount + 1
 						}
@@ -772,7 +674,6 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 											if vStatus[v["param"]] == false {
 												if options.UseHeadless {
 													if CheckXSSWithHeadless(k.URL.String(), options) {
-														mutex.Lock()
 														printing.DalLog("VULN", "Triggered XSS Payload (found dialog in headless)", options)
 														poc := model.PoC{
 															Type:       "V",
@@ -816,11 +717,8 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 														if options.FoundAction != "" {
 															foundAction(options, target, k.URL.String(), "VULN")
 														}
-														scanObject.Results = append(scanObject.Results, poc)
-														scanResult.PoCs = append(scanResult.PoCs, poc)
-														mutex.Unlock()
+														resultsChan <- poc
 													} else {
-														mutex.Lock()
 														if options.FoundAction != "" {
 															foundAction(options, target, k.URL.String(), "WEAK")
 														}
@@ -853,12 +751,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 															printing.DalLog("CODE", string(resbody), options)
 														}
 
-														scanObject.Results = append(scanObject.Results, poc)
-														scanResult.PoCs = append(scanResult.PoCs, poc)
-														mutex.Unlock()
+														resultsChan <- poc
 													}
 												} else {
-													mutex.Lock()
 													code := CodeView(resbody, v["payload"])
 													printing.DalLog("WEAK", "Reflected Payload in JS: "+v["param"]+"="+v["payload"], options)
 													printing.DalLog("CODE", code, options)
@@ -902,16 +797,13 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 													if options.FoundAction != "" {
 														foundAction(options, target, k.URL.String(), "WEAK")
 													}
-													scanObject.Results = append(scanObject.Results, poc)
-													scanResult.PoCs = append(scanResult.PoCs, poc)
-													mutex.Unlock()
+													resultsChan <- poc
 												}
 											}
 										}
 									}
 								} else if strings.Contains(v["type"], "inATTR") {
 									if vds {
-										mutex.Lock()
 										if vStatus[v["param"]] == false {
 											code := CodeView(resbody, v["payload"])
 											printing.DalLog("VULN", "Triggered XSS Payload (found DOM Object): "+v["param"]+"="+v["payload"], options)
@@ -957,12 +849,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 											if options.FoundAction != "" {
 												foundAction(options, target, k.URL.String(), "VULN")
 											}
-											scanObject.Results = append(scanObject.Results, poc)
-											scanResult.PoCs = append(scanResult.PoCs, poc)
+											resultsChan <- poc
 										}
-										mutex.Unlock()
 									} else if vrs {
-										mutex.Lock()
 										if vStatus[v["param"]] == false {
 											code := CodeView(resbody, v["payload"])
 											printing.DalLog("WEAK", "Reflected Payload in Attribute: "+v["param"]+"="+v["payload"], options)
@@ -1007,14 +896,11 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 											if options.FoundAction != "" {
 												foundAction(options, target, k.URL.String(), "WEAK")
 											}
-											scanObject.Results = append(scanObject.Results, poc)
-											scanResult.PoCs = append(scanResult.PoCs, poc)
+											resultsChan <- poc
 										}
-										mutex.Unlock()
 									}
 								} else {
 									if vds {
-										mutex.Lock()
 										if vStatus[v["param"]] == false {
 											code := CodeView(resbody, v["payload"])
 											printing.DalLog("VULN", "Triggered XSS Payload (found DOM Object): "+v["param"]+"="+v["payload"], options)
@@ -1060,12 +946,9 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 											if options.FoundAction != "" {
 												foundAction(options, target, k.URL.String(), "VULN")
 											}
-											scanObject.Results = append(scanObject.Results, poc)
-											scanResult.PoCs = append(scanResult.PoCs, poc)
+											resultsChan <- poc
 										}
-										mutex.Unlock()
 									} else if vrs {
-										mutex.Lock()
 										if vStatus[v["param"]] == false {
 											code := CodeView(resbody, v["payload"])
 											printing.DalLog("WEAK", "Reflected Payload in HTML: "+v["param"]+"="+v["payload"], options)
@@ -1110,16 +993,13 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 											if options.FoundAction != "" {
 												foundAction(options, target, k.URL.String(), "WEAK")
 											}
-											scanObject.Results = append(scanObject.Results, poc)
-											scanResult.PoCs = append(scanResult.PoCs, poc)
+											resultsChan <- poc
 										}
-										mutex.Unlock()
 									}
 								}
 							}
 						}
 					}
-					mutex.Lock()
 					queryCount = queryCount + 1
 
 					if !(options.Silence || options.NoSpinner) {
@@ -1146,10 +1026,8 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 							percent2 := fmt.Sprintf("%0.2f%%", (float64(options.NowURL) / float64(options.AllURLS) * 100))
 							s.Suffix = "  [" + strconv.Itoa(queryCount) + "/" + strconv.Itoa(len(query)+len(durls)) + " Queries][" + percent + "][" + strconv.Itoa(options.NowURL) + "/" + strconv.Itoa(options.AllURLS) + " Tasks][" + percent2 + "] " + msg
 						}
-						//s.Suffix = " Waiting routines.. (" + strconv.Itoa(queryCount) + " / " + strconv.Itoa(len(query)) + ") reqs"
 						s.Unlock()
 					}
-					mutex.Unlock()
 				}
 				wg.Done()
 			}()
@@ -1167,23 +1045,16 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 		if !(options.Silence || options.NoSpinner) {
 			s.Stop()
 		}
+
+		close(resultsChan)
+		<-doneChan
 	}
 
 	options.Scan[sid] = scanObject
 	scanResult.EndTime = time.Now()
 	scanResult.Duration = scanResult.EndTime.Sub(scanResult.StartTime)
 	if !(options.Silence && options.MulticastMode) {
-		if term.IsTerminal(0) {
-			width, _, err := term.GetSize(0)
-			if err == nil {
-				var dash string
-				for i := 0; i < width-5; i++ {
-					dash = dash + "-"
-				}
-				printing.DalLog("SYSTEM-M", dash, options)
-			}
-		}
-		printing.DalLog("SYSTEM-M", "[duration: "+scanResult.Duration.String()+"][issues: "+strconv.Itoa(len(scanResult.PoCs))+"] Finish Scan!", options)
+		printScanSummary(scanResult, options)
 	}
 	if options.ReportBool {
 		printing.DalLog("SYSTEM-M", "Report\n", options)
@@ -1198,3 +1069,131 @@ func Scan(target string, options model.Options, sid string) (model.Result, error
 	}
 	return scanResult, nil
 }
+
+// initializeSpinner initializes the spinner with the given options.
+func initializeSpinner(options model.Options) {
+	time.Sleep(1 * time.Second) // Waiting log
+	s.Prefix = " "
+	s.Suffix = ""
+	if !options.NoColor {
+		s.Color("red", "bold")
+	}
+	if options.SpinnerObject != nil {
+		s = options.SpinnerObject
+	} else {
+		options.SpinnerObject = s
+	}
+	s.Start()
+}
+
+// logStartScan logs the start of the scan.
+func logStartScan(target string, options model.Options, sid string) {
+	printing.DalLog("SYSTEM", "Start Scan 🦊", options)
+	if options.AllURLS > 0 {
+		snow, _ := strconv.Atoi(sid)
+		percent := fmt.Sprintf("%0.2f%%", float64(snow)/float64(options.AllURLS)*100)
+		printing.DalLog("SYSTEM-M", "🦊 Start scan [SID:"+sid+"]["+sid+"/"+strconv.Itoa(options.AllURLS)+"]["+percent+"%] / URL: "+target, options)
+	} else {
+		printing.DalLog("SYSTEM-M", "🦊 Start scan [SID:"+sid+"] / URL: "+target, options)
+	}
+}
+
+// createHTTPClient creates an HTTP client with the given options.
+func createHTTPClient(options model.Options) *http.Client {
+	transport := getTransport(options)
+	t := options.Timeout
+	client := &http.Client{
+		Timeout:   time.Duration(t) * time.Second,
+		Transport: transport,
+	}
+
+	if !options.FollowRedirect {
+		client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+			return http.ErrUseLastResponse
+		}
+	}
+	return client
+}
+
+// shouldIgnoreReturn checks if the response status code should be ignored.
+func shouldIgnoreReturn(statusCode int, ignoreReturn string) bool {
+	rcode := strings.Split(ignoreReturn, ",")
+	tcode := strconv.Itoa(statusCode)
+	for _, v := range rcode {
+		if tcode == v {
+			return true
+		}
+	}
+	return false
+}
+
+// runBAVAnalysis runs the BAV analysis.
+func runBAVAnalysis(target string, options model.Options, rl *rateLimiter, bav *string) {
+	var bavWaitGroup sync.WaitGroup
+	bavTask := 5
+	bavWaitGroup.Add(bavTask)
+	go func() {
+		defer bavWaitGroup.Done()
+		ESIIAnalysis(target, options, rl)
+	}()
+	go func() {
+		defer bavWaitGroup.Done()
+		SqliAnalysis(target, options, rl)
+	}()
+	go func() {
+		defer bavWaitGroup.Done()
+		SSTIAnalysis(target, options, rl)
+	}()
+	go func() {
+		defer bavWaitGroup.Done()
+		CRLFAnalysis(target, options, rl)
+	}()
+	go func() {
+		defer bavWaitGroup.Done()
+		OpenRedirectorAnalysis(target, options, rl)
+	}()
+	bavWaitGroup.Wait()
+	*bav = options.AuroraObject.Green(*bav).String()
+	printing.DalLog("SYSTEM", "["+*bav+"] Waiting for analysis 🔍", options)
+}
+
+// logPolicyAndPathReflection logs the policy and path reflection information.
+func logPolicyAndPathReflection(policy map[string]string, options model.Options, parsedURL *url.URL) {
+	for k, v := range policy {
+		if len(v) != 0 {
+			if k == "BypassCSP" {
+				printing.DalLog("WEAK", k+": "+v, options)
+			} else {
+				printing.DalLog("INFO", k+" is "+v, options)
+			}
+		}
+	}
+	for k, v := range options.PathReflection {
+		if len(parsedURL.Path) == 0 {
+			str := options.AuroraObject.Yellow("dalfoxpathtest").String()
+			printing.DalLog("INFO", "Reflected PATH '/"+str+"' => "+v+"]", options)
+		} else {
+			split := strings.Split(parsedURL.Path, "/")
+			if len(split) > k+1 {
+				split[k+1] = options.AuroraObject.Yellow("dalfoxpathtest").String()
+				tempURL := strings.Join(split, "/")
+				printing.DalLog("INFO", "Reflected PATH '"+tempURL+"' => "+v+"]", options)
+			}
+		}
+	}
+}
+
+// printScanSummary prints the summary of the scan.
+func printScanSummary(scanResult model.Result, options model.Options) {
+	if term.IsTerminal(0) {
+		width, _, err := term.GetSize(0)
+		if err == nil {
+			var dash string
+			for i := 0; i < width-5; i++ {
+				dash = dash + "-"
+			}
+			printing.DalLog("SYSTEM-M", dash, options)
+		}
+	}
+	printing.DalLog("SYSTEM-M", "[duration: "+scanResult.Duration.String()+"][issues: "+strconv.Itoa(len(scanResult.PoCs))+"] Finish Scan!", options)
+}



Test patch:
diff --git a/pkg/scanning/parameterAnalysis_test.go b/pkg/scanning/parameterAnalysis_test.go
new file mode 100644
index 0000000..114972f
--- /dev/null
+++ b/pkg/scanning/parameterAnalysis_test.go
@@ -0,0 +1,114 @@
+package scanning
+
+import (
+	"net/url"
+	"reflect"
+	"testing"
+
+	"github.com/hahwul/dalfox/v2/pkg/model"
+)
+
+func TestSetP(t *testing.T) {
+	type args struct {
+		p       url.Values
+		dp      url.Values
+		name    string
+		options model.Options
+	}
+	tests := []struct {
+		name   string
+		args   args
+		want   url.Values
+		wantDp url.Values
+	}{
+		{
+			name: "Set parameter",
+			args: args{
+				p:       url.Values{},
+				dp:      url.Values{},
+				name:    "test",
+				options: model.Options{},
+			},
+			want:   url.Values{"test": []string{""}},
+			wantDp: url.Values{},
+		},
+		{
+			name: "Set data parameter",
+			args: args{
+				p:       url.Values{},
+				dp:      url.Values{},
+				name:    "test",
+				options: model.Options{Data: "data"},
+			},
+			want:   url.Values{"test": []string{""}},
+			wantDp: url.Values{"test": []string{""}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, gotDp := setP(tt.args.p, tt.args.dp, tt.args.name, tt.args.options)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("setP() got = %v, want %v", got, tt.want)
+			}
+			if !reflect.DeepEqual(gotDp, tt.wantDp) {
+				t.Errorf("setP() gotDp = %v, want %v", gotDp, tt.wantDp)
+			}
+		})
+	}
+}
+
+func TestParseURL(t *testing.T) {
+	type args struct {
+		target string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *url.URL
+		wantP   url.Values
+		wantDp  url.Values
+		wantErr bool
+	}{
+		{
+			name: "Valid URL",
+			args: args{
+				target: "http://example.com",
+			},
+			want: &url.URL{
+				Scheme: "http",
+				Host:   "example.com",
+			},
+			wantP:   url.Values{},
+			wantDp:  url.Values{},
+			wantErr: false,
+		},
+		{
+			name: "Invalid URL",
+			args: args{
+				target: "://example.com",
+			},
+			want:    nil,
+			wantP:   nil,
+			wantDp:  nil,
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, gotP, gotDp, err := parseURL(tt.args.target)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("parseURL() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("parseURL() got = %v, want %v", got, tt.want)
+			}
+			if !reflect.DeepEqual(gotP, tt.wantP) {
+				t.Errorf("parseURL() gotP = %v, want %v", gotP, tt.wantP)
+			}
+			if !reflect.DeepEqual(gotDp, tt.wantDp) {
+				t.Errorf("parseURL() gotDp = %v, want %v", gotDp, tt.wantDp)
+			}
+		})
+	}
+}