Fast concurrent subdomain enumeration, written in Go. Point it at a domain and a wordlist; it brute-forces DNS across a worker pool and prints the subdomains that resolve. Built for pentesters, bug-bounty hunters, and operators doing authorized reconnaissance of their own infrastructure.
Quick Start | Configuration | Usage | Architecture | Changelog
Important
Authorized use only. Only scan domains you own or have explicit written permission to test. Unauthorized scanning may violate applicable laws. Users are solely responsible for compliance with all applicable laws and regulations.
git clone https://github.com/TMHSDigital/subenum.git
cd subenum
go build -buildvcs=false -o subenum
./subenum -w examples/sample_wordlist.txt example.comNo network required to try it out. Simulation mode generates synthetic results with zero DNS queries:
./subenum -simulate -hit-rate 20 -w examples/sample_wordlist.txt example.comOr launch the interactive terminal UI with no flags:
./subenum -tui| Module | Description |
|---|---|
| Worker Pool | Spawn N goroutines for parallel DNS resolution with a configurable concurrency ceiling |
| DNS Engine | Resolve subdomains against any DNS server with per-query timeouts and retry backoff |
| Wildcard Detection | Double-probe check before scanning; aborts early unless -force is set |
| Graceful Shutdown | Trap SIGINT/SIGTERM, drain in-flight workers, flush partial results |
| Input Validation | RFC-compliant domain syntax and strict ip:port format enforcement |
| Wordlist Dedup | Deduplicate wordlist entries in a single pass before scanning begins |
| Simulation Mode | Generate synthetic DNS results at a configurable hit rate, with zero network I/O |
| Output Pipeline | Resolved domains to stdout (pipe-clean); progress and diagnostics to stderr |
| Output Formats | Emit results as text, json (array of subdomain plus typed records), or csv via -format |
| Rate Limiting | Cap total DNS queries per second across the worker pool with -rate (context-aware) |
| Record Types | Look up and filter by A, AAAA, or CNAME records with -type |
| Recursive Enumeration | Enumerate subdomains of discovered subdomains with -recursive and a -depth cap, with loop and duplicate protection |
| Interactive TUI | Form-based config and live-scrolling results via -tui; session values persisted |
flowchart LR
subgraph Input
A[Wordlist File] -->|"dedup + load"| B(Entry Slice)
C[CLI Flags / TUI Form] --> D(Argument Parser)
end
subgraph PreScan
D --> W{Wildcard\nDetection}
W -->|"no wildcard / -force"| E
end
subgraph Engine
B --> E{Worker Pool\nN Goroutines}
E -->|subdomain.domain| F[DNS Resolver]
F -->|retry + backoff| F
G[Context] -->|cancel| E
G -->|timeout| F
end
subgraph OutputLayer ["Output"]
F -->|resolved| H["stdout (results)"]
F -->|resolved| I[Output File]
E -->|atomic counters| J["stderr (progress)"]
end
K[SIGINT / SIGTERM] -->|cancel| G
Prerequisites: Go 1.24.2+ · Git · Make (optional) · Docker (optional)
Build from source
git clone https://github.com/TMHSDigital/subenum.git
cd subenum
go build -buildvcs=false -o subenumPre-built binaries
Download the appropriate binary for your platform from the Releases page.
Platforms: Linux (amd64, arm64) · macOS (amd64, arm64) · Windows (amd64)
SHA-256 checksums are provided alongside each binary.
Docker
docker build -t subenum .
docker run --rm -v $(pwd)/data:/data subenum -w /data/wordlist.txt example.comOr with Compose:
docker compose run subenumMake targets
make build # compile binary
make test # run test suite with race detector
make lint # run golangci-lint
make simulate # safe run - no DNS queries
make tui # launch interactive TUI
make docker-build # build Docker image
make help # list all targets| Flag | Default | Description |
|---|---|---|
-w <file> |
n/a | Wordlist file, one prefix per line (required) |
-t <n> |
100 |
Concurrent worker goroutines |
-timeout <ms> |
1000 |
Per-query DNS timeout in milliseconds |
-dns-server <ip:port> |
8.8.8.8:53 |
DNS server address (validated on startup) |
-attempts <n> |
1 |
DNS resolution attempts per subdomain (1 = no retry) |
-force |
false |
Continue scanning even if wildcard DNS is detected |
-o <file> |
n/a | Write results to file in addition to stdout |
-format <fmt> |
text |
Output format: text, json, or csv |
-rate <qps> |
0 |
Max DNS queries per second across all workers (0 = unlimited) |
-type <list> |
A,AAAA |
Comma-separated record types to look up: A, AAAA, CNAME |
-recursive |
false |
Recursively enumerate subdomains of discovered subdomains |
-depth <n> |
1 |
Max recursion depth when -recursive is set (1 = no recursion) |
-v |
false |
Verbose output: IPs, timings, per-query detail (stderr) |
-progress |
true |
Live progress line on stderr |
-simulate |
false |
Simulation mode: no real DNS queries |
-hit-rate <n> |
15 |
Simulated resolution rate, percent (1-100) |
-tui |
false |
Launch the interactive Terminal UI |
-version |
n/a | Print version and exit |
-retries <n> |
n/a | Deprecated - alias for -attempts, prints a warning |
Note
Wildcard DNS is detected automatically before scanning begins. If the target resolves wildcard records, the tool exits with a warning, since all subdomains would match, making results meaningless. Pass -force to override.
Caution
Simulation mode (-simulate) generates synthetic results and performs zero network I/O. Do not confuse simulated output with real DNS data.
subenum -w <wordlist> [flags] <domain>Examples
Basic scan
./subenum -w wordlist.txt example.comHigh-throughput with Cloudflare DNS, saving results
./subenum -w wordlist.txt -t 300 -timeout 500 -dns-server 1.1.1.1:53 -o results.txt example.comResilient scan for flaky networks
./subenum -w wordlist.txt -attempts 3 -timeout 2000 example.comPipe-friendly - only resolved subdomains on stdout
./subenum -w wordlist.txt example.com | cut -d' ' -f2 | your-takeover-scannerForce scan on a wildcard domain
./subenum -w wordlist.txt -force example.comSimulation - zero network I/O
./subenum -simulate -hit-rate 20 -w examples/sample_wordlist.txt example.comPress Ctrl+C at any time to abort. In-flight queries drain and partial results are flushed before exit.
./subenum -tuiNo flags required. Fill in the form and press ctrl+r to start scanning. Last-used values are saved to ~/.config/subenum/last.json and restored on next launch. The interface is shown at the top of this README.
TUI keyboard reference
| Key | Action |
|---|---|
tab / shift+tab / ↑ ↓ |
Navigate fields |
space |
Toggle Simulate / Force |
ctrl+r |
Start scan |
ctrl+c |
Abort scan (scan view) / quit (form) |
r |
New scan - restores last-used values |
q |
Quit after scan completes |
| Layer | Components |
|---|---|
| Core Engine | Go 1.24.2 · net.Resolver · context · sync/atomic |
| Concurrency | goroutines · channels · sync.WaitGroup · sync.Mutex |
| TUI | Bubble Tea · Bubbles (textinput, viewport, progress) · Lip Gloss |
| Infrastructure | Docker · Alpine · Make · docker-compose |
| CI/CD | GitHub Actions · CodeQL · Dependabot · golangci-lint v2 |
| Quality | go test -race · gosec · govet · staticcheck |
Project structure
subenum/
├── .github/
│ ├── workflows/
│ │ ├── go.yml # CI: build, test, lint, release
│ │ ├── codeql.yml # Weekly CodeQL security analysis
│ │ └── pages.yml # GitHub Pages deployment
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ └── PULL_REQUEST_TEMPLATE.md
├── data/
│ └── wordlist.txt # Default wordlist for Docker/Make
├── docs/
│ ├── assets/
│ │ └── tui-form.png # TUI screenshot
│ ├── ARCHITECTURE.md
│ ├── CONTRIBUTING.md
│ ├── DEVELOPER_GUIDE.md
│ ├── docker.md
│ ├── _config.yml
│ └── index.md
├── examples/
│ ├── sample_wordlist.txt
│ ├── advanced_usage.md
│ ├── demo.sh
│ └── multi_domain_scan.sh
├── internal/
│ ├── dns/ # ResolveDomain, CheckWildcard, SimulateResolution
│ ├── output/ # Thread-safe Writer (stdout/stderr separation)
│ ├── scan/ # Scan engine: Config, Event types, Run()
│ ├── tui/ # Bubble Tea UI: form, scan view, session config
│ └── wordlist/ # LoadWordlist (dedup + sanitize)
├── tools/
│ └── wordlist-gen.go
├── main.go # CLI entry point
├── main_test.go
├── go.mod
├── Dockerfile
├── docker-compose.yml
├── Makefile
├── .golangci.yml # golangci-lint v2 configuration
├── CHANGELOG.md
├── SECURITY.md
└── LICENSE # GNU General Public License v3.0
See CONTRIBUTING.md for the pull request workflow and ethical guidelines. See DEVELOPER_GUIDE.md for build setup, testing, and project structure.