diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
@@ -19,7 +19,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
- go-version: 1.25
+ go-version: 1.26
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
diff --git a/.gitignore b/.gitignore
@@ -35,6 +35,7 @@ target.txt
dist/
yellow
nuclei-templates/
+inventory/__pycache__/
# Out files
.yellow
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,14 +1,34 @@
-## v0.0.7
+## v0.0.6
+- fix(scan): perform the real robots.txt fetch (was duplicating the sitemap scan)
+- fix(net): add HTTP client timeouts and close response bodies (prevents hangs and fd leaks)
+- fix(net): keep running when the public IP lookup fails instead of exiting the program
+- fix(helpers): exit cleanly on an unreadable target file instead of a nil-deref panic
+- fix(osint): replace panics and log.Fatal with graceful skips (subfinder, dnsx, file aggregation)
+- fix(scan): skip a target instead of panicking when its output dir cannot be created
+- fix(scan): report port scan write failures instead of silently dropping results
+- fix(scan): check request errors in robots/sitemap and release the gobuster scan context
+- fix(cli): remove the vestigial prune --dir-name flag that collided with --out
+- fix(cli): correct the scan command help to match the tools it actually runs
+- fix(helpers): FolderNameFactory no longer allocates phantom empty folders
+- fix(helpers): report directory-creation failures in the create command instead of silently doing nothing
+- fix(scripts): quick-run.sh now uses set -euo pipefail (pipefail was off and the options table was dumped)
+- fix(scripts): use portable /usr/bin/env shebangs in the inventory scripts
+- fix(scan): convert remaining cvemap/gobuster/httpx panics to graceful skips
+- fix(osint): graceful error on the final domains.txt write instead of panicking
+- fix(cli): prune validates the --out path up front instead of losing results after the work
+- fix(net): trim the trailing newline from the detected public IP
+- chore(cleanup): replace the builtin println with fmt across main, cli, and osint
+- docs(readme): drop the already-implemented robots.txt item from the roadmap
+- chore(cleanup): drop dead code (ParseDomain, AssetfinderConfig) and satisfy staticcheck
+- chore(build): align Go version to 1.26 across Dockerfile, README, and CI
+- chore(build): copy go.sum in the Docker dependency layer for verified builds
- chore(deps): bump nerva to 1.18.0
- chore(deps): bump subfinder to 2.14.0
- chore(deps): bump wappalyzergo to 0.2.84
- chore(deps): bump vflame6/leaker to 1.6.7
- chore(deps): bump golang.org/x/* packages (crypto, net, sys, term, text)
- chore(deps): bump modernc.org/sqlite to 1.51.0, shirou/gopsutil to v4
-
-## v0.0.6
-
- chore(deps): bump go version to 1.26
- chore(deps): bump nerva to 1.3.0
- chore(deps): bump vflame6/leaker to 1.6.4
@@ -16,7 +36,7 @@
- chore(deps): bump golang.org/x/* packages (crypto, net, sys, text, tools, etc.)
- chore(deps): bump grpc to 1.80.0, miekg/dns to 1.1.72, klauspost/compress to 1.18.5
- chore(deps): bump go-exploit to 1.58.0
-- chore(meta): bump nerva libarry to 1.2.0 + using the new --misconfig flag
+- chore(meta): bump nerva library to 1.2.0 + using the new --misconfig flag
- chore(meta): bump nerva library to 1.1.0
## v0.0.5
diff --git a/Dockerfile b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.25-alpine AS builder
+FROM golang:1.26-alpine AS builder
WORKDIR /app
RUN apk update --no-cache && \
@@ -7,7 +7,7 @@ RUN apk update --no-cache && \
make \
build-base
-COPY go.mod .
+COPY go.mod go.sum ./
RUN go mod download
COPY . .
diff --git a/README.md b/README.md
@@ -49,7 +49,7 @@ For next version, i want to take care of at least two items described here:
## 2.a Bugfixes
-* sitemap should be stored to a file & fetch robots.txt
+* sitemap should be stored to a file
feel free to suggest more ideas. :)~
if you'd like to do so, reach me by mail or on social media: https://djnn.sh/pgp
@@ -62,7 +62,7 @@ am able to maintain it afterwards.
# 3. Installing
-Using go v1.25.
+Using go v1.26.
```bash
git clone https://evil.djnn.sh/yellow.git
diff --git a/cli/parser.go b/cli/parser.go
@@ -28,6 +28,12 @@ func GetParser(opts *StandardOptions) *cobra.Command {
os.Exit(1)
}
+ if opts.OutName == "" {
+ fmt.Println("[!] Error: output path cannot be empty. Please run with --out [something]")
+ fmt.Println("\nIf you're confused, feel free to use --help option. :)~")
+ os.Exit(1)
+ }
+
pruneOpts := prune.PruneOpts{}
pruneOpts.SetProxy(opts.Proxy)
pruneOpts.SetDryRun(opts.RunDry)
@@ -56,7 +62,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
helper.DisplayNetInfo()
if opts.OutName == "" || !helper.Exists(opts.OutName) {
- println("[!] collected assets will be sent to current working directory")
+ fmt.Println("[!] collected assets will be sent to current working directory")
}
scanOpts := scan.ScanOpts{}
@@ -102,7 +108,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
helper.DisplayNetInfo()
if opts.OutName == "" || !helper.Exists(opts.OutName) {
- println("[!] collected assets will be sent to current working directory")
+ fmt.Println("[!] collected assets will be sent to current working directory")
}
scanOpts := scan.ScanOpts{}
@@ -150,7 +156,7 @@ func GetParser(opts *StandardOptions) *cobra.Command {
helper.DisplayNetInfo()
if opts.OutName == "" || !helper.Exists(opts.OutName) {
- println("[!] collected assets will be sent to current working directory")
+ fmt.Println("[!] collected assets will be sent to current working directory")
}
osintOpts := osint.OsintOpts{}
@@ -233,7 +239,6 @@ func GetParser(opts *StandardOptions) *cobra.Command {
scanCmd.Flags().StringVarP(&opts.Ports, "ports", "", defaults.Ports, "Ports to scan, comma-separated or ranges (e.g. 22,80,443,8000-9000)")
rootCmd.AddCommand(pruneCmd)
- pruneCmd.Flags().StringVarP(&opts.OutName, "dir-name", "d", defaults.OutName, "Outfile directory name (if no target-file is specified, will also be target domain)")
pruneCmd.Flags().StringVarP(&opts.Proxy, "proxy", "p", defaults.Proxy, "Proxy URL (used for the tools supporting it. Other will prompt a warning msg)")
pruneCmd.Flags().BoolVarP(&opts.UseHttpInsecure, "insecure", "k", defaults.UseHttpInsecure, "Ignore SSL warnings and force http")
pruneCmd.Flags().StringVarP(&opts.TargetFilePath, "file", "f", defaults.TargetFilePath, "File containing list of targets (should be a list of IP Addresses or domains)")
diff --git a/helpers/directories.go b/helpers/directories.go
@@ -14,7 +14,10 @@ func SetUpDirectoryArchitecture(target string) {
dirname := ReplaceWithHyphen(target)
fmt.Println("[+] setting up directory architecture for", dirname)
- _ = os.MkdirAll(dirname, 0775)
+ if err := os.MkdirAll(dirname, 0775); err != nil {
+ fmt.Printf("[!] could not create %s: %v\n", dirname, err)
+ return
+ }
CreateDirectory(dirname, []Folder{
{
Name: "scans",
@@ -39,7 +42,9 @@ func Exists(path string) bool {
func CreateDirectory(base string, folders []Folder) {
for _, f := range folders {
current := fmt.Sprintf("%s/%s", base, f.Name)
- _ = os.Mkdir(current, 0775)
+ if err := os.Mkdir(current, 0775); err != nil && !os.IsExist(err) {
+ fmt.Printf("[!] could not create %s: %v\n", current, err)
+ }
if len(f.Children) != 0 {
CreateDirectory(current, f.Children)
}
@@ -47,7 +52,7 @@ func CreateDirectory(base string, folders []Folder) {
}
func FolderNameFactory(names ...string) []Folder {
- f := make([]Folder, len(names))
+ f := make([]Folder, 0, len(names))
for _, name := range names {
f = append(f, Folder{Name: name})
}
diff --git a/helpers/net.go b/helpers/net.go
@@ -58,18 +58,18 @@ func GetCurrentIP() string {
resp, err := cli.Do(req)
if err != nil || resp == nil {
- fmt.Printf("[!] Could not query public IP address: %s\n", err.Error())
- return ""
+ fmt.Printf("[!] Could not query public IP address: %v\n", err)
+ return "127.0.0.1" // service offline or unreachable, we should be able to keep going
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
- fmt.Printf("[!] Could not IP address: %s\n", err.Error())
- return ""
+ fmt.Printf("[!] Could not read public IP address: %v\n", err)
+ return "127.0.0.1"
}
- return string(result)
+ return strings.TrimSpace(string(result))
}
func DisplayNetInfo() {
diff --git a/inventory/quick-run.sh b/inventory/quick-run.sh
@@ -1,6 +1,6 @@
-#!/bin/env bash
+#!/usr/bin/env bash
-set -euo
+set -euo pipefail
./yellow --help || make
diff --git a/inventory/run-web-recon b/inventory/run-web-recon
@@ -1,4 +1,4 @@
-#!/bin/env python3
+#!/usr/bin/env python3
"""
HTB recon script — uses yellow to set up the environment and run an initial
diff --git a/main.go b/main.go
@@ -1,6 +1,8 @@
package main
import (
+ "fmt"
+
"evil.djnn.sh/djnn/yellow/cli"
)
@@ -36,7 +38,7 @@ const ASCII_ART = `
`
func main() {
- println(ASCII_ART)
+ fmt.Print(ASCII_ART)
opts := cli.GetDefaultOptions()
parser := cli.GetParser(&opts)
diff --git a/osint/run.go b/osint/run.go
@@ -45,7 +45,7 @@ func (opts *OsintOpts) SetEmailsFile(data string) {
}
func (opts OsintOpts) RunCleanup() {
- println("[+] cleaning up...")
+ fmt.Println("[+] cleaning up...")
/* remove superfluous files */
asfFilepath := fmt.Sprintf("%s/assetfinder.txt", opts.scanPath)
@@ -210,9 +210,9 @@ func (opts OsintOpts) Run() {
// now add all assets together, line by line
uniqueOutfile := fmt.Sprintf("%s/domains.txt", opts.scanPath)
- err := os.WriteFile(uniqueOutfile, domainBuffer.Bytes(), 0644)
- if err != nil {
- panic(err)
+ if err := os.WriteFile(uniqueOutfile, domainBuffer.Bytes(), 0644); err != nil {
+ fmt.Printf("[!] osint: could not write %s: %v\n", uniqueOutfile, err)
+ return
}
fmt.Printf("[OSINT %s] Registered %v IP addresses and assets.\n", opts.domain, len(domains))
diff --git a/scan/cvemap.go b/scan/cvemap.go
@@ -225,6 +225,6 @@ func (c *Cvemap) saveResults(cves []nvdCVE) {
filename := c.scanPath + "/cves.json"
if err := os.WriteFile(filename, data, 0644); err != nil {
- panic(err)
+ fmt.Printf("[!] cvemap: could not write %s: %v\n", filename, err)
}
}
diff --git a/scan/fingerprint.go b/scan/fingerprint.go
@@ -26,9 +26,9 @@ func (opts *ScanOpts) Fingerprint() {
pathForDomain := opts.scanPath + "/" + opts.domain
opts.SetScanPath(pathForDomain)
- err := os.MkdirAll(opts.scanPath, 0755)
- if err != nil {
- panic(err)
+ if err := os.MkdirAll(opts.scanPath, 0755); err != nil {
+ fmt.Printf("[!] fingerprint: could not create %s, skipping %s: %v\n", opts.scanPath, opts.domain, err)
+ return
}
opts.SetDomain(fullDomain)
diff --git a/scan/gobuster.go b/scan/gobuster.go
@@ -53,7 +53,8 @@ func (g *Gobuster) Run(rawUrl string) {
u, err := url.Parse(rawUrl)
if err != nil {
- panic("[gobuster] Invalid URL: " + rawUrl)
+ fmt.Printf("[!] gobuster: invalid URL %s: %v\n", rawUrl, err)
+ return
}
pluginOpts := gobusterdir.NewOptions()
@@ -70,7 +71,8 @@ func (g *Gobuster) Run(rawUrl string) {
log := libgobuster.NewLogger(globalOpts.Debug)
plugin, err := gobusterdir.New(&globalOpts, pluginOpts, log)
if err != nil {
- panic("[gobuster] cannot load plugin: " + err.Error())
+ fmt.Printf("[!] gobuster: cannot load plugin: %v\n", err)
+ return
}
mainContext, cancel := context.WithCancel(context.Background())
diff --git a/scan/httpx.go b/scan/httpx.go
@@ -43,12 +43,14 @@ func (h *Httpx) Run(domain string) {
}
if err := options.ValidateOptions(); err != nil {
- panic(err)
+ fmt.Printf("[!] httpx: invalid options for %s: %v\n", domain, err)
+ return
}
httpxRunner, err := runner.New(&options)
if err != nil {
- panic(err)
+ fmt.Printf("[!] httpx: could not create runner for %s: %v\n", domain, err)
+ return
}
defer httpxRunner.Close()
diff --git a/scan/portscan.go b/scan/portscan.go
@@ -53,7 +53,10 @@ func (opts ScanOpts) runPortScan() {
}
fmt.Printf("[+] Running port scan on %s\n", opts.domain)
if !opts.dryRun {
- os.MkdirAll(ps.scanPath, 0755)
+ if err := os.MkdirAll(ps.scanPath, 0755); err != nil {
+ fmt.Printf("[!] port scan: could not create %s: %v\n", ps.scanPath, err)
+ return
+ }
ps.Run(opts.domain)
}
}
@@ -84,7 +87,9 @@ func (p *PortScanner) Run(host string) {
}
data, _ := json.MarshalIndent(out, "", " ")
- os.WriteFile(p.scanPath+"/portscan.json", data, 0644)
+ if err := os.WriteFile(p.scanPath+"/portscan.json", data, 0644); err != nil {
+ fmt.Printf("[!] port scan: could not write %s/portscan.json: %v\n", p.scanPath, err)
+ }
fmt.Printf("[SCAN %s] port scan done — %d open ports\n\n", host, len(open))
}
diff --git a/scan/run.go b/scan/run.go
@@ -180,9 +180,9 @@ func (opts *ScanOpts) Run() {
fmt.Printf("[SCAN %s] web panel online....running web scans\n", opts.domain)
pathForDomain := opts.scanPath + "/" + opts.domain
- err := os.MkdirAll(pathForDomain, 0755)
- if err != nil {
- panic(err)
+ if err := os.MkdirAll(pathForDomain, 0755); err != nil {
+ fmt.Printf("[!] scan: could not create %s, skipping %s: %v\n", pathForDomain, opts.domain, err)
+ return
}
/* modifying opts.scanPath directly would cause nested directories */
| | | | | | | | | | | | | | | | | |