yellow.git


yellow

~ opinionated web scanning pipeline for pentesters in a hurry
git clone https://github.com/djnnvx/yellow.git
log | files | refs | archive | README | LICENSE

commit 7b21a6efa19df8567031d6f5f11a7917b616eb53
parent 9a3e88825ec18fc1d370db5c83c4bd4ca3e18bf2
Author: djnn <[email protected]>
Date: Sun, 9 Aug 2026 15:51:25 +0900

fix(cli): target files stream line by line

Diffstat:
MCHANGELOG.txt | 1+
Mcli/parser.go | 12++++++------
Mhelpers/file.go | 27+++++++++++++++++++++++++--
Ahelpers/file_test.go | 38++++++++++++++++++++++++++++++++++++++
Mprune/run.go | 4+---

5 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,6 @@
 ## v0.0.8
 
+- fix(cli): target files stream line by line ~ @djnnvx
 - fix(cli): fingerprint -d should only run wappalyzer and cvemap ~ @djnnvx
 - fix(scan): bind every response body read ~ @djnnvx
 - fix(scan): nuclei ignores non-match events, a rate-limited host reported 95913 fake findings ~ @djnnvx
diff --git a/cli/parser.go b/cli/parser.go
@@ -85,8 +85,8 @@ func GetParser(opts *StandardOptions) *cobra.Command {
 			scanner := helper.LoadTargetFile(opts.TargetFilePath)
 			defer scanner.Close()
 
-			for scanner.Scan() {
-				scanOpts.Domain = scanner.Text()
+			for target := range scanner.Lines() {
+				scanOpts.Domain = target
 				scanOpts.Fingerprint()
 			}
 		},
@@ -139,8 +139,8 @@ func GetParser(opts *StandardOptions) *cobra.Command {
 			scanner := helper.LoadTargetFile(opts.TargetFilePath)
 			defer scanner.Close()
 
-			for scanner.Scan() {
-				scanOpts.Domain = scanner.Text()
+			for target := range scanner.Lines() {
+				scanOpts.Domain = target
 				scanOpts.Run()
 			}
 		},
@@ -188,8 +188,8 @@ func GetParser(opts *StandardOptions) *cobra.Command {
 			scanner := helper.LoadTargetFile(opts.TargetFilePath)
 			defer scanner.Close()
 
-			for scanner.Scan() {
-				osintOpts.Domain = scanner.Text()
+			for target := range scanner.Lines() {
+				osintOpts.Domain = target
 				osintOpts.Run()
 			}
 		},
diff --git a/helpers/file.go b/helpers/file.go
@@ -4,12 +4,15 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"iter"
 	"os"
+	"strings"
 )
 
 type FileScanner struct {
 	io.Closer
-	*bufio.Scanner
+	reader *bufio.Reader
+	path   string
 }
 
 func LoadTargetFile(targetPath string) *FileScanner {
@@ -20,5 +23,25 @@ func LoadTargetFile(targetPath string) *FileScanner {
 	}
 
 	fmt.Println("[+] Loaded targets from", targetPath)
-	return &FileScanner{file, bufio.NewScanner(file)}
+	return &FileScanner{file, bufio.NewReader(file), targetPath}
+}
+
+// Lines streams the file with no line-length cap. A read failure exits instead
+// of ending the run short with nothing to show for it.
+func (f *FileScanner) Lines() iter.Seq[string] {
+	return func(yield func(string) bool) {
+		for {
+			line, err := f.reader.ReadString('\n')
+			if t := strings.TrimSpace(line); t != "" && !yield(t) {
+				return
+			}
+			if err != nil {
+				if err != io.EOF {
+					fmt.Printf("[!] Could not read target file %q: %v\n", f.path, err)
+					os.Exit(1)
+				}
+				return
+			}
+		}
+	}
 }
diff --git a/helpers/file_test.go b/helpers/file_test.go
@@ -0,0 +1,38 @@
+package helper
+
+import (
+	"os"
+	"path/filepath"
+	"slices"
+	"strings"
+	"testing"
+)
+
+// a line past bufio.Scanner's 64KB cap used to end the run silently
+func TestLoadTargetFileKeepsGoingPastALongLine(t *testing.T) {
+	long := strings.Repeat("A", 70_000)
+	path := filepath.Join(t.TempDir(), "targets.txt")
+	if err := os.WriteFile(path, []byte(long+"\n\nexample.com\n  example.org  \n"), 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	f := LoadTargetFile(path)
+	defer f.Close()
+
+	got := slices.Collect(f.Lines())
+	want := []string{long, "example.com", "example.org"}
+	if !slices.Equal(got, want) {
+		t.Errorf("got %d targets %q, want %d", len(got), truncate(got), len(want))
+	}
+}
+
+func truncate(lines []string) []string {
+	out := make([]string, len(lines))
+	for i, l := range lines {
+		if len(l) > 20 {
+			l = l[:20] + "..."
+		}
+		out[i] = l
+	}
+	return out
+}
diff --git a/prune/run.go b/prune/run.go
@@ -25,9 +25,7 @@ func (opts *PruneOpts) Run() {
 	scanner := helper.LoadTargetFile(opts.InFilePath)
 	defer scanner.Close()
 
-	for scanner.Scan() {
-		targetDomain := scanner.Text()
-
+	for targetDomain := range scanner.Lines() {
 		if target, ok := helper.ResolveWebTarget(targetDomain, opts.ForceInsecure); ok {
 			fmt.Printf("[+] %s is alive (%s)!\n", targetDomain, target)
 			results = append(results, targetDomain)