High-concurrency subdomain enum, passive OSINT, HTTP path discovery, and Java / hidden endpoint extract in one Rust binary.
Sanskrit vega = impetus / velocity. Also the star.
Subdomain and content-discovery tools split into two camps: slow-but-clean passive OSINT and fast-but-junk-prone DNS brute. vegadns does both in one binary, and it filters the noise (wildcard DNS, soft-404s) that buries massdns and gobuster users in false positives. It is built and benchmarked against massdns, puredns, dnsx, subfinder, altdns, and ZDNS on planted-answer lab suites (see below).
git clone [email protected]:WattoCyber/vegadns.git
cd vegadns
cargo build --release
./target/release/vegadns --help
Requires Rust stable. Full CLI reference in the README below or --help.
The shipped product is vegadns from src/ (cargo build --release). There is
no Python on the scan path. scripts/*.py are optional peer-bench drivers
(massdns / ffuf / subfinder H2H on the same mock) and are excluded from
language statistics - see scripts/README.md.
| Lane | Command | What it does |
|---|---|---|
| Offline | wordlist / expand / permute | Depth packs, FQDN expand, altdns-class mutate (no network) |
| Lab DNS | mock-serve | Fixture zone over UDP for peer H2H |
| Passive OSINT | passive | Public CT / datasets / APIs → in-scope names (no DNS brute) |
| DNS | enum | Wordlist expand → concurrent UDP resolve → wildcard filter → emit |
| Live HTTP | probe | Host list → concurrent GET → live URLs (httpx-class) |
| Archives | harvest | Wayback CDX → in-scope hosts + subdirectory prefixes |
| Surface | map | Passive → recurse/permute → resolve → harvest → probe |
| HTTP paths | paths | Concurrent path scan + soft-404 fingerprint filter |
| Endpoints | endpoints | Java / source / hidden seed + HTML/JS/robots/sitemap extract |
Research pass covered massdns, puredns/shuffledns, dnsx, subfinder, alterx/gotator/altdns, and ZDNS. See docs/RESEARCH.md.
We plant a fixed set of real answers (oracle). Every tool gets the same wordlist and the same mock server.
| Column | Plain English |
|---|---|
| Time | Seconds until the tool finishes (lower is faster) |
| Real found | How many planted answers it recovered (higher is better; max = oracle size) |
| Reported | How many names/URLs it printed as hits |
| Junk | Reported − Real found (noise you still have to triage) |
| Clean hit rate | Real found / Reported. 100% means every printed hit was real |
Faster is not always better. A tool can finish first and still bury you in junk. We care about all real answers, almost no junk, then speed.
These are private lab / gym suites plus one public OSINT domain (hackerone.com). Not “fastest on the public internet.”
Full raw tables: docs/BENCHMARKS.md.
This-revision vegadns-only clocks: docs/feature_timing_cloud.json (python scripts/feature_timing.py).
Setup: 500 real subdomains planted. Zone also answers random junk labels (wildcard). Wordlist: 8000 labels. Host: Kali.
| tool | Time | Real found (of 500) | Reported | Junk | Clean hit rate |
|---|---|---|---|---|---|
| vegadns | 0.18s | 500 | 500 | 0 | 100% |
| massdns | 0.43s | 500 | 721 | 221 | 69% |
| gobuster-dns | 161s | 0 | 0 | 0 | - |
Takeaway: vegadns and massdns both found every real name. massdns also printed 221 wildcard lies. vegadns filtered those and finished faster on this suite.
Setup: 800 real names. Mock DNS adds 10 ms delay, 5% SERVFAIL, 2% drop. Wordlist: 2000. Host: Kali.
| tool | Time | Real found (of 800) | Reported | Junk | Clean hit rate |
|---|---|---|---|---|---|
| vegadns | 0.14s | 800 | 800 | 0 | 100% |
| massdns | 0.55s | 800 | 1700 | ~900 | 47% |
Takeaway: vegadns wins wall and clean output on this suite. massdns still dumps ~half junk.
Setup: Windows gym-stress, 3000 candidates, same 800 oracle. No peer race. We only compare vegadns to itself.
| build | Time | Real found | Clean hit rate | Names checked / sec |
|---|---|---|---|---|
| before | 0.59s | 800 / 800 | 100% | 5,047 |
| after (best) | 0.40s | 800 / 800 | 100% | 7,583 |
Takeaway: ~33% faster, ~50% more names per second, still zero junk. Detail: docs/OPTIMIZATION_BREAKTHROUGHS.md. Later ceiling work (UDP buffers, poll instead of spin, no silent concurrency clamps) is in docs/OPTIMIZATION_CEILING.md.
Setup: 24 real paths planted (/admin, /api, …). Soft-404: missing paths still return HTTP 200 with a fixed “not found” body. Status-only tools treat those as hits. Wordlist mixes real paths + bait. Same process-wall clock for every tool.
| tool | Time | Real found (of 24) | Reported | Junk | Clean hit rate |
|---|---|---|---|---|---|
| vegadns paths | 0.032s | 24 | 24 | 0 | 100% |
| feroxbuster | 1.03s | 24 | 61 | 37 | 39% |
What this means
Takeaway: vegadns wins clean output and wall on this fixed Kali suite (body drain + keep-alive reuse; process-wall H2H).
Real adjacent binaries on PATH (massdns, dnsx, puredns, shuffledns, gobuster, ffuf, ferox). Single measured run. Full tables: docs/PEER_BENCH_CLOUD_2026-08-20.md.
DNS gym-stress (800 planted, 2000 labels, 10 ms / 5% SERVFAIL / 2% drop):
| tool | Time | vs vegadns | Real found (of 800) | Junk | Clean hit rate | F1 |
|---|---|---|---|---|---|---|
| vegadns | 0.164s | 1.0× | 800 | 0 | 100% | 1.000 |
| massdns | 0.515s | 3.1× | 800 | 900 | 47% | 0.640 |
| puredns | 1.211s | 7.4× | 800 | 900 | 47% | 0.640 |
| shuffledns | 1.435s | 8.7× | 800 | 900 | 47% | 0.640 |
| dnsx | 6.673s | 41× | 795 | 895 | 47% | 0.639 |
DNS lab (500 planted, 5000 labels, wildcard zone): vegadns 0.029s / 500/500 / 0 junk vs massdns 0.337s (11.8×, 400 junk) vs dnsx 0.502s (17.5×, 400 junk).
HTTP hard (24 planted, soft-404 200s; process-wall H2H vs peers):
| tool | Time | Real found (of 24) | Reported | Junk | Clean hit rate | F1 |
|---|---|---|---|---|---|---|
| ffuf | 0.132s | 24 | 60 | 36 | 40% | 0.571 |
| vegadns paths | 0.239s | 24 | 24 | 0 | 100% | 1.000 |
| gobuster-dir | 0.461s | 23 | 23 | 0 | 100% | 0.979 |
| feroxbuster | 0.777s | 24 | 61 | 37 | 39% | 0.565 |
ffuf wins raw wall and loses the quality race. vegadns is the only timed tool at R=1.0 and P=1.0. In-process hard mock on this revision: 0.025s / 24/24 / 36 soft-404s dropped (no peer process start).
vegadns does subdomain enum in two lanes: vegadns enum (active wordlist brute) and vegadns passive (OSINT / CT / public datasets, no DNS brute). Full write-up: docs/SUBDOMAIN_ENUM_COMPARE.md.
Active lab (500 planted, 2000 labels): vegadns 0.023s / 500/500 / 0 junk vs findomain-brute 26s (1120×, 1500 junk) vs gobuster-dns 41s / 0 found.
Passive on the same private zone: vegadns / subfinder / amass / assetfinder / findomain all found 0 of 500 (no CT/API data for lab.test).
Passive on hackerone.com (union = 31 in-scope descendants, no brute): vegadns 31 names in 0.339s (100% of union, 0 junk, F1 1.000, 91 names/s) vs assetfinder 10 real / 3 junk / 1.11s vs subfinder 24 / 20.2s vs findomain 13 / 36s vs amass 0.
This revision, vegadns-only (same domain, no peers): complete 31 names / 0.493s (wall = last source); --fast 30 names / 0.055s.
Same local fixture (HTML → runtime.js → chunk.js → hidden paths + robots/sitemap). No Chrome. Raw: docs/e2e_http_compare.json. Write-up: docs/E2E_CLOUD_2026-08-20.md.
| tool | Time | Must-have (5) | URLs |
|---|---|---|---|
| vegadns endpoints --no-seed | 0.009s | 5/5 | 10 |
| vegadns endpoints (java seed pack) | 2.01s | 5/5 | 11 |
| naive urllib | 0.023s | 1/5 | 3 |
katana -jc -kf all -d 3 -c 32 | 6.39s | 1/5 | 5 |
Seeded wall on Python ThreadingHTTPServer is pinned by ~100 404s. Same seed pack on the in-process mock, this revision: 0.053s / 104 req / 9 hits. --no-seed mock: 0.003s / 9 req / 9 hits.
Probe (4-line list, 3 live + 1 dead):
| tool | Time | Live |
|---|---|---|
| vegadns probe | 0.005s | 3 |
| httpx | 0.423s | 3 |
| Target | Tool | Time | In-scope names | Notes |
|---|---|---|---|---|
| hackerone.com | vegadns | 0.34s | 31 | 100% of same-run peer union; 0 junk |
| hackerone.com | subfinder | 20.2s (0.58s best earlier) | 24 | crt.sh stall on the slow run |
| tesla.com | vegadns | 0.32s | 1326 | Chaos + sub.md + Anubis + THC |
| tesla.com | subfinder | 8.38s | 1327 | 1292 overlap; 34 only-vega; 35 only-subfinder |
| tesla.com | vegadns --fast | 0.40s | 1301 | Wall = sub.md that run |
vegadns is faster on the domains we timed, with a near-equal tesla count and a different remainder. It does not emit a strict superset of subfinder’s keyed-source farm. Raw: docs/tesla_passive_cloud.json.
Linux cloud host, target/release/vegadns, 2026-08-20. Process wall unless noted. Quality floors held where an oracle exists.
| Feature | Time | Result |
|---|---|---|
--help / wordlist list / expand file | 0.001-0.002s | process-start floor |
wordlist emit tiny / medium | 0.002s / 0.005s | 273 / 5038 labels |
permute --max 5000 | 0.005s | 5000 lines |
enum lab mock (25k / 500) | 0.091s (engine 0.067s) | 500/500, 0 junk, ~425k qps |
enum gym-stress 2k (10 ms / 5% / 2%) | 0.085s (engine 0.082s) | 800/800, 0 junk |
paths hard mock | 0.025s (engine 0.023s) | 24/24, 36 soft-404s dropped |
passive hackerone.com | 0.493s | 31 names (wall = last source) |
passive --fast hackerone.com | 0.055s | 30 names |
probe local HTTP | 0.003s | 1 live |
endpoints --mock-crawl | 0.053s | 104 req, 9 hits, 0 err |
endpoints --mock-crawl --no-seed | 0.003s | 9 req, 9 hits |
map mock (no passive/harvest/probe) | 0.058s | 10 names, 1 round |
harvest hackerone.com Wayback | 8.008s | failed (archive.org CDX error / timeout) |
expand via bash -lc stdin is ~0.20s - that is shell+pipe cost. Native expand on a file is 1-2 ms.
| Gate | Result |
|---|---|
cargo test --release --offline | 141 passed, 0 failed (lib 81 + cli 3 + lab 2 + logic 7 + mutation 24 + paths 5 + extra 17 + scrutinize 2) |
python scripts/gherkin_run.py | 4/4 scenarios |
python scripts/paths_suite.py | PASS, 8/8 hits, two-run path set stable |
Binary: target/release/vegadns (.exe on Windows).
Requires a recent Rust toolchain. No massdns dependency for the binary itself.
Output model: stdout = results only (pipe-safe). stderr = ferox-class human UI (ASCII banner, scan-config panel, [INF]/[OK ]/[WRN] tags, live [####>---] progress on TTY, boxed complete stats). Color when TTY and NO_COLOR unset. Use -q / --quiet to silence the UI; --quiet-names keeps stderr stats but does not reprint names.
# Wordlist packs (no network)
vegadns wordlist list
vegadns wordlist emit tiny | wc -l # 273
vegadns wordlist emit medium -o medium.txt # 5038
# Labels → FQDNs (no network)
vegadns expand -d lab.test -w fixtures/wordlist_small.txt
printf 'www\nmail\n' | vegadns expand -d lab.test -w -
# altdns-class permute (no network)
printf 'api\ndev\nwww\n' > seeds.txt
vegadns permute -i seeds.txt -w alter -d example.com --max 20 -o muts.txt
# Active enum against an embedded zone (wildcard filtered)
vegadns enum \
--mock-zone fixtures/zone_bench.json \
--wordlist fixtures/wordlist_small.txt \
--output hits.txt \
--known-true fixtures/known_true.txt
# → 10/10 names, recall=1.000 precision=1.000
# Lab volume (500 planted, 25k labels, wildcard parents)
vegadns enum \
--mock-zone fixtures/lab/zone_lab.json \
-w fixtures/lab/wordlist_lab.txt \
--known-true fixtures/lab/known_true_lab.txt \
-o lab.txt --stats-json lab.json -q --quiet-names
# HTTP paths, soft-404 200s
vegadns paths --mock-hard-zone fixtures/paths/hard_zone.txt \
-w fixtures/paths/wordlist_hard.txt --known-true fixtures/paths/known_true_hard.txt \
--status 200,401,403 --soft404-probes 10 -q -o path_hits.txt
# → 24/24 real, 36 soft-404s dropped
# Java / hidden / JS extract (HTML → chunk.js → /hidden/admin)
vegadns endpoints --mock-crawl -o endpoints.txt --stats-json end.json
vegadns endpoints --mock-crawl --no-seed --jsonl -o endpoints.jsonl
# Surface map on a fixture (skip OSINT / Wayback / probe)
vegadns map -d bench.test --mock-zone fixtures/zone_bench.json \
--no-passive --no-harvest --no-probe --rounds 1 -D fast -o ./map -q
# → ./map/subs.txt (10 names)
# Shared UDP mock for a peer tool on the same answers
vegadns mock-serve --zone fixtures/lab/zone_lab.json --bind 127.0.0.1:5353
SCAN CONFIG
------------------------------------------------------
mode | mock DNS
target | lab.test
labels | 25000
concurrency | 2000
------------------------------------------------------
[INF] resolving | 25000 labels
[INF] wrote 500 names → lab.txt
[OK ] quality floor recall=1.000 precision=1.000 known=500 found=500
Stdout is the 500 names only. Pipe that into probe / paths / another tool.
vegadns enum \
-d example.com \
-w wordlist.txt \
-r resolvers.txt \
-o found.txt \
--concurrency 4000 \
--timeout-ms 1500
| Depth | Flag | Pack | Labels | Auto-permute |
|---|---|---|---|---|
| fast | -D fast / -D 1 | tiny | 273 | off |
| normal | -D normal / -D 2 | small | 621 | off |
| deep | -D deep / -D 3 | medium | 5038 | off |
| deeper | -D deeper / -D 4 | large | 20012 | off |
| final | -D final / -D 5 | final | 65478 | on (top 300 seeds × alter, cap 250k) |
vegadns wordlist list
vegadns enum -d example.com -D fast -r resolvers.txt -o found.txt
vegadns enum -d example.com -D deep -r resolvers.txt -o found.txt
vegadns enum -d example.com -D final -r resolvers.txt -o found.txt
vegadns enum -d example.com -D final --no-permute -r resolvers.txt
vegadns passive -d hackerone.com -o names.txt --stats-json stats.json
vegadns passive -d hackerone.com --fast -o names.txt # skip slower bonus source
vegadns passive -d hackerone.com -d tesla.com --fast # parallel apexes
vegadns enum -d example.com -w words.txt -r resolvers.txt --aaaa # IPv6-only names
Queries public sources concurrently (crt.name, Chaos, sub.md, Anubis,
THC unless --fast). Emits in-scope descendants only (drops apex echo
and foohackerone.com lookalikes). Then enum / map still resolve
and wildcard-filter. The index is a seed, not a replacement for DNS.
vegadns probe -l hosts.txt -o live.txt --stats-json probe.json
printf 'example.com\n' | vegadns probe -l - -o live.txt --schemes https
Toy enum is wordlist × apex. A real program is iterative: names you already
have become parents (api.dev.target.com) and alterx seeds, and subdirs come
from archives, not only /admin.
# One loop: passive → recurse/permute → resolve → Wayback harvest → HTTP probe
vegadns map -d in-scope.example -r resolvers.txt -o ./map --depth fast --rounds 2
# Archives only (no DNS brute)
vegadns harvest -d in-scope.example -o urls.txt --hosts-out hosts.txt --paths-out paths.txt
# Brute the harvested subdirectory prefixes on a live host
vegadns paths -u https://in-scope.example/ -w ./map/paths.txt -o path_hits.txt
# Java / source / hidden endpoints (authorized origin only; no browser)
vegadns endpoints -u https://in-scope.example/ -o endpoints.txt --jsonl
vegadns endpoints -l live.txt --scope-subs --jsonl -o endpoints.jsonl
--no-enum skips resolve (OSINT + harvest + candidate expansion only).
--no-harvest / --no-probe / --fast cut wall when you do not need archives.
./map/ contains subs.txt, candidates.txt, urls.txt, paths.txt, live.txt, stats.json.
Wayback CDX can be slow or empty. This host’s harvest of hackerone.com hit an archive.org timeout (8s, 0 URLs). Use --no-harvest if you only want DNS.
vegadns paths --mock-paths fixtures/paths/hit_paths.txt \
-w fixtures/paths/wordlist.txt --known-true fixtures/paths/known_true.txt \
-o hits.txt --status 200
# hard soft-404 mock
vegadns paths --mock-hard-zone fixtures/paths/hard_zone.txt \
-w fixtures/paths/wordlist_hard.txt --known-true fixtures/paths/known_true_hard.txt \
--status 200,401,403 --soft404-probes 10 -q
# live (authorized base URL only)
vegadns paths -u http://127.0.0.1:18080/ -w paths.txt -o hits.txt
vegadns passive → vegadns enum [--aaaa] → vegadns probe → vegadns paths
↘ vegadns endpoints
Or one authorized program loop: vegadns map then paths / endpoints on live origins.
The product suite is Rust. This revision: 141 tests.
Not part of the binary. Python here only drives target/release/vegadns (and
peer tools on PATH) for gym / H2H. Details: scripts/README.md.
# Gym (mock-stress / mock-clean / live-resolve)
python scripts/gen_gym_fixtures.py
python scripts/gym_bench.py --mode mock-stress --out ./gym_out --wordlist-cap 5000
# Same mock + wordlist vs massdns / dnsx / ffuf / …
bash scripts/install_bench_peers.sh
python scripts/coverage_surpass.py --out ./coverage_out --wordlist-cap 8000
Docs: docs/SUBDOMAIN_SCANNER_GYM.md, docs/HTB_UNBIASED_BENCH.md, docs/DISCOVERY_COVERAGE.md.
MIT. See LICENSE.
Wordlist packs include SecLists / altdns snapshots under their upstream licenses. See wordlists/README.md.
Use only on systems you own or are authorized to test. The gym and lab suites are private mocks by default. Live enum and path modes are for authorized targets.