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 a6dc625d640858ed5a9b7f20e4d4e95751bd0268
parent 305cd01261127b5454f5f82b75e3a7c4b604d771
Author: djnn <[email protected]>
Date: Thu, 12 Mar 2026 12:51:41 +0900

fix(scan): replace dead cvemap/vulnx API with NVD API v2

Diffstat:
MCHANGELOG.txt | 2++
MREADME.md | 14++++++++++----
Mgo.mod | 4+---
Mgo.sum | 8++------
Minventory/quick-run.sh | 2+-
Mscan/cvemap.go | 179++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------

6 files changed, 161 insertions(+), 48 deletions(-)

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,7 @@
 ## v0.0.5
 
+- fix(scan): replace dead cvemap/vulnx API with NVD API v2 ~ @djnnvx
+- chore(deps): drop github.com/projectdiscovery/cvemap dependency ~ @djnnvx
 - fix(osint): only IPs should be passed to shodan ~ @djnnvx
 - feat(osint): adding support for leaker ~ @djnnvx
 - feat(scan): adding port-scanning ~ @djnnvx
diff --git a/README.md b/README.md
@@ -156,11 +156,17 @@ some domains that are not reachable anymore. To filter them out, you can run:
 
 #### Retrieving CVEs automatically:
 
-We use vulnx( https://github.com/projectdiscovery/cvemap) under the hood to query CVEs based on
-initial fingerprinting. To get this feature working, you will need to create an account
-on ProjectDiscovery (https://cloud.projectdiscovery.io/) and retrieve an API key.
+CVE lookups use the [NVD API v2](https://nvd.nist.gov/developers/vulnerabilities) (NIST National
+Vulnerability Database) — no account required. Results are queried by detected technology name and
+saved to `cves.json` in your scan path.
 
-Then you should set this key in your .bashrc (or equivalent) to VULNX_API_KEY.
+Without an API key, NVD allows 5 requests per 30 seconds (yellow sleeps 7s between queries to stay
+safe). For faster scans, grab a free key at https://nvd.nist.gov/developers/request-an-api-key
+and set it:
+
+```bash
+export NVD_API_KEY=your-key-here
+```
 
 #### Credential Leak Checking (Leaker)
 
diff --git a/go.mod b/go.mod
@@ -6,7 +6,6 @@ require (
 	github.com/OJ/gobuster/v3 v3.8.2
 	github.com/bogdzn/gork v0.0.5
 	github.com/praetorian-inc/nerva v1.0.0
-	github.com/projectdiscovery/cvemap v1.0.0
 	github.com/projectdiscovery/dnsx v1.2.3
 	github.com/projectdiscovery/goflags v0.1.74
 	github.com/projectdiscovery/httpx v1.9.0
@@ -38,6 +37,7 @@ require (
 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/bits-and-blooms/bitset v1.24.4 // indirect
+	github.com/bits-and-blooms/bloom/v3 v3.6.0 // indirect
 	github.com/bodgit/plumbing v1.3.0 // indirect
 	github.com/bodgit/sevenzip v1.6.1 // indirect
 	github.com/bodgit/windows v1.0.1 // indirect
@@ -64,7 +64,6 @@ require (
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect
 	github.com/fatih/color v1.18.0 // indirect
 	github.com/gaissmai/bart v0.26.0 // indirect
 	github.com/go-faker/faker/v4 v4.7.0 // indirect
@@ -94,7 +93,6 @@ require (
 	github.com/iangcarroll/cookiemonster v1.6.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 // indirect
-	github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kataras/jwt v0.1.17 // indirect
 	github.com/kennygrant/sanitize v1.2.4 // indirect
diff --git a/go.sum b/go.sum
@@ -54,6 +54,7 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
 github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
+github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
 github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
 github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
 github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
@@ -133,8 +134,6 @@ github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj6
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
-github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
 github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -227,8 +226,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 h1:36qep4gxKs+JgeHGWeQ040RyZdt9kQlLglL1rFVn/oQ=
 github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
-github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
-github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
@@ -347,8 +344,6 @@ github.com/projectdiscovery/chaos-client v0.5.2 h1:dN+7GXEypsJAbCD//dBcUxzAEAEH1
 github.com/projectdiscovery/chaos-client v0.5.2/go.mod h1:KnoJ/NJPhll42uaqlDga6oafFfNw5l2XI2ajRijtDuU=
 github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
 github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
-github.com/projectdiscovery/cvemap v1.0.0 h1:0PVbaWozUNM4oislukPdeKrmeQKbdGwhWGpRNBQ5tpg=
-github.com/projectdiscovery/cvemap v1.0.0/go.mod h1:QQ/tnIoUewT+l9h5yaJyEgwmKEPjjY+0de/RJOGyDis=
 github.com/projectdiscovery/dnsx v1.2.3 h1:S87U9kYuuqqvMFyen8mZQy1FMuR5EGCsXHqfHPQAeuc=
 github.com/projectdiscovery/dnsx v1.2.3/go.mod h1:NjAEyJt6+meNqZqnYHL4ZPxXfysuva+et56Eq/e1cVE=
 github.com/projectdiscovery/dsl v0.8.13 h1:HjjHta7c02saH2tUGs8CN5vDeE2MyWvCV32koT8ZCWs=
@@ -489,6 +484,7 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
 github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
 github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=
 github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
 github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
 github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
diff --git a/inventory/quick-run.sh b/inventory/quick-run.sh
@@ -11,4 +11,4 @@ set -euo
 
 ./yellow prune -f ${1}/scans/domains.txt -o ${1}/scans/active-web-panels.txt
 
-./yellow scan -d ${1}/scans/infra --file ${1}/scans/active-web-panels.txt
+./yellow scan --port-scan -d ${1}/scans/infra --file ${1}/scans/active-web-panels.txt
diff --git a/scan/cvemap.go b/scan/cvemap.go
@@ -3,13 +3,23 @@ package scan
 import (
 	"encoding/json"
 	"fmt"
+	"io"
+	"net/http"
+	"net/url"
 	"os"
 	"strings"
+	"time"
+)
 
-	"github.com/projectdiscovery/cvemap/pkg/runner"
-	"github.com/projectdiscovery/cvemap/pkg/types"
+// NVD rate limits: 5 req/30s without key, 50 req/30s with key.
+// We sleep conservatively between queries to avoid 403s.
+const (
+	nvdDelayNoKey   = 7 * time.Second
+	nvdDelayWithKey = 1 * time.Second
 )
 
+const nvdBaseURL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
+
 type Cvemap struct {
 	HTTPProxy string
 	scanPath  string
@@ -19,6 +29,57 @@ type Cvemap struct {
 	Debug     bool
 }
 
+type nvdResponse struct {
+	TotalResults    int       `json:"totalResults"`
+	Vulnerabilities []nvdVuln `json:"vulnerabilities"`
+}
+
+type nvdVuln struct {
+	CVE nvdCVE `json:"cve"`
+}
+
+type nvdCVE struct {
+	ID           string     `json:"id"`
+	Published    string     `json:"published"`
+	LastModified string     `json:"lastModified"`
+	Descriptions []nvdDesc  `json:"descriptions"`
+	Metrics      nvdMetrics `json:"metrics"`
+	Weaknesses   []nvdWeak  `json:"weaknesses"`
+	References   []nvdRef   `json:"references"`
+}
+
+type nvdDesc struct {
+	Lang  string `json:"lang"`
+	Value string `json:"value"`
+}
+
+type nvdMetrics struct {
+	CVSSv31 []nvdCVSS `json:"cvssMetricV31,omitempty"`
+	CVSSv30 []nvdCVSS `json:"cvssMetricV30,omitempty"`
+	CVSSv2  []nvdCVSS `json:"cvssMetricV2,omitempty"`
+}
+
+type nvdCVSS struct {
+	Source   string      `json:"source"`
+	Type     string      `json:"type"`
+	CVSSData nvdCVSSData `json:"cvssData"`
+}
+
+type nvdCVSSData struct {
+	Version      string  `json:"version"`
+	BaseScore    float64 `json:"baseScore"`
+	BaseSeverity string  `json:"baseSeverity"`
+}
+
+type nvdWeak struct {
+	Description []nvdDesc `json:"description"`
+}
+
+type nvdRef struct {
+	URL    string `json:"url"`
+	Source string `json:"source"`
+}
+
 func (c *Cvemap) Info(target string) {
 	fmt.Println("[+] Running cvemap for", target)
 }
@@ -50,64 +111,114 @@ func (c *Cvemap) Configure(cfg any) {
 }
 
 func (c *Cvemap) Run(techs []string) {
-	runner.PDCPApiKey = os.Getenv("VULNX_API_KEY")
-	if runner.PDCPApiKey == "" {
-		println("[!] Automatic CVE research requires VULNX_API_KEY to be set in env...skipping")
+	if len(techs) == 0 {
+		fmt.Println("[+] no technologies provided")
 		return
 	}
 
-	options := runner.Options{
-		HTTPProxy: c.HTTPProxy,
-		Limit:     c.Limit,
-		Offset:    c.Offset,
-		Verbose:   c.Verbose,
-		Debug:     c.Debug,
+	client := &http.Client{}
+	if c.HTTPProxy != "" {
+		proxyURL, err := url.Parse(c.HTTPProxy)
+		if err != nil {
+			fmt.Printf("[ERROR] invalid proxy URL: %v\n", err)
+			return
+		}
+		client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
 	}
 
-	rn, err := runner.New(&options)
-	if err != nil {
-		panic(err)
+	apiKey := os.Getenv("NVD_API_KEY")
+	delay := nvdDelayNoKey
+	if apiKey != "" {
+		delay = nvdDelayWithKey
 	}
 
-	if len(techs) == 0 {
-		fmt.Println("[+] no technologies provided")
-		return
+	limit := c.Limit
+	if limit <= 0 {
+		limit = 50
 	}
 
+	var allCVEs []nvdCVE
+
+	queried := 0
 	for _, tech := range techs {
 		tech = strings.TrimSpace(tech)
 		if tech == "" {
 			continue
 		}
 
-		c.Info(tech)
+		// wappalyzer formats versioned results as "TechName:version" — skip unversioned ones
+		// since NVD results without a version are too noisy to be actionable
+		colonIdx := strings.Index(tech, ":")
+		if colonIdx == -1 {
+			fmt.Printf("[+] skipping %s (no version detected)\n", tech)
+			continue
+		}
+		name := tech[:colonIdx]
+		version := tech[colonIdx+1:]
+		query := name + " " + version
+
+		if queried > 0 {
+			time.Sleep(delay)
+		}
+		queried++
 
-		rn.Options.Search = tech
+		c.Info(query)
 
-		cvesResp, err := rn.GetCves()
+		cves, total, err := c.queryCVEs(client, apiKey, query, limit, c.Offset)
 		if err != nil {
 			fmt.Printf("[ERROR] query for '%s' failed: %v\n", tech, err)
 			continue
 		}
-		if cvesResp == nil {
-			fmt.Printf("[SCAN %s] no results\n\n", tech)
-			continue
-		}
 
-		total := 0
-		if cvesResp.TotalResults > 0 {
-			total = cvesResp.TotalResults
-		} else if cvesResp.Cves != nil {
-			total = len(cvesResp.Cves)
-		}
+		fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", query, total, len(cves))
+		allCVEs = append(allCVEs, cves...)
+	}
+
+	c.saveResults(allCVEs)
+}
+
+func (c *Cvemap) queryCVEs(client *http.Client, apiKey, keyword string, limit, offset int) ([]nvdCVE, int, error) {
+	params := url.Values{}
+	params.Set("keywordSearch", keyword)
+	params.Set("resultsPerPage", fmt.Sprintf("%d", limit))
+	params.Set("startIndex", fmt.Sprintf("%d", offset))
 
-		c.saveResults(cvesResp.Cves)
-		fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", tech, total, len(cvesResp.Cves))
+	reqURL := fmt.Sprintf("%s?%s", nvdBaseURL, params.Encode())
+
+	req, err := http.NewRequest("GET", reqURL, nil)
+	if err != nil {
+		return nil, 0, err
 	}
+	if apiKey != "" {
+		req.Header.Set("apiKey", apiKey)
+	}
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, 0, err
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		body, _ := io.ReadAll(resp.Body)
+		return nil, 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
+	}
+
+	var nvdResp nvdResponse
+	if err := json.NewDecoder(resp.Body).Decode(&nvdResp); err != nil {
+		return nil, 0, err
+	}
+
+	cves := make([]nvdCVE, len(nvdResp.Vulnerabilities))
+	for i, v := range nvdResp.Vulnerabilities {
+		cves[i] = v.CVE
+	}
+
+	return cves, nvdResp.TotalResults, nil
 }
 
-func (c *Cvemap) saveResults(names []types.CVEData) {
-	data, err := json.MarshalIndent(names, "", "\t")
+func (c *Cvemap) saveResults(cves []nvdCVE) {
+	data, err := json.MarshalIndent(cves, "", "\t")
 	if err != nil {
 		return
 	}