@@ -198,27 +198,74 @@ func ScanReflectionWithOptions(opts Options) (*Result, error) {
198198 }
199199 }
200200
201- // ── Dalfox: confirm XSS on kxss findings that had {<} or {>} unfiltered ──
202- // These characters indicate the site doesn't filter angle brackets —
203- // the strongest signal for exploitable XSS. Feed only those URLs to dalfox.
204- xssCandidateURLs := extractAngleBracketURLs (findings )
205- if len (xssCandidateURLs ) > 0 {
206- logger .GetLogger ().Infof ("[INFO] Running dalfox on %d kxss angle-bracket candidates" , len (xssCandidateURLs ))
207- runDalfoxOnURLs (xssCandidateURLs , opts .Domain , opts .Threads )
208- } else {
209- logger .GetLogger ().Infof ("[INFO] No angle-bracket candidates from kxss — skipping dalfox" )
210- if scanID := utils .GetCurrentScanID (); scanID != "" {
211- _ = utils .WriteNoFindingsJSON (scanID , opts .Domain , "xss-detection" , "dalfox-xss-results.json" )
212- }
213- }
214-
215201 return & Result {
216202 Domain : opts .Domain ,
217203 Reflections : reflectionCount ,
218204 OutputFile : outFile ,
219205 }, nil
220206}
221207
208+ // RunDalfoxPhase reads the kxss results for a domain, filters URLs where {<} or {>}
209+ // was unfiltered, and runs dalfox on those candidates as a separate pipeline phase.
210+ // Returns a non-nil error only on configuration failures (not on "no findings").
211+ func RunDalfoxPhase (domain string ) error {
212+ resultsDir := utils .GetResultsDir ()
213+ domainDir := filepath .Join (resultsDir , domain )
214+ kxssFile := filepath .Join (domainDir , "vulnerabilities" , "kxss-results.txt" )
215+
216+ // Parse kxss text output: "URL: <url> Param: <p> Unfiltered: [{<} {>} ...]"
217+ data , err := os .ReadFile (kxssFile )
218+ if err != nil || len (strings .TrimSpace (string (data ))) == 0 {
219+ scanID := utils .GetCurrentScanID ()
220+ if scanID != "" {
221+ _ = utils .WriteNoFindingsJSON (scanID , domain , "xss-detection" , "dalfox-xss-results.json" )
222+ }
223+ return nil
224+ }
225+
226+ // Collect URLs where angle brackets were unfiltered
227+ seen := make (map [string ]struct {})
228+ var candidates []string
229+ for _ , line := range strings .Split (string (data ), "\n " ) {
230+ line = strings .TrimSpace (line )
231+ if line == "" {
232+ continue
233+ }
234+ hasAngle := strings .Contains (line , "{<}" ) || strings .Contains (line , "{>}" )
235+ if ! hasAngle {
236+ continue
237+ }
238+ // Extract the URL part: "URL: <url> Param: ..."
239+ urlPart := ""
240+ if idx := strings .Index (line , "URL: " ); idx >= 0 {
241+ rest := line [idx + 5 :]
242+ if end := strings .Index (rest , " Param:" ); end >= 0 {
243+ urlPart = strings .TrimSpace (rest [:end ])
244+ } else {
245+ urlPart = strings .TrimSpace (rest )
246+ }
247+ }
248+ if urlPart != "" {
249+ if _ , ok := seen [urlPart ]; ! ok {
250+ seen [urlPart ] = struct {}{}
251+ candidates = append (candidates , urlPart )
252+ }
253+ }
254+ }
255+
256+ if len (candidates ) == 0 {
257+ logger .GetLogger ().Infof ("[INFO] Dalfox: no angle-bracket candidates from kxss for %s" , domain )
258+ if scanID := utils .GetCurrentScanID (); scanID != "" {
259+ _ = utils .WriteNoFindingsJSON (scanID , domain , "xss-detection" , "dalfox-xss-results.json" )
260+ }
261+ return nil
262+ }
263+
264+ logger .GetLogger ().Infof ("[INFO] Dalfox: running on %d kxss angle-bracket candidates for %s" , len (candidates ), domain )
265+ runDalfoxOnURLs (candidates , domain , 50 )
266+ return nil
267+ }
268+
222269// xssFinding is one structured kxss result persisted to the dashboard.
223270type xssFinding struct {
224271 TemplateID string `json:"template-id"`
0 commit comments