Async subdomain enumeration and recon orchestration for authorized security testing.
subenum maps hostnames that belong to a target, then enriches them until you can decide what is worth touching next: live HTTP, TLS names, ASN/CDN, third-party CNAMEs, historical URLs, and optional nuclei findings.
It started as a sequential wordlist brute-forcer. It is now a Python-native enumerator that optionally shells out to the same open-source stack used in modern bug bounty pipelines (ProjectDiscovery, OWASP Amass, tomnomnom, Assetnote).
Use it only on assets you own or have written permission to test.
passive OSINT ──► DNS resolve ──► HTTP / TLS ──► crawl / ports ──► optional nuclei
│ │ │
crt.sh, OTX, wildcard httpx or
subfinder, filter, AXFR, native probe
CT, Wayback SRV, SPF, PTR + Team Cymru
pip install -r requirements.txt
python3 -m subenum example.com --deep --json -o results.jsonThe original subdomenum.py walked a hardcoded list of ~1,000 labels, one DNS query at a time, and printed “valid”. That misses how subdomain discovery actually works in 2026:
| Technique | Why it exists | In subenum |
|---|---|---|
| Certificate Transparency (RFC 6962) | Publicly logged certs leak hostnames operators forget | crtsh, certspotter |
| Passive DNS / OSINT | Historical resolutions survive after a name is unpublished | OTX, HackerTarget, urlscan, Anubis, RapidDNS, ThreatMiner |
| Wayback / Common Crawl CDX | Old URLs encode forgotten environments | native Wayback + optional gau / waybackurls |
| Wildcard-aware brute force | Catch-all DNS (*.example.com) poisons naive brute force |
random-label probes, then IP-set filter (puredns-style) |
| Zone transfer (AXFR) | Some NS still allow RFC 1035 transfers | queried against the zone’s own nameservers |
| NSEC walking | DNSSEC zones without NSEC3 publish the next name in the zone (RFC 4034; NSEC3 is RFC 5155 and is not brute-forced) | --dnssec, authoritative NS only |
| TLS SAN harvest | The certificate the server presents often names siblings | --tls or tlsx |
| HTTP enrichment | Status, title, stack, WAF, favicon hash | native probe or httpx |
| ASN / CDN | Team Cymru IP-to-ASN plus CNAME provider inventory | on by default after resolve |
Root is not required. The old README said to run as root; that was unnecessary.
Python 3.9+ (asyncio, aiodns, httpx, dnspython, rich).
git clone https://github.com/alexbieber/subenum.git
cd subenum
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txtOptional, on your PATH:
python3 -m pip install -e .
subenum example.comThe old entry point still works: python3 subdomenum.py example.com.
Native mode is complete without extra binaries. If you already use the bounty stack, subenum will call whatever is installed:
python3 -m subenum --list-tools
go install github.com/projectdiscovery/pdtm/cmd/pdtm@latest
pdtm -ia# Default: passive OSINT + wordlist brute + DNS intel + AXFR + ASN
python3 -m subenum example.com
# Enrichment: permutations, recursive labels, HTTP, TLS, PTR, DNSSEC
python3 -m subenum example.com --deep
# Use installed subfinder/httpx/katana/gau/… when present
python3 -m subenum example.com --deep --chain
# Deep + chain + port scan + common-path fuzz + nuclei (if installed)
python3 -m subenum example.com --full
# Pipeline-friendly (one hostname per line on stdout)
python3 -m subenum example.com --silent | sort -u
# Never shell out
python3 -m subenum example.com --no-externalexample.com is the IANA documentation domain. Use it to verify the install, not as a stand-in for someone else’s production.
targets
│
├─ passive crt.sh · certspotter · OTX · Wayback · urlscan · Anubis · …
├─ binary subfinder · amass · assetfinder · findomain (if on PATH)
├─ dns NS/MX/TXT/SPF/SRV harvest · AXFR · optional NSEC
├─ brute 1,230-label wordlist, concurrent A/AAAA/CNAME
├─ mutate --permute / --recursive
├─ resolve trusted resolvers, wildcard filter, optional DoH
├─ tls SAN / issuer / expiry
├─ http status, title, tech, CSP/HTML extract, favicon mmh3
├─ intel Team Cymru ASN, CDN, private IPs, CNAME providers
├─ crawl gau · waybackurls · katana · hakrawler (--chain)
├─ ports naabu or nmap on configured web ports (--naabu)
├─ paths ffuf / feroxbuster against a small common-path list (--ffuf)
└─ nuclei community templates on live URLs (--nuclei)
stdout is for results. Logs and the summary panel go to stderr, so this composes:
python3 -m subenum example.com --silent | httpx -silentpython3 -m subenum [domain] [options]
| Flag | Meaning |
|---|---|
domain or -d |
Single target (example.com, URL, or IDN) |
-l FILE |
One domain per line |
| stdin | Used when no domain is given and stdin is not a TTY |
-c FILE |
JSON config overlay (resolvers, concurrency, rate, ports) |
| Flag | Meaning |
|---|---|
-w FILE |
Wordlist (default: packaged common.txt) |
-t N |
Concurrency (default 100) |
--timeout SEC |
Network timeout |
--resolvers |
Comma-separated DNS servers (default 1.1.1.1,8.8.8.8,9.9.9.9) |
--sources / --exclude-sources |
Passive source allow/deny list |
--passive-only / --brute-only |
Skip brute or skip OSINT |
--permute |
Label permutations (dev-api, numeric neighbors) |
--recursive |
Common labels under names already found (staging.api.) |
--rate N |
Max DNS queries per second (0 = unlimited) |
--doh |
Retry unresolved names over Cloudflare DNS-over-HTTPS |
--no-validate-resolvers |
Keep resolvers even if they fail the sanity check |
| Flag | Meaning |
|---|---|
--deep |
permute + recursive + probe + TLS + PTR + DNSSEC |
--chain |
Prefer installed PD/OWASP binaries + crawl |
--full |
--deep + --chain + --naabu + --ffuf + --nuclei |
--probe |
HTTP/S, HTML/CSP host extraction, favicon hash |
--tls |
Certificate SAN harvest |
--ptr |
Reverse DNS on discovered IPs |
--dnssec |
NSEC walk on authoritative NS (skips NSEC3) |
--crawl |
gau / waybackurls / katana / hakrawler |
--naabu |
Port scan via naabu, or nmap if naabu is missing |
--ffuf |
Common-path discovery (small packaged wordlist) |
--arjun |
Parameter discovery |
--nuclei |
Template scan of live URLs |
--ports |
HTTP / scan ports (default 80,443) |
--no-axfr --no-intel --no-resolve --no-apex --no-external |
Turn pieces off |
| Flag | Meaning |
|---|---|
-o FILE |
Write .txt / .json / .jsonl / .csv by suffix or flag |
--json --jsonl --csv |
Structured stdout |
--filter-status 200,403 |
Keep those HTTP codes |
--private-only |
RFC 1918 / loopback / link-local only |
--has-http |
Keep hosts that answered HTTP/S |
-s --silent |
Hostnames only |
-v |
Per-source counts |
--list-sources |
Built-in OSINT list |
--list-tools |
Installed vs missing external binaries |
No API key (on by default)
| Source | Origin |
|---|---|
crtsh |
crt.sh Certificate Transparency |
certspotter |
SSLMate Cert Spotter issuances |
hackertarget |
Host search API |
otx |
AlienVault OTX passive DNS |
wayback |
Internet Archive CDX |
urlscan |
urlscan.io |
anubis |
jldc.me / Anubis index |
threatminer |
ThreatMiner domain API |
rapiddns |
RapidDNS index |
Enabled automatically when the environment variable is set
| Variable | Source |
|---|---|
VIRUSTOTAL_API_KEY or VT_API_KEY |
VirusTotal subdomains |
SECURITYTRAILS_API_KEY |
SecurityTrails |
GITHUB_TOKEN |
GitHub code search (text-match) |
Failed sources are reported and skipped. One timeout does not abort the scan.
subenum orchestrates; it does not vendor fifty Go binaries. shutil.which decides whether to exec. Arguments are passed as a list (shell=False).
| Stage | Binaries |
|---|---|
| Enum | subfinder, amass, assetfinder, findomain |
| Probe | httpx, tlsx |
| Crawl | katana, gau, waybackurls, hakrawler |
| Ports | naabu, nmap |
| Paths | ffuf, feroxbuster |
| Params | arjun |
| Scan | nuclei |
Not wrapped: sqlmap, dalfox, hydra, Metasploit. Those are exploitation tools. If a program allows that testing, pipe hostnames out of subenum yourself.
gitleaks and trufflehog appear in --list-tools because they are part of the usual secret-scanning set; they are not auto-run against a bare domain (they need a repo or files).
On a TTY: a host table plus a summary panel (unique/private IPs, AXFR, ASNs, CDNs, extra SANs, tools used).
When stdout is redirected: one hostname per line.
JSON shape (abridged, from a live run against example.com):
{
"tool": "subenum",
"version": "2.2.0",
"domain": "example.com",
"count": 2,
"stats": {
"hosts": 2,
"unique_ips": 4,
"http_live": 2,
"axfr": "refused",
"asns": { "13335": "CLOUDFLARENET - Cloudflare, Inc., US" },
"cdns": { "Cloudflare": 2 },
"soa": "elliott.ns.cloudflare.com serial=2413856909"
},
"results": [
{
"host": "www.example.com",
"ips": ["104.20.23.154", "172.66.147.243"],
"tags": ["cdn:cloudflare", "waf:cloudflare"],
"asn": { "asn": "13335", "org": "CLOUDFLARENET - Cloudflare, Inc., US" },
"http": { "status": 200, "title": "Example Domain", "tech": ["Cloudflare"] },
"tls": {
"sans": ["example.com"],
"issuer": "SSL Corporation / Cloudflare TLS Issuing ECC CA 3",
"not_after": "2026-10-27",
"expired": false
}
}
]
}Favicon hashing uses signed MurmurHash3 x86-32 over base64.encodebytes, the same construction Shodan documents for favicon search.
python3 -m subenum example.com -c subenum.json{
"resolvers": ["1.1.1.1", "8.8.8.8", "9.9.9.9"],
"concurrency": 80,
"rate": 40,
"timeout": 8,
"ports": "80,443,8080,8443"
}- Resolvers are validated before brute force: a known name must resolve, a
.invalidname must not. Liars are dropped. - Wildcard filtering probes several random labels. Hosts whose A/AAAA set is a subset of the wildcard set are discarded unless they have a distinct CNAME.
- AXFR is attempted in parallel against a few authoritative NS with a short timeout. Most modern zones refuse; an open transfer is still worth knowing about.
- NSEC walking talks to the zone’s nameserver with recursion disabled. Covering proofs from a public recursive resolver are not treated as a zone listing. NSEC3 hashed names are reported, not cracked.
- CNAME providers (GitHub Pages, Heroku, S3, Fastly, …) are inventory tags, not takeover instructions.
- Rate limiting (
--rate) is a token bucket on DNS so you do not burn shared resolvers.
Educational use and authorized testing only (your assets, a written pentest SoW, or an in-scope bug bounty). Unauthorized scanning is typically a legal and terms-of-service violation. You are responsible for staying inside the law and the program rules. The authors are not responsible for misuse.
Apache License 2.0. Derived from alexbieber/subenum.
External tools remain under their own licenses; subenum only execs them when you have installed them.