package runner import ( "bufio" "encoding/json" "fmt" "log" "net" "os/exec" "strings" "time" "nucleus/internal/db" "nucleus/internal/mailer" "nucleus/internal/models" ) func RunScan(targetID int, isManual bool) { // 1. Get Target row := db.DB.QueryRow("SELECT id, name, address, schedule FROM targets WHERE id = ?", targetID) var t models.Target err := row.Scan(&t.ID, &t.Name, &t.Address, &t.Schedule) if err != nil { log.Println("Failed to find target for scan:", err) return } // 2. Create Scan Record res, err := db.DB.Exec("INSERT INTO scans (target_id, status, started_at) VALUES (?, 'running', ?)", t.ID, time.Now()) if err != nil { log.Println("Failed to create scan record:", err) return } scanID, _ := res.LastInsertId() // 3. Update target last_scan_at db.DB.Exec("UPDATE targets SET last_scan_at = ? WHERE id = ?", time.Now(), t.ID) // 4. Run Nuclei cmd := exec.Command("nuclei", "-target", t.Address, "-jsonl", "-silent") stdout, err := cmd.StdoutPipe() if err != nil { log.Println("Failed to get stdout pipe:", err) db.DB.Exec("UPDATE scans SET status = 'failed', completed_at = ? WHERE id = ?", time.Now(), scanID) return } if err := cmd.Start(); err != nil { log.Println("Failed to start nuclei:", err) db.DB.Exec("UPDATE scans SET status = 'failed', completed_at = ? WHERE id = ?", time.Now(), scanID) return } var findings []models.Finding hasMediumOrHigher := false scanner := bufio.NewScanner(stdout) for scanner.Scan() { line := scanner.Bytes() var nf models.NucleiFinding if err := json.Unmarshal(line, &nf); err == nil { displayHost := nf.Host ipStr := displayHost if hostPart, _, err := net.SplitHostPort(displayHost); err == nil { ipStr = hostPart } if ip := net.ParseIP(ipStr); ip != nil { if names, err := net.LookupAddr(ip.String()); err == nil && len(names) > 0 { hostname := strings.TrimSuffix(names[0], ".") displayHost = fmt.Sprintf("%s (%s)", hostname, displayHost) } } // Insert finding res, err := db.DB.Exec(` INSERT INTO findings (scan_id, template_id, name, severity, host, matched_at, description) VALUES (?, ?, ?, ?, ?, ?, ?) `, scanID, nf.TemplateID, nf.Info.Name, nf.Info.Severity, displayHost, nf.MatchedAt, nf.Info.Description) if err == nil { fid, _ := res.LastInsertId() f := models.Finding{ ID: int(fid), ScanID: int(scanID), TemplateID: nf.TemplateID, Name: nf.Info.Name, Severity: nf.Info.Severity, Host: displayHost, MatchedAt: nf.MatchedAt, Description: nf.Info.Description, } findings = append(findings, f) if f.Severity == "critical" || f.Severity == "high" || f.Severity == "medium" { hasMediumOrHigher = true } } } } err = cmd.Wait() status := "completed" if err != nil { log.Println("Nuclei finished with error:", err) status = "failed" } db.DB.Exec("UPDATE scans SET status = ?, completed_at = ? WHERE id = ?", status, time.Now(), scanID) // Send Email Report if manual or has medium+ severity if isManual || hasMediumOrHigher { mailer.SendReport(t, findings) } }