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 a908deb2f8bf40d7606f4ef8498ee670ce93d011
parent 6e20f491b52742074cf2e61ad019776d6418001a
Author: djnn <[email protected]>
Date: Sun, 2 Aug 2026 14:06:14 +0900

fix(scan): open ports nerva cannot fingerprint no longer vanish from portscan.json

Diffstat:
MCHANGELOG.txt | 1+
Mscan/portscan.go | 21++++++++++++++++++---
Ascan/portscan_test.go | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

3 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,6 @@
 ## v0.0.7
 
+- fix(scan): open ports nerva cannot fingerprint no longer vanish from portscan.json ~ @djnnvx
 - chore(deps): bump dependencies ~ @djnnvx
 - refactor(core): dead code deletion, simplification ~ @djnnvx
 - fix(osint): gau init errors for one provider dont bring everything down  ~ @djnnvx
diff --git a/scan/portscan.go b/scan/portscan.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"net/netip"
 	"os"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -117,6 +118,7 @@ func (p *PortScanner) tcpScan(host string, ports []int) []int {
 		}(port)
 	}
 	wg.Wait()
+	sort.Ints(open)
 	return open
 }
 
@@ -158,7 +160,7 @@ func (p *PortScanner) fingerprint(host string, openPorts []int) []PortResult {
 		return fallbackResults(openPorts)
 	}
 
-	results := make([]PortResult, 0, len(nervaResults))
+	enriched := make(map[int]PortResult, len(nervaResults))
 	for _, r := range nervaResults {
 		var findings []SecurityFinding
 		for _, f := range r.SecurityFindings {
@@ -169,13 +171,26 @@ func (p *PortScanner) fingerprint(host string, openPorts []int) []PortResult {
 				Evidence:    f.Evidence,
 			})
 		}
-		results = append(results, PortResult{
+		enriched[r.Port] = PortResult{
 			Port:     r.Port,
 			Proto:    "tcp",
 			Service:  r.Protocol,
 			Version:  r.Version,
 			Findings: findings,
-		})
+		}
+	}
+
+	return mergePortResults(openPorts, enriched)
+}
+
+func mergePortResults(openPorts []int, enriched map[int]PortResult) []PortResult {
+	results := make([]PortResult, len(openPorts))
+	for i, port := range openPorts {
+		if r, ok := enriched[port]; ok {
+			results[i] = r
+			continue
+		}
+		results[i] = PortResult{Port: port, Proto: "tcp"}
 	}
 	return results
 }
diff --git a/scan/portscan_test.go b/scan/portscan_test.go
@@ -0,0 +1,69 @@
+package scan
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestMergePortResultsKeepsUnfingerprintedPorts(t *testing.T) {
+	open := []int{22, 80, 443}
+	enriched := map[int]PortResult{
+		22: {Port: 22, Proto: "tcp", Service: "ssh"},
+		80: {Port: 80, Proto: "tcp", Service: "http", Version: "OpenBSD httpd"},
+	}
+
+	got := mergePortResults(open, enriched)
+
+	if len(got) != len(open) {
+		t.Fatalf("dropped open ports: got %d results, want %d (%v)", len(got), len(open), got)
+	}
+	want := []PortResult{
+		{Port: 22, Proto: "tcp", Service: "ssh"},
+		{Port: 80, Proto: "tcp", Service: "http", Version: "OpenBSD httpd"},
+		{Port: 443, Proto: "tcp"},
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %+v, want %+v", got, want)
+	}
+}
+
+func TestMergePortResultsNoFingerprints(t *testing.T) {
+	got := mergePortResults([]int{8080}, map[int]PortResult{})
+	want := []PortResult{{Port: 8080, Proto: "tcp"}}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("got %+v, want %+v", got, want)
+	}
+}
+
+func TestParsePorts(t *testing.T) {
+	tests := []struct {
+		spec    string
+		want    []int
+		wantErr bool
+	}{
+		{spec: "22,80,443", want: []int{22, 80, 443}},
+		{spec: "80-83", want: []int{80, 81, 82, 83}},
+		{spec: "22, 100-102", want: []int{22, 100, 101, 102}},
+		{spec: "443-443", want: []int{443}},
+		{spec: "100-80", wantErr: true},
+		{spec: "http", wantErr: true},
+		{spec: "1-x", wantErr: true},
+	}
+
+	for _, tt := range tests {
+		got, err := parsePorts(tt.spec)
+		if tt.wantErr {
+			if err == nil {
+				t.Errorf("parsePorts(%q): want error, got %v", tt.spec, got)
+			}
+			continue
+		}
+		if err != nil {
+			t.Errorf("parsePorts(%q): unexpected error %v", tt.spec, err)
+			continue
+		}
+		if !reflect.DeepEqual(got, tt.want) {
+			t.Errorf("parsePorts(%q) = %v, want %v", tt.spec, got, tt.want)
+		}
+	}
+}