Skip to content

Commit bfb7b1c

Browse files
committed
Refactor: Handle zero-result scans efficiently across all modules and Discord bot
1 parent 3975a8a commit bfb7b1c

8 files changed

Lines changed: 79 additions & 84 deletions

File tree

internal/modules/aem/aem.go

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,15 @@ func DiscoverAEM(opts Options) ([]string, error) {
106106
log.Printf("[AEM] Starting AEM discovery (native Go)...")
107107
discovered := DiscoverAEMFromURLs(urls, client, opts.Threads)
108108

109-
// Always save discovered instances (even if empty)
110-
discoveredFH, err := os.Create(discoveredFile)
111-
if err == nil {
112-
if len(discovered) > 0 {
109+
// Save discovered instances (only if found - empty files handled by SendPhaseFiles)
110+
if len(discovered) > 0 {
111+
discoveredFH, err := os.Create(discoveredFile)
112+
if err == nil {
113113
for _, url := range discovered {
114114
fmt.Fprintln(discoveredFH, url)
115115
}
116-
} else {
117-
fmt.Fprintln(discoveredFH, "No AEM instances discovered.")
116+
discoveredFH.Close()
118117
}
119-
discoveredFH.Close()
120118
}
121119

122120
// Don't send individual discovery messages - will be sent in consolidated file from Run()
@@ -238,39 +236,22 @@ func Run(opts Options) (*Result, error) {
238236
}
239237
res.DiscoveredCount = len(discovered)
240238

241-
// Always create consolidated result file even if no instances discovered
239+
// If no AEM instances discovered, save JSON and return (no file created — SendPhaseFiles sends message)
242240
if len(discovered) == 0 {
243241
log.Printf("[AEM] No AEM instances discovered")
244242

245-
// Create consolidated file with "no results" message
246-
consolidatedFile := filepath.Join(outputDir, "aem-scan.txt")
247-
consolidatedF, err := os.Create(consolidatedFile)
248-
if err == nil {
249-
defer consolidatedF.Close()
250-
domain := opts.Domain
251-
if domain == "" && opts.LiveHostsFile != "" {
252-
domain = "targets"
253-
}
254-
fmt.Fprintf(consolidatedF, "AEM Scan Results for %s\n", domain)
255-
fmt.Fprintf(consolidatedF, "========================================\n\n")
256-
fmt.Fprintf(consolidatedF, "No AEM instances discovered.\n")
257-
}
258-
259-
// Save empty results to JSON
243+
// Save empty results to JSON only
260244
allResults := map[string]interface{}{
261245
"discovered_count": 0,
262246
"vulnerabilities": 0,
263247
"discovered": []string{},
264248
"findings": []Finding{},
265-
"scan_time": time.Now().Format(time.RFC3339),
249+
"scan_time": time.Now().Format(time.RFC3339),
266250
}
267251
if data, err := json.MarshalIndent(allResults, "", " "); err == nil {
268252
os.WriteFile(resultsFile, data, 0644)
269253
}
270254

271-
// Webhook sending removed - files are sent via utils.SendPhaseFiles from phase functions
272-
273-
274255
res.Duration = time.Since(startTime)
275256
return res, nil
276257
}

internal/modules/backup/backup.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,6 @@ func Run(opts Options) (*Result, error) {
189189
}
190190
}
191191

192-
// Always write a message if no results found
193-
if foundCount == 0 {
194-
resultsFH.WriteString("No backup files found.\n")
195-
}
196-
197192
log.Printf("[INFO] Backup scan: Wrote %d backup URLs to results file: %s", foundCount, resultsFile)
198193

199194
res.Duration = time.Since(start)

internal/modules/depconfusion/depconfusion.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,12 @@ func convertJSONToText(jsonFile, textFile string) error {
357357
}
358358

359359
// Rewrite the JSON file to only contain vulnerable entries (clean up false positives at source)
360-
if cleanJSON, marshalErr := json.MarshalIndent(vulnerable, "", " "); marshalErr == nil {
361-
os.WriteFile(jsonFile, cleanJSON, 0644)
360+
if len(vulnerable) == 0 {
361+
os.WriteFile(jsonFile, []byte{}, 0644) // Leave JSON file entirely empty
362+
} else {
363+
if cleanJSON, marshalErr := json.MarshalIndent(vulnerable, "", " "); marshalErr == nil {
364+
os.WriteFile(jsonFile, cleanJSON, 0644)
365+
}
362366
}
363367

364368
// Create text file
@@ -369,9 +373,7 @@ func convertJSONToText(jsonFile, textFile string) error {
369373
defer f.Close()
370374

371375
if len(vulnerable) == 0 {
372-
fmt.Fprintf(f, "=== Dependency Confusion Scan Results ===\n\n")
373-
fmt.Fprintf(f, "No vulnerable packages found.\n")
374-
return nil
376+
return nil // Leave text file empty, do not write header or "No vulnerable packages found"
375377
}
376378

377379
// Write header

internal/modules/dns/dns.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -537,17 +537,19 @@ func checkAzureAWS(domainDir, findingsDir, subsFile string) error {
537537
return err
538538
}
539539

540-
// Append simple summary to combo file
541-
summary := []string{
542-
"=== AZURE & AWS SUBDOMAIN TAKEOVER DETECTION SUMMARY ===",
543-
"Scan Date: " + timeNowString(),
544-
fmt.Sprintf("Total Subdomains Checked: %d", mustCount(subsFile)),
545-
fmt.Sprintf("Azure Vulnerabilities Found: %d", azureCount),
546-
fmt.Sprintf("AWS Vulnerabilities Found: %d", awsCount),
547-
fmt.Sprintf("Total Vulnerabilities: %d", vulnCount),
548-
}
549-
if err := appendLines(comboOut, summary...); err != nil {
550-
return err
540+
// Append simple summary to combo file only if vulnerabilities were found
541+
if vulnCount > 0 {
542+
summary := []string{
543+
"=== AZURE & AWS SUBDOMAIN TAKEOVER DETECTION SUMMARY ===",
544+
"Scan Date: " + timeNowString(),
545+
fmt.Sprintf("Total Subdomains Checked: %d", mustCount(subsFile)),
546+
fmt.Sprintf("Azure Vulnerabilities Found: %d", azureCount),
547+
fmt.Sprintf("AWS Vulnerabilities Found: %d", awsCount),
548+
fmt.Sprintf("Total Vulnerabilities: %d", vulnCount),
549+
}
550+
if err := appendLines(comboOut, summary...); err != nil {
551+
return err
552+
}
551553
}
552554

553555
// Send findings to webhook if configured

internal/modules/misconfig/misconfig.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,6 @@ func handleScan(opts Options, resultsDir string) error {
172172
return fmt.Errorf("failed to write result: %w", err)
173173
}
174174
}
175-
} else {
176-
// Write "no results" message to file
177-
f.WriteString("No misconfiguration findings found.\n")
178175
}
179176

180177
fmt.Printf("[OK] Misconfig scan completed for %s (%d findings)\n", opts.Target, len(allResults))

internal/modules/ports/ports.go

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,36 +63,14 @@ func ScanPorts(domain string, threads int) (*Result, error) {
6363
count, err := naabutool.ScanFromFile(subsFile, threads, outFile)
6464
if err != nil {
6565
log.Printf("[WARN] Naabu scan failed: %v", err)
66-
// Create empty file with "no results" message
67-
if f, err := os.Create(outFile); err == nil {
68-
f.WriteString("No open ports found (excluding ports 80, 443, 8080 and 8443).\n")
69-
f.Close()
70-
}
7166
count = 0
72-
} else {
73-
// Check if file is empty and write "no results" message if so
74-
if info, err := os.Stat(outFile); err == nil && info.Size() == 0 {
75-
if f, err := os.OpenFile(outFile, os.O_WRONLY|os.O_APPEND, 0644); err == nil {
76-
f.WriteString("No open ports found (excluding ports 80, 443, 8080 and 8443).\n")
77-
f.Close()
78-
}
79-
}
8067
}
8168
log.Printf("[OK] Port scan completed, found %d open ports", count)
8269

8370
// Send result files to Discord webhook if configured (only when not running under bot)
8471
// When running under bot (AUTOAR_CURRENT_SCAN_ID is set), the bot handles R2 upload and zip link
8572
if os.Getenv("AUTOAR_CURRENT_SCAN_ID") == "" {
86-
webhookURL := os.Getenv("DISCORD_WEBHOOK")
87-
if webhookURL != "" {
88-
// Send ports.txt if it exists and has content
89-
if info, err := os.Stat(outFile); err == nil && info.Size() > 0 {
90-
utils.SendWebhookFileAsync(outFile, fmt.Sprintf("Port Scan Results: %d open ports found for %s", count, domain))
91-
} else if count == 0 {
92-
// Send "no findings" message if no ports found
93-
utils.SendWebhookLogAsync(fmt.Sprintf("Port scan completed for %s: 0 open ports found (excluding ports 80, 443, 8080 and 8443)", domain))
94-
}
95-
}
73+
utils.SendPhaseFiles("ports", domain, []string{outFile})
9674
}
9775

9876
return &Result{

internal/modules/tech/tech.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@ func DetectTech(domain string, threads int) (*Result, error) {
8787
}
8888

8989
if len(targets) == 0 {
90-
log.Printf("[WARN] No live hosts found; creating empty tech file for %s", domain)
91-
if f, err := os.Create(outFile); err == nil {
92-
f.WriteString("No live hosts found for technology detection.\n")
93-
f.Close()
94-
}
90+
log.Printf("[WARN] No live hosts found for %s; skipping tech detection", domain)
9591
return &Result{
9692
Domain: domain,
9793
Hosts: 0,

internal/modules/utils/discord.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,7 @@ func SendPhaseFiles(phaseName, domain string, filePaths []string) error {
7676

7777
if len(existingFiles) == 0 {
7878
log.Printf("[DEBUG] [DISCORD] No valid files to send for phase %s", phaseName)
79-
// Send a short ⚪ message indicating 0 findings
80-
msg := fmt.Sprintf("⚪ **%s** — 0 findings", phaseName)
81-
if domain != "" {
82-
msg = fmt.Sprintf("⚪ **%s** — 0 findings for `%s`", phaseName, domain)
83-
}
79+
msg := phaseNoResultsMessage(phaseName, domain)
8480
SendWebhookLogAsync(msg)
8581
return nil
8682
}
@@ -416,3 +412,51 @@ func GetPhaseFiles(phaseName, domain string) []string {
416412
return files
417413
}
418414

415+
// phaseNoResultsMessage returns a styled Discord message for a phase that yielded zero results
416+
func phaseNoResultsMessage(phaseName, domain string) string {
417+
target := domain
418+
if target == "" {
419+
target = "targets"
420+
} else {
421+
target = "`" + target + "`"
422+
}
423+
424+
switch phaseName {
425+
case "ports":
426+
return fmt.Sprintf("[ ⚪ ] **Port Scan** — No open ports found (excluding 80, 443, 8080, 8443) for %s", target)
427+
case "aem", "aem_scan":
428+
return fmt.Sprintf("[ ⚪ ] **AEM Scan** — No AEM instances discovered for %s", target)
429+
case "tech":
430+
return fmt.Sprintf("[ ⚪ ] **Tech Detection** — No live hosts found for %s", target)
431+
case "backup":
432+
return fmt.Sprintf("[ ⚪ ] **Backup Scan** — No backup files found for %s", target)
433+
case "misconfig":
434+
return fmt.Sprintf("[ ⚪ ] **Misconfig Scan** — No misconfigurations found for %s", target)
435+
case "subdomains":
436+
return fmt.Sprintf("[ ⚪ ] **Subdomains** — No subdomains found for %s", target)
437+
case "livehosts":
438+
return fmt.Sprintf("[ ⚪ ] **Live Hosts** — No live hosts found for %s", target)
439+
case "urls":
440+
return fmt.Sprintf("[ ⚪ ] **URLs** — No interesting URLs found for %s", target)
441+
case "jsscan", "js":
442+
return fmt.Sprintf("[ ⚪ ] **JS Scan** — No JavaScript vulnerabilities found for %s", target)
443+
case "nuclei":
444+
return fmt.Sprintf("[ ⚪ ] **Nuclei** — No vulnerabilities found for %s", target)
445+
case "gf":
446+
return fmt.Sprintf("[ ⚪ ] **GF Patterns** — No vulnerable parameters found for %s", target)
447+
case "s3":
448+
return fmt.Sprintf("[ ⚪ ] **S3 Scan** — No exposed buckets found for %s", target)
449+
case "githubscan":
450+
return fmt.Sprintf("[ ⚪ ] **GitHub Scan** — No secrets found for %s", target)
451+
case "zerodays", "0days":
452+
return fmt.Sprintf("[ ⚪ ] **0-Days** — No zero-day vulnerabilities found for %s", target)
453+
case "ffuf":
454+
return fmt.Sprintf("[ ⚪ ] **Fuzzing** — No hidden directories found for %s", target)
455+
default:
456+
name := phaseName
457+
if len(phaseName) > 0 {
458+
name = strings.ToUpper(string(phaseName[0])) + phaseName[1:]
459+
}
460+
return fmt.Sprintf("[ ⚪ ] **%s** — 0 findings for %s", name, target)
461+
}
462+
}

0 commit comments

Comments
 (0)