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 f46e9b622a6570beec8a43f74fca76b112560dc9
parent 3a9a96542ccc7531f47487bc92206e4c9b5295fe
Author: djnn <[email protected]>
Date: Wed, 22 Oct 2025 22:10:01 +0200

unstable -> current not ready for a new release yet but usable at least

Diffstat:
MChangelog.md | 5+++++
MREADME.md | 38++++++++++++++++++++++++++++----------
Mgo.mod | 7+++++--
Mgo.sum | 17++++++++++++-----
Mmain.go | 2+-
Mquick-run.sh | 2+-
Ascan/cvemap.go | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mscan/run.go | 53++++++++++++++++++++++++++++++++++++++++++++++-------
Mscan/wappalyzergo.go | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------

9 files changed, 328 insertions(+), 40 deletions(-)

diff --git a/Changelog.md b/Changelog.md
@@ -1,3 +1,8 @@
+## v0.0.4
+
+- feat(scan): saving wappalyzer results to file ~ @djnnvx
+- feat(scan): adding cvemap module ~ @djnnvx
+
 ## v0.0.3
 
 - feat: bumping gobuster to latest version (unstable) ~ @djnnvx
diff --git a/README.md b/README.md
@@ -9,7 +9,7 @@
       --------  ⠀⠀⢸⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣙⢿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀
         ⠀⠀⠀⠀  ⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠻⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀
         djnn.sh⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀
-        ⠀v0.0.3  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
+        ⠀v0.0.4  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠋⡬⢿⣿⣷⣤⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡇⢸⡇⢸⣿⣿⣿⠟⠁⢀⣬⢽⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣧⣈⣛⣿⣿⣿⡇⠀⠀⣾⠁⢀⢻⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀
@@ -44,11 +44,16 @@ Please use at your own risk, in a controlled environnement. Thanks<3
 
 For next version, i want to take care of at least two items described here:
 
-* `scan`: integrate browser-dependant tools (katana, ...),
-* `scan`: integrate a runner for nuclei that will automatically fetch new rules,
-* `scan`: port third-party tools (smbenum, ldapsearch, enum4linux),
-* `scan`: add a CVE query system
-* `scan`: port some quick win, such as anonymous ftp
+* `scan`: add toolbox scan script to automatically integrate it with
+          nmap and other CLI tools
+* `scan`: integrate browser-dependant tools (katana, ...) (still TBD),
+
+* `osint`: add support for more dorks
+* `osint`: shodan API integration
+
+### Bugfixes
+
+* `sitemap` should be stored to a file & fetch robots.txt
 
 > feel free to suggest more ideas. :)~
 >
@@ -57,8 +62,8 @@ For next version, i want to take care of at least two items described here:
 ### Contributing
 
 This software's code is public, but not open to contributions.
-The reason for that is that if something is integrated, I want to make sure I am able to maintain it
-afterwards.
+The reason for that is that if something is integrated, I want to make sure I
+am able to maintain it afterwards.
 
 ## Installing
 
@@ -128,6 +133,19 @@ Run scans against the target actively.
 ```bash
 ./yellow scan --help
 
-# or, if in a hurry
-./yellow scan -d djnn.sh/scans --file djnn.sh/scans/domains.txt
+# run scan on ports 80, 443, 8080 & 8443
+nmap -T4 -Pn -p 80,443,8080,8443 --open -oA domains -iL djnn.sh/scans/domains.txt
+cat *.gnmap | grep -i "open/tcp" | cut -d " " -f2 | sort -u > djnn.sh/scans/web-targets.txt
+
+# you can also just run the domains.txt file directly
+
+./yellow scan -d djnn.sh/scans/infra --file djnn.sh/scans/web-targets.txt
 ```
