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 a60597e8f855707c0d3ec8f1a1122e90433435be
parent b44dc08bc6aaaadb40133c653185c0d25af27a99
Author: djnn <[email protected]>
Date: Sat, 13 Jun 2026 12:06:10 +0900

fix(meta): warnings and nitpicks 1. robots.txt scan was silently broken runRobots() instantiated Sitemap{} instead of RobotsTxt{}, so it fetched /sitemap.xml twice and never checked /robots.txt. Changed to RobotsTxt{}; the dead RobotsTxt type is now live. 2. No HTTP client timeouts Added helper.GetHttpClient(insecure bool) with a shared 15s HttpTimeout, routed GetCurrentIP, HasUnavailableWebInterface, robots, sitemap, and wappalyzer through it. cvemap got a 30s timeout. A dead host can no longer hang the run. Also removed duplicated crypto/tls boilerplate from 3 files. 3. Unchecked http.NewRequest → latent nil panic Added error checks with continue before req is used. 4. scan help text lied Long listed phantom tools (nuclei, gowitness) and omitted real ones. Now reads: sitemap, robots.txt, wappalyzergo, cvemap, httpx, gobuster, optional port-scan. 5. OSINT panicked on missing files RunCleanup and the domain-aggregation loop now log and skip missing files instead of crashing with a stack trace. 6. gobuster context leak context.WithCancel's cancel func was discarded. Now captured with defer cancel(). 7. staticcheck S1008 Exists collapsed to return err == nil. 8. staticcheck U1000 Deleted the fully dead AssetfinderConfig type. 9. staticcheck S1030 string(pretty.Bytes()) → pretty.String().

Diffstat:
Mcli/parser.go | 2+-
Mhelpers/directories.go | 5+----
Mhelpers/net.go | 27+++++++++++++++++----------
Mosint/assetfinder.go | 5-----
Mosint/run.go | 15++++++---------
Mosint/shodan.go | 2+-
Mscan/cvemap.go | 2+-
Mscan/gobuster.go | 3++-
Mscan/robotstxt.go | 12+++++-------
Mscan/run.go | 2+-
Mscan/sitemap.go | 12+++++-------
Mscan/wappalyzergo.go | 8+-------

12 files changed, 41 insertions(+), 54 deletions(-)