+
+#### Retrieving CVEs automatically
+
+We use [vulnx](https://github.com/projectdiscovery/cvemap/tree/main) 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.
+
+Then you should set this key in your `.bashrc` or equivalent to `VULNX_API_KEY`.
diff --git a/go.mod b/go.mod
@@ -5,6 +5,7 @@ go 1.25
 require (
 	github.com/OJ/gobuster/v3 v3.8.2
 	github.com/bogdzn/gork v0.0.5
+	github.com/projectdiscovery/cvemap v1.0.0
 	github.com/projectdiscovery/dnsx v1.2.2
 	github.com/projectdiscovery/goflags v0.1.74
 	github.com/projectdiscovery/httpx v1.7.1
@@ -15,7 +16,7 @@ require (
 )
 
 require (
-	aead.dev/minisign v0.2.0 // indirect
+	aead.dev/minisign v0.2.1 // indirect
 	github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
 	github.com/Masterminds/semver/v3 v3.2.1 // indirect
 	github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect
@@ -39,7 +40,7 @@ require (
 	github.com/charmbracelet/glamour v0.8.0 // indirect
 	github.com/charmbracelet/lipgloss v0.13.0 // indirect
 	github.com/charmbracelet/x/ansi v0.3.2 // indirect
-	github.com/cheggaaa/pb/v3 v3.1.4 // indirect
+	github.com/cheggaaa/pb/v3 v3.1.5 // indirect
 	github.com/cloudflare/cfssl v1.6.4 // indirect
 	github.com/cloudflare/circl v1.6.1 // indirect
 	github.com/cnf/structhash v0.0.0-20250313080605-df4c6cc74a9a // indirect
@@ -50,6 +51,7 @@ require (
 	github.com/dlclark/regexp2 v1.11.5 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
+	github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect
 	github.com/fatih/color v1.18.0 // indirect
 	github.com/gaissmai/bart v0.25.0 // indirect
 	github.com/go-faker/faker/v4 v4.6.1 // indirect
@@ -76,6 +78,7 @@ require (
 	github.com/hdm/jarm-go v0.0.7 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
+	github.com/jedib0t/go-pretty/v6 v6.6.7 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/kataras/jwt v0.1.10 // indirect
 	github.com/kennygrant/sanitize v1.2.4 // indirect
diff --git a/go.sum b/go.sum
@@ -1,5 +1,6 @@
-aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
 aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
+aead.dev/minisign v0.2.1 h1:Z+7HA9dsY/eGycYj6kpWHpcJpHtjAwGiJFvbiuO9o+M=
+aead.dev/minisign v0.2.1/go.mod h1:oCOjeA8VQNEbuSCFaaUXKekOusa/mll6WtMoO5JY4M4=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -68,8 +69,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
 github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
-github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=
-github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
+github.com/bits-and-blooms/bloom/v3 v3.6.0 h1:dTU0OVLJSoOhz9m68FTXMFfA39nR8U/nTCs1zb26mOI=
+github.com/bits-and-blooms/bloom/v3 v3.6.0/go.mod h1:VKlUSvp0lFIYqxJjzdnSsZEw4iHb1kOL2tfHTgyJBHg=
 github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
 github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
 github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
@@ -88,8 +89,8 @@ github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyf
 github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
 github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
 github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
-github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=
-github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=
+github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
+github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -121,6 +122,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
 github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
+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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
@@ -230,6 +233,8 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg=
 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
 github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=
+github.com/jedib0t/go-pretty/v6 v6.6.7/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -330,6 +335,8 @@ 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.2 h1:ZjUov0GOyrS8ERlKAAhk+AOkqzaYHBzCP0qZfO+6Ihg=
 github.com/projectdiscovery/dnsx v1.2.2/go.mod h1:3iYm86OEqo0WxeGDkVl5WZNmG0qYE5TYNx8fBg6wX1I=
 github.com/projectdiscovery/dsl v0.5.0 h1:3HHY14FNmdwWXq3pi9dd8JjUHQzskZjLD/pZKVx5Vi4=
diff --git a/main.go b/main.go
@@ -13,7 +13,7 @@ const ASCII_ART = `
       --------  ⠀⠀⢸⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣙⢿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀
         ⠀⠀⠀⠀  ⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠻⣿⣿⣿⣿⣿⣿⣿⣄⠀⠀⠀⠀
         djnn.sh⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀
-        ⠀v0.0.3  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
+        ⠀v0.0.4  ⠀⠀⢠⣿⣿⣿⣿⡟⠹⠿⠟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡿⠋⡬⢿⣿⣷⣤⣤⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⡇⢸⡇⢸⣿⣿⣿⠟⠁⢀⣬⢽⣿⣿⣿⣿⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀
         ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣧⣈⣛⣿⣿⣿⡇⠀⠀⣾⠁⢀⢻⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀
diff --git a/quick-run.sh b/quick-run.sh
@@ -9,4 +9,4 @@ set -euo
 
 ./yellow osint -d ${1}
 
-./yellow scan -d ${1}/scans --file ${1}/scans/domains.txt
+./yellow scan -d ${1}/scans/infra --file ${1}/scans/domains.txt
diff --git a/scan/cvemap.go b/scan/cvemap.go
@@ -0,0 +1,119 @@
+package scan
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"strings"
+
+	"github.com/projectdiscovery/cvemap/pkg/runner"
+	"github.com/projectdiscovery/cvemap/pkg/types"
+)
+
+type Cvemap struct {
+	HTTPProxy string
+	scanPath  string
+	Limit     int
+	Offset    int
+	Verbose   bool
+	Debug     bool
+}
+
+func (c *Cvemap) Info(target string) {
+	fmt.Println("[+] Running cvemap for", target)
+}
+
+func (c *Cvemap) Configure(cfg any) {
+	if cfg == nil {
+		return
+	}
+	m := cfg.(map[string]any)
+
+	if v, ok := m["Proxy"].(string); ok {
+		c.HTTPProxy = v
+	}
+	if v, ok := m["Limit"].(int); ok {
+		c.Limit = v
+	}
+	if v, ok := m["Offset"].(int); ok {
+		c.Offset = v
+	}
+	if v, ok := m["Verbose"].(bool); ok {
+		c.Verbose = v
+	}
+	if v, ok := m["Debug"].(bool); ok {
+		c.Debug = v
+	}
+	if v, ok := m["ScanPath"].(string); ok {
+		c.scanPath = v
+	}
+}
+
+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")
+		return
+	}
+
+	options := runner.Options{
+		HTTPProxy: c.HTTPProxy,
+		Limit:     c.Limit,
+		Offset:    c.Offset,
+		Verbose:   c.Verbose,
+		Debug:     c.Debug,
+	}
+
+	rn, err := runner.New(&options)
+	if err != nil {
+		panic(err)
+	}
+
+	if len(techs) == 0 {
+		fmt.Println("[+] no technologies provided")
+		return
+	}
+
+	for _, tech := range techs {
+		tech = strings.TrimSpace(tech)
+		if tech == "" {
+			continue
+		}
+
+		c.Info(tech)
+
+		rn.Options.Search = tech
+
+		cvesResp, err := rn.GetCves()
+		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)
+		}
+
+		c.saveResults(cvesResp.Cves)
+		fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", tech, total, len(cvesResp.Cves))
+	}
+}
+
+func (c *Cvemap) saveResults(names []types.CVEData) {
+	data, err := json.MarshalIndent(names, "", "\t")
+	if err != nil {
+		return
+	}
+
+	filename := c.scanPath + "/cves.json"
+	if err := os.WriteFile(filename, data, 0644); err != nil {
+		panic(err)
+	}
+}
diff --git a/scan/run.go b/scan/run.go
@@ -1,8 +1,10 @@
 package scan
 
 import (
-	"evil.djnn.sh/djnn/yellow/helpers"
 	"fmt"
+	"os"
+
+	"evil.djnn.sh/djnn/yellow/helpers"
 )
 
 type ScanOpts struct {
@@ -15,6 +17,15 @@ type ScanOpts struct {
 	forceInsecure bool
 }
 
+/*
+	save in shared memory to avoid having to read a write from files
+
+all the time
+*/
+var (
+	wappalyzerResults []string
+)
+
 func (opts *ScanOpts) SetForceInsecure(data bool) {
 	opts.forceInsecure = data
 }
@@ -58,16 +69,33 @@ func (opts ScanOpts) runRobots() {
 }
 
 func (opts ScanOpts) runWappalyzerGo() {
+	wappalyzerResults = make([]string, 0)
 
 	wp := WappalyzerGo{}
 	wpCfg := make(map[string]any)
 
 	wpCfg["Proxy"] = opts.proxy
+	wpCfg["ScanPath"] = opts.scanPath
 
 	wp.Configure(wpCfg)
 	wp.Info(opts.domain)
 	if !opts.dryRun {
-		wp.Run(opts.domain)
+		wappalyzerResults = wp.Run(opts.domain)
+	}
+}
+
+func (opts ScanOpts) runCvemap() {
+	cv := Cvemap{}
+	cvCfg := make(map[string]any)
+
+	cvCfg["Proxy"] = opts.proxy
+	cvCfg["ScanPath"] = opts.scanPath
+	cv.Configure(cvCfg)
+	cv.Info(opts.domain)
+
+	if !opts.dryRun {
+		cv.Run(wappalyzerResults)
+		wappalyzerResults = make([]string, 0)
 	}
 }
 
@@ -89,7 +117,7 @@ func (opts *ScanOpts) runHttpx() {
 	httpx := Httpx{}
 	httpxCfg := make(map[string]any)
 
-	httpxOutdir := fmt.Sprintf("%s/infra/httpx", opts.scanPath)
+	httpxOutdir := fmt.Sprintf("%s/httpx", opts.scanPath)
 
 	httpxCfg["OutDirPath"] = httpxOutdir
 	httpxCfg["Proxy"] = opts.proxy
@@ -125,22 +153,33 @@ func (opts ScanOpts) runGobusterDir() {
 func (opts *ScanOpts) Run() {
 	fmt.Printf("\n[SCAN] domain: %s\n", opts.domain)
 
+	fullDomain := "https://" + opts.domain
 	if opts.forceInsecure {
-		opts.SetDomain("http://" + opts.domain)
-	} else {
-		opts.SetDomain("https://" + opts.domain)
+		fullDomain = "http://" + opts.domain
 	}
 
-	if !helper.HasUnavailableWebInterface(opts.domain) {
+	if !helper.HasUnavailableWebInterface(fullDomain) {
 
+		initialPath := opts.scanPath
 		fmt.Printf("[SCAN %s] web panel online....running web scans\n", opts.domain)
 
+		pathForDomain := opts.scanPath + "/" + opts.domain
+		opts.SetScanPath(pathForDomain)
+		err := os.MkdirAll(opts.scanPath, 0755)
+		if err != nil {
+			panic(err)
+		}
+
+		opts.SetDomain(fullDomain)
+
 		opts.runSitemap()
 		opts.runRobots()
 		opts.runWappalyzerGo()
+		opts.runCvemap()
 		opts.runHttpx()
 		opts.runGobusterDir()
 
+		opts.SetScanPath(initialPath)
 	} else {
 		fmt.Printf("[SCAN %s] => no web panel online. skipping\n", opts.domain)
 	}
diff --git a/scan/wappalyzergo.go b/scan/wappalyzergo.go
@@ -2,16 +2,21 @@ package scan
 
 import (
 	"crypto/tls"
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
+	"os"
+	"reflect"
+	"sort"
 
-	"evil.djnn.sh/djnn/yellow/helpers"
+	helper "evil.djnn.sh/djnn/yellow/helpers"
 	wappalyzer "github.com/projectdiscovery/wappalyzergo"
 )
 
 type WappalyzerGo struct {
-	Proxy string
+	Proxy    string
+	ScanPath string
 }
 
 func (d *WappalyzerGo) Info(url string) {
@@ -20,35 +25,127 @@ func (d *WappalyzerGo) Info(url string) {
 
 func (d *WappalyzerGo) Configure(c any) {
 	d.Proxy = c.(map[string]any)["Proxy"].(string)
+	d.ScanPath = c.(map[string]any)["ScanPath"].(string)
 }
 
-func (d *WappalyzerGo) Run(url string) {
+func (d *WappalyzerGo) Run(targetURL string) []string {
+	var names = make([]string, 0)
 
+	client := d.newHTTPClient()
+
+	resp, body, err := d.fetchResponse(client, targetURL)
+	if err != nil {
+		fmt.Printf("error fetching %s: %v\n", targetURL, err)
+		fmt.Printf("[SCAN %s] WappalyzerGo scan for %s completed\n\n", targetURL, targetURL)
+		return names
+	}
+	if resp != nil {
+		defer resp.Body.Close()
+	}
+
+	results, err := d.fingerprint(resp, body)
+	if err != nil {
+		fmt.Printf("error fingerprinting %s: %v\n", targetURL, err)
+		fmt.Printf("[SCAN %s] WappalyzerGo scan for %s completed\n\n", targetURL, targetURL)
+		return names
+	}
+
+	names = techNamesFromResults(results)
+	filename, err := d.saveNames(names, targetURL)
+	if err != nil {
+		fmt.Printf("error saving results for %s: %v\n", targetURL, err)
+	} else {
+		fmt.Printf("[SCAN %s] WappalyzerGo results are stored in %s\n", targetURL, filename)
+	}
+
+	fmt.Printf("%v\n", names)
+	fmt.Printf("[SCAN %s] WappalyzerGo scan for %s completed\n\n", targetURL, targetURL)
+	return names
+}
+
+func (d *WappalyzerGo) newHTTPClient() *http.Client {
 	transport := helper.GetHttpTransport()
 	transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
 
-	client := &http.Client{
+	return &http.Client{
 		Transport: transport,
 	}
+}
 
-	req, err := http.NewRequest("GET", url, nil)
+func (d *WappalyzerGo) fetchResponse(client *http.Client, targetURL string) (*http.Response, []byte, error) {
+	req, err := http.NewRequest("GET", targetURL, nil)
+	if err != nil {
+		return nil, nil, fmt.Errorf("creating request: %w", err)
+	}
 	req.Header.Add("User-Agent", helper.GetUserAgent())
 
 	resp, err := client.Do(req)
 	if err != nil {
-		fmt.Printf("%v", err)
+		return resp, nil, fmt.Errorf("performing request: %w", err)
 	}
 
-	if resp != nil {
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			fmt.Printf("%v", err)
+	if resp == nil {
+		return nil, nil, fmt.Errorf("no response received")
+	}
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return resp, nil, fmt.Errorf("reading response body: %w", err)
+	}
+
+	return resp, body, nil
+}
+
+func (d *WappalyzerGo) fingerprint(resp *http.Response, body []byte) (any, error) {
+	wappalyzerClient, err := wappalyzer.New()
+	if err != nil {
+		return nil, fmt.Errorf("initializing wappalyzer client: %w", err)
+	}
+
+	results := wappalyzerClient.Fingerprint(resp.Header, body)
+	return results, nil
+}
+
+func (d *WappalyzerGo) saveNames(names []string, targetURL string) (string, error) {
+	data, err := json.MarshalIndent(names, "", "\t")
+	if err != nil {
+		return "", fmt.Errorf("marshal names: %w", err)
+	}
+
+	filename := d.ScanPath + "/wappalyzer.json"
+	if err := os.WriteFile(filename, data, 0644); err != nil {
+		return "", fmt.Errorf("write file %s: %w", filename, err)
+	}
+
+	return filename, nil
+}
+
+func techNamesFromResults(results any) []string {
+	if s, ok := results.([]string); ok {
+		out := make([]string, len(s))
+		copy(out, s)
+		sort.Strings(out)
+		return out
+	}
+
+	if si, ok := results.([]interface{}); ok {
+		out := make([]string, 0, len(si))
+		for _, v := range si {
+			out = append(out, fmt.Sprint(v))
 		}
+		sort.Strings(out)
+		return out
+	}
 
-		wappalyzerClient, err := wappalyzer.New()
-		fingerprints := wappalyzerClient.Fingerprint(resp.Header, body)
-		fmt.Printf("%v\n", fingerprints)
+	val := reflect.ValueOf(results)
+	if val.Kind() == reflect.Map {
+		out := make([]string, 0, val.Len())
+		for _, k := range val.MapKeys() {
+			out = append(out, fmt.Sprint(k.Interface()))
+		}
+		sort.Strings(out)
+		return out
 	}
 
-	fmt.Printf("[SCAN %s] WappalyzerGo scan for %s completed\n\n", url, url)
+	return []string{fmt.Sprint(results)}
 }