diff --git a/cli/parser.go b/cli/parser.go
@@ -88,7 +88,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
 	var scanCmd = &cobra.Command{
 		Use:   "scan",
 		Short: "Run active scanning tools to perform enumeration",
-		Long:  "Runs active scanning (wappalyzergo, sitemap, robots.txt, gobuster, nuclei, httpx, gowitness)",
+		Long:  "Runs active scanning (sitemap, robots.txt, wappalyzergo, cvemap, httpx, gobuster, and optional TCP port-scan)",
 		Args:  cobra.MinimumNArgs(0),
 		Run: func(cmd *cobra.Command, args []string) {
 
diff --git a/helpers/directories.go b/helpers/directories.go
@@ -33,10 +33,7 @@ func SetUpDirectoryArchitecture(target string) {
 
 func Exists(path string) bool {
 	_, err := os.Stat(path)
-	if err == nil {
-		return true
-	}
-	return false
+	return err == nil
 }
 
 func CreateDirectory(base string, folders []Folder) {
diff --git a/helpers/net.go b/helpers/net.go
@@ -6,11 +6,14 @@ import (
 	"io"
 	"net/http"
 	"net/url"
+	"time"
 
 	"os"
 	"strings"
 )
 
+const HttpTimeout = 15 * time.Second
+
 func ParseDomain(website string) string {
 	if strings.HasPrefix(website, "http") {
 		parsedUrl, err := url.Parse(website)
@@ -37,17 +40,26 @@ func GetHttpTransport() *http.Transport {
 	return &http.Transport{}
 }
 
+func GetHttpClient(insecure bool) *http.Client {
+	transport := GetHttpTransport()
+	if insecure {
+		transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+	}
+
+	return &http.Client{
+		Transport: transport,
+		Timeout:   HttpTimeout,
+	}
+}
+
 func GetUserAgent() string {
 	return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
 }
 
 func GetCurrentIP() string {
-	ht := GetHttpTransport()
 	ua := GetUserAgent()
 
-	cli := &http.Client{
-		Transport: ht,
-	}
+	cli := GetHttpClient(false)
 
 	req, err := http.NewRequest("GET", "http://icanhazip.com", nil)
 	if err != nil {
@@ -98,12 +110,7 @@ func CheckProxy(proxy string) {
 func HasUnavailableWebInterface(url string) bool {
 	url = strings.TrimSuffix(url, "/")
 
-	transport := GetHttpTransport()
-	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
-	client := &http.Client{
-		Transport: transport,
-	}
+	client := GetHttpClient(true)
 
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
diff --git a/osint/assetfinder.go b/osint/assetfinder.go
@@ -14,11 +14,6 @@ import (
 
 type fetchFn func(string) ([]string, error)
 
-type AssetfinderConfig struct {
-	scanPath string
-	outfile  string
-}
-
 type Assetfinder struct {
 	outfile   string
 	functions []fetchFn
diff --git a/osint/run.go b/osint/run.go
@@ -51,14 +51,10 @@ func (opts OsintOpts) RunCleanup() {
 	asfFilepath := fmt.Sprintf("%s/assetfinder.txt", opts.scanPath)
 	sbfOutfile := fmt.Sprintf("%s/subfinder.txt", opts.scanPath)
 
-	err := os.Remove(asfFilepath)
-	if err != nil {
-		panic(err)
-	}
-
-	err = os.Remove(sbfOutfile)
-	if err != nil {
-		panic(err)
+	for _, f := range []string{asfFilepath, sbfOutfile} {
+		if err := os.Remove(f); err != nil && !os.IsNotExist(err) {
+			fmt.Printf("[!] cleanup: could not remove %s: %v\n", f, err)
+		}
 	}
 }
 
@@ -183,7 +179,8 @@ func (opts OsintOpts) Run() {
 	for _, file := range domainsFiles {
 		newDomains, err := os.ReadFile(file)
 		if err != nil {
-			panic(err)
+			fmt.Printf("[!] osint: could not read %s, skipping: %v\n", file, err)
+			continue
 		}
 
 		/* check if valid IP address / domain & if it's already in the list */
diff --git a/osint/shodan.go b/osint/shodan.go
@@ -121,7 +121,7 @@ func (s *Shodan) Run(ip string) {
 
 	summary, perr := buildShodanSummaryFromJSON(bytes.NewReader(pretty.Bytes()), ip)
 	if perr != nil {
-		summary = fmt.Sprintf("IP: %s\n\n(Unable to build structured summary: %v)\n\n%s\n", ip, perr, string(pretty.Bytes()))
+		summary = fmt.Sprintf("IP: %s\n\n(Unable to build structured summary: %v)\n\n%s\n", ip, perr, pretty.String())
 	}
 
 	f, err := os.OpenFile(s.outfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
diff --git a/scan/cvemap.go b/scan/cvemap.go
@@ -116,7 +116,7 @@ func (c *Cvemap) Run(techs []string) {
 		return
 	}
 
-	client := &http.Client{}
+	client := &http.Client{Timeout: 30 * time.Second}
 	if c.HTTPProxy != "" {
 		proxyURL, err := url.Parse(c.HTTPProxy)
 		if err != nil {
diff --git a/scan/gobuster.go b/scan/gobuster.go
@@ -73,7 +73,8 @@ func (g *Gobuster) Run(rawUrl string) {
 		panic("[gobuster] cannot load plugin: " + err.Error())
 	}
 
-	mainContext, _ := context.WithCancel(context.Background())
+	mainContext, cancel := context.WithCancel(context.Background())
+	defer cancel()
 	if err := cli.Gobuster(mainContext, &globalOpts, plugin, log); err != nil {
 
 		var wErr *gobusterdir.WildcardError
diff --git a/scan/robotstxt.go b/scan/robotstxt.go
@@ -1,7 +1,6 @@
 package scan
 
 import (
-	"crypto/tls"
 	"fmt"
 	"io"
 	"net/http"
@@ -25,15 +24,14 @@ func (d *RobotsTxt) Configure(c any) {
 func (d *RobotsTxt) Run(domain string) {
 	domain = strings.TrimSuffix(domain, "/")
 
-	transport := helper.GetHttpTransport()
-	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
-	client := &http.Client{
-		Transport: transport,
-	}
+	client := helper.GetHttpClient(true)
 
 	for _, u := range getUrls(domain) {
 		req, err := http.NewRequest("GET", fmt.Sprint(u, "/robots.txt"), nil)
+		if err != nil {
+			fmt.Printf("%v", err)
+			continue
+		}
 		req.Header.Add("User-Agent", helper.GetUserAgent())
 
 		resp, err := client.Do(req)
diff --git a/scan/run.go b/scan/run.go
@@ -69,7 +69,7 @@ func (opts *ScanOpts) SetPorts(data string) {
 
 func (opts ScanOpts) runRobots() {
 
-	rb := Sitemap{}
+	rb := RobotsTxt{}
 	rbCfg := make(map[string]any)
 
 	rbCfg["Proxy"] = opts.proxy
diff --git a/scan/sitemap.go b/scan/sitemap.go
@@ -1,7 +1,6 @@
 package scan
 
 import (
-	"crypto/tls"
 	"fmt"
 	"io"
 	"net/http"
@@ -28,15 +27,14 @@ func (d *Sitemap) Configure(c any) {
 func (d *Sitemap) Run(domain string) {
 	domain = strings.TrimSuffix(domain, "/")
 
-	transport := helper.GetHttpTransport()
-	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
-	client := &http.Client{
-		Transport: transport,
-	}
+	client := helper.GetHttpClient(true)
 
 	for _, u := range getUrls(domain) {
 		req, err := http.NewRequest("GET", fmt.Sprint(u, "/sitemap.xml"), nil)
+		if err != nil {
+			fmt.Printf("%v", err)
+			continue
+		}
 		req.Header.Add("User-Agent", helper.GetUserAgent())
 
 		resp, err := client.Do(req)
diff --git a/scan/wappalyzergo.go b/scan/wappalyzergo.go
@@ -1,7 +1,6 @@
 package scan
 
 import (
-	"crypto/tls"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -64,12 +63,7 @@ func (d *WappalyzerGo) Run(targetURL string) []string {
 }
 
 func (d *WappalyzerGo) newHTTPClient() *http.Client {
-	transport := helper.GetHttpTransport()
-	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
-
-	return &http.Client{
-		Transport: transport,
-	}
+	return helper.GetHttpClient(true)
 }
 
 func (d *WappalyzerGo) fetchResponse(client *http.Client, targetURL string) (*http.Response, []byte, error) {