dumbtv
dumbtv takes the ads, screen tracking and update nags out of your Fire TV over ADB. No root, none of your apps touched, every step can be undone. Free and open source.
Subdomain enumeration you can trust. subenum classifies every DNS lookup, grades the run complete, degraded or unreliable, and ships as a single static Go binary.
SecurityGo · MakefileGitHub @TMHSDigitalOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Fast, concurrent subdomain enumeration via DNS brute-forcing, with per-query accounting, wildcard filtering, real rate limiting, and a TUI. Single static Go binary.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
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.
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.Read the full README ↗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.
Website | 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.
go install github.com/TMHSDigital/subenum@latest
subenum yourdomain.com # a domain you own or are authorized to test
With no -w, subenum uses its built-in list of the 5,000 most common subdomain labels (from SecLists, MIT-licensed), so a first scan finds real results without hunting for a wordlist. See Choosing a wordlist for larger lists.
Or build from a clone, which also gives you the sample wordlists used below:
git clone https://github.com/TMHSDigital/subenum.git
cd subenum
go build -buildvcs=false -o subenum
./subenum -w examples/sample_wordlist.txt example.com
No 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.com
Or launch the interactive terminal UI with no flags:
./subenum -tui
Tools like puredns, shuffledns and dnsx are built for raw mass resolution, and subfinder covers passive sources. subenum is a single static binary focused on telling you how much to trust the result:
-stats writes a versioned JSON report with a complete / degraded / unreliable verdict that CI can gate on, and when more than 20% of queries fail the scan stops and blames the resolver instead of returning a quietly incomplete list.-force, answers matching the wildcard fingerprint are dropped, inconclusive wildcard checks are reported rather than read as "no wildcard", and recursive scans skip wildcard branches.-r resolvers.txt spreads queries over many resolvers and benches failing ones. Every hit is re-validated against your trusted -dns-server before it is reported, and a resolver that hides existing names is caught by canary checks and second opinions, then taken out of rotation.-rate caps DNS packets on the wire, including retries, every record type and wildcard probes, so it can be quoted in rules of engagement.-exclude keeps out-of-scope names from ever being queried.-simulate produces marked, reproducible output with zero network traffic, and -tui gives a form-driven interface for people who don't live in a shell.| 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. With -force, answers that match the wildcard fingerprint are dropped |
| Scan Accounting | Every query is counted as resolved, nxdomain, timeout, refused, or other. After 200 queries a >20% infrastructure failure rate aborts unless -no-abort is set |
| Query Cap | -max-queries stops admitting work. Recursive scans warn about the theoretical ceiling and refuse to start above 1e7 jobs unless -max-queries or -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 Hygiene | Normalize the wordlist in one pass before scanning: skip blank lines and # comments, lowercase, deduplicate, and drop entries that are not valid DNS labels or would exceed 253 characters |
| 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), streaming jsonl, or csv via -format; -show-records adds record values to text output |
| Rate Limiting | Cap DNS queries per second on the wire with -rate: every record type, retry and wildcard probe takes a slot, and queueing never eats into |
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.26+ · Git · Make (optional) · Docker (optional)
go install github.com/TMHSDigital/subenum@latest
Requires Go 1.26 or newer. The binary lands in $(go env GOPATH)/bin and reports the installed module version with -version.
git clone https://github.com/TMHSDigital/subenum.git
cd subenum
go build -buildvcs=false -o subenum
Download the archive for your platform from the Releases page: subenum_<version>_<os>_<arch>.tar.gz (.zip on Windows). Each archive holds the static binary, LICENSE, README.md and CHANGELOG.md.
Platforms: Linux (amd64, arm64, 386) · macOS (amd64, arm64) · Windows (amd64, arm64) · FreeBSD (amd64, arm64)
Every release also has a checksums.txt, a keyless cosign signature over it, an SPDX SBOM per archive, and GitHub build-provenance attestations. To verify a download:
# 1. The archive matches the published checksum.
sha256sum --ignore-missing -c checksums.txt
# 2. checksums.txt was signed by this repository's release workflow.
cosign verify-blob --bundle checksums.txt.sigstore.json \
--certificate-identity-regexp 'https://github.com/TMHSDigital/subenum/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txt
# 3. Or check the build provenance with the GitHub CLI.
gh attestation verify subenum_<version>_linux_amd64.tar.gz --owner TMHSDigital
Releases up to v0.8.0 shipped bare binaries with one .sha256 file each.
Releases from v0.8.0 onward publish a multi-arch image (linux/amd64, linux/arm64) to GHCR:
docker run --rm -v "$(pwd)/data:/data" ghcr.io/tmhsdigital/subenum:latest -w /data/wordlist.txt example.com
Or build it locally:
docker build -t subenum .
docker run --rm -v $(pwd)/data:/data subenum -w /data/wordlist.txt example.com
Or with Compose:
docker compose run --rm subenum # simulated demo, no DNS traffic
docker compose run --rm subenum -w /data/wordlist.txt yourdomain.com # live
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> | bundled top-5000 | Wordlist file, one prefix per line; # comments allowed; - reads stdin. Omit it to use the built-in list (see Choosing a wordlist) |
-dL <file> | n/a | File of apex domains to scan, one per line (- reads stdin), instead of a <domain> argument. Each domain is an independent scan; results share one output |
-t <n> | 100 | Concurrent worker goroutines |
-timeout <ms> | 1000 | DNS timeout in milliseconds, applied to each record-type lookup separately |
-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) |
-ct | false | Add names from Certificate Transparency logs (crt.sh; one HTTPS request per target, no API key) as candidates and -permute seeds |
-force | false | Continue scanning even if wildcard DNS is detected |
-no-abort | false | Keep scanning after the 20% resolver failure-rate abort (warning is still emitted) |
-max-queries <n> | 0 | Max candidate names to test (0 = unlimited). Each name sends one query per record type per attempt ( costs two) |
[!NOTE] Wildcard DNS is detected automatically before scanning begins. If the target resolves wildcard records, or the wildcard probes fail so the check is inconclusive, the tool exits with a warning, since all subdomains would match, making results meaningless. Pass
-forceto override. With-force, results whose records are a subset of the wildcard fingerprint are still dropped and counted aswildcard-filtered. The fingerprint comes from several random probes, and when the wildcard answers from a rotating CDN or load-balancer pool, subenum keeps probing until it has learned the pool; a result that shares only some records with the fingerprint is re-checked against fresh probes. Recursive scans probe each new parent and skip expanding wildcard branches.
[!CAUTION] Simulation mode (
-simulate) generates synthetic results and performs zero network I/O. Do not confuse simulated output with real DNS data. Simulated output marks itself: JSON and JSONL results carry"simulated": true, CSV gets asimulatedcolumn, and-otext files start with a# SIMULATEDcomment line. Bare names piped to stdout stay unmarked so they still pipe cleanly.
[!TIP] For teaching,
-simulate-zone lab.zonegoes further than-simulate: a scenario file defines exact names, records, wildcards, failures and resolver rate limits, and a DNS server on127.0.0.1answers from it. The real resolver, wildcard filtering,-rateand the run-quality report all run unchanged, deterministically and with no traffic leaving the machine. docs/labs.md has five guided exercises with answers, built on the scenarios inexamples/labs/.
subenum -w <wordlist> [flags] <domain>
subenum -w <wordlist> [flags] -dL <domains_file>
Basic scan
./subenum -w wordlist.txt example.com
High-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.com
Resilient scan for flaky networks
./subenum -w wordlist.txt -attempts 3 -timeout 2000 example.com
Pipe-friendly - only resolved subdomains on stdout
./subenum -w wordlist.txt example.com | your-takeover-scanner
Force scan on a wildcard domain
./subenum -w wordlist.txt -force example.com
Simulation - zero network I/O
./subenum -simulate -hit-rate 20 -w examples/sample_wordlist.txt example.com
Press Ctrl+C at any time to abort. In-flight queries drain, partial results are flushed, and the process exits with code 130 (143 for SIGTERM, as sent by docker stop or systemd). Interrupted lookups are not counted as resolver failures.
Resuming an interrupted scan. On Ctrl+C or SIGTERM, subenum saves where it stopped to subenum-resume.json (or the path given with -state): the original command line, a checksum of each input file, the position in the wordlist per target, and the results found so far. Continue with:
subenum -resume subenum-resume.json
The resumed run refuses to start if an input file, the wordlist (including the bundled one, which changes with the binary) or an effective setting (for example a SUBENUM_* variable or the config file) changed. It re-emits the earlier results (so -o and JSON output end up complete), skips targets that had finished, and continues the interrupted one from the first wordlist entry not yet fully looked up; a -permute pass resumes from its own position. At most about one worker pool's worth (-t) of lookups is repeated, and -max-queries covers the whole scan, so a resumed run only gets what is left of it. In recursive mode, names found before the interrupt have their children re-scanned. A resumed run that finishes deletes its state file. Runs that read the wordlist or domain list from stdin cannot be resumed.
Exit codes
| Code | Meaning |
|---|---|
0 | Success |
1 | A scan, the wordlist, the domain list or the output file failed |
2 | Invalid flags or arguments |
3 | -dL: some targets failed while others completed |
4 | -diff: names were added or removed since the previous run |
130 | Interrupted (SIGINT, Ctrl+C); partial results are kept |
143 | Terminated (SIGTERM); partial results are kept |
With -dL, each domain is scanned in turn as an independent scan: -max-queries, -rate and the reliability guard apply per target. A failed target does not stop the others, but a reliability abort (the resolver looks overloaded) skips the remaining targets unless -no-abort is set. A per-target status list is printed at the end.
Where outbound UDP/53 is blocked or intercepted, point -dns-server at an encrypted resolver:
subenum -dns-server tls://1.1.1.1 example.com # DNS over TLS (port 853)
subenum -dns-server https://cloudflare-dns.com/dns-query example.com # DNS over HTTPS
DoT connections are pooled and reused, and DoH uses one keep-alive HTTP client (HTTP/2 where the server offers it), so most queries skip the TLS handshake. Outcome classification and -rate accounting are identical to UDP, and the run-quality report names the transport. -r resolver pools are plain UDP; with -r, an encrypted -dns-server is the trusted resolver that re-validates every hit.
Settings you repeat on every run can live in a config file or the environment. Precedence, highest first: command-line flag > SUBENUM_* environment variable > config file > built-in default.
{ "dns-server": "1.1.1.1:53", "rate": 200, "t": 50, "r": "/home/me/resolvers.txt" }
config.json in your user config directory (~/.config/subenum/ on Linux, ~/Library/Application Support/subenum/ on macOS, %AppData%\subenum\ on Windows), or the path in SUBENUM_CONFIG. Keys are flag names; values are strings, numbers or booleans, and exclude and type also take a list of strings.SUBENUM_<FLAG>, upper-cased with dashes as underscores: SUBENUM_DNS_SERVER, SUBENUM_RATE, SUBENUM_T.-tui) starts its form from these values too, over the values it remembers from its last session.subenum -print-config shows every effective setting and where it came from (flag, env, config or default). It, -h and -version still work when a value is bad, so you can find it; a scan refuses to start and names every bad value.Shell completions and a man page are generated from the same flags, and release archives include them under completions/ and man/:
subenum completion bash > /etc/bash_completion.d/subenum # or zsh, fish, powershell
subenum completion zsh > "${fpath[1]}/_subenum"
subenum man > /usr/local/share/man/man1/subenum.1
Results can only be as good as the wordlist. Without -w, subenum uses its bundled top-5000 list (data/subdomains-5k.txt, from SecLists' subdomains-top1million-5000.txt), a good first pass that takes seconds. For deeper coverage:
| List | Size | Notes |
|---|---|---|
SecLists Discovery/DNS | 5k to 1M+ | subdomains-top1million-20000.txt and -110000.txt are the usual next steps |
Assetnote wordlists (best-dns-wordlist.txt) | ~9M | Built from real DNS data; for long, thorough scans |
| n0kovo subdomains | 50k to 3M | Tiered lists from scraped certificates |
| trickest wordlists | varies | Also maintains a public resolver list for -r |
List size sets the query volume: each entry costs one query per record type (two with the default A,AAAA), per attempt, per recursion level. A 1M-entry list at -rate 500 is about 70 minutes of wire time per target, so pair big lists with -rate (and -r for a resolver pool), and use -max-queries as a budget. tools/wordlist-gen builds a small target-specific list from a domain's own terms.
Certificates name the hosts they cover, so Certificate Transparency logs show real names, including forgotten ones like legacy-billing, that no generic wordlist contains. -ct asks crt.sh once per target (HTTPS, no API key) and adds the names under the target as candidates after the wordlist, and as seeds for -permute. They are only candidates: each is resolved like any other name, so expired certificates cost a query, never a false result.
subenum -ct -permute example.com
The run-quality report counts them per target (ct_names). If crt.sh is down or slow, the scan warns, records ct_error and carries on with the wordlist. The CT request goes to crt.sh, not to the target and not through -rate; lab mode (-simulate-zone) refuses -ct, and a resumed scan reuses the names the interrupted run fetched.
Once api.example.com and dev.example.com exist, the next names most likely to exist are api-dev, dev-api, dev.api and api2. -permute runs a second scan per target over permutations of the names the first pass found: built-in environment and role words (dev, staging, prod, qa, internal, v1, ...) joined with - on either side or as a new level, plus number increments (api2 -> api1, api3, api4). -seeds results.jsonl adds names from an earlier run (any format) and implies -permute.
The pass is a normal scan with the same preflight, wildcard filtering, -rate, -max-queries and -exclude; candidates already in the wordlist, and any whose full name would pass 253 characters, are skipped. It is one level deep even under -recursive, so a permutation hit is reported but not expanded further. Its hits carry "permutation": true in JSON/JSONL, and the run-quality report shows the seeds, candidates and hits per target.
subenum -permute -format jsonl -o results.jsonl example.com
-diff previous.jsonl reports only what changed since an earlier run: + name for new subdomains and - name for ones that disappeared (only when the run's verdict is complete, and only after a fresh lookup of the name returns NXDOMAIN, so names this run did not test and failed lookups are never mistaken for removals). The -o file keeps the full current results for the next comparison, and the exit code is 4 when anything changed. The repository is also a GitHub Action (uses: TMHSDigital/subenum@<release>) that installs a verified release, keeps the baseline as an artifact, writes a job summary and fails on a bad verdict; docs/monitoring.md documents it and a hand-written scheduled workflow that opens an issue when new names appear.
subenum -w wordlist.txt -format jsonl -diff previous.jsonl -o current.jsonl example.com
When a result has a CNAME record, subenum resolves the CNAME target with the trusted resolver and marks the result as a takeover candidate:
| Marker | Meaning |
|---|---|
dangling | The CNAME target does not exist (NXDOMAIN) |
provider:<name> | The target is at a service where dangling records have allowed takeovers (S3, GitHub Pages, Heroku, Azure, Fastly, Shopify, ...) |
dangling:<name> | Both: a takeover-prone service and a target that does not exist |
The marker is a takeover_candidate field in JSON and JSONL, a takeover_candidate column in CSV, TAKEOVER?=<marker> in text with -show-records, and a count in the breakdown and the run-quality report. A dangling CNAME makes A/AAAA lookups fail, so scan with CNAME records included to find them: -type A,AAAA,CNAME.
-sarif takeovers.sarif also writes the candidates as a SARIF 2.1.0 log, one rule per kind of finding and a stable fingerprint per name, so GitHub code scanning can track, triage and dismiss them in the Security tab. Upload it from a scheduled workflow (see Monitoring) with github/codeql-action/upload-sarif and security-events: write.
[!IMPORTANT] Takeover markers are DNS-only hints. subenum makes no HTTP requests; a
provider:match is often a perfectly healthy service. Verify every candidate by hand, and only against targets you are authorized to test.
Every lookup is accounted for, and subenum says how far the results can be trusted. -stats run.json writes a report in any format, and -format jsonl ends with the same object as its last line ("type": "summary"):
{
"type": "summary",
"schema": 1,
"tool": "subenum",
"version": "vX.Y.Z",
"started": "2026-10-04T21:00:00Z",
"duration_ms": 41230,
"simulated": false,
"resolver": "1.1.1.1:53",
"rate_limit": 200,
"queries_sent": 8214,
"achieved_qps": 199.2,
"verdict": "degraded",
"reason": "61 of 4096 lookups failed (timeout 58, refused 3, other 0)",
"targets": [
{
"domain": "example.com",
"status": "ok",
"processed": 4096, "total": 4096, "found": 37,
"outcomes": { "resolved": 37, "nxdomain": 3998, "timeout": 58, "refused": 3, "other": 0, "wildcard_filtered": 0, "excluded": 0 },
"queries_sent": 8214,
"skipped_by_cap": 0,
"wildcard": false,
"verdict": "degraded",
"reason": "61 of 4096 lookups failed (timeout 58, refused 3, other 0)"
}
]
}
| Verdict | Meaning |
|---|---|
complete | Under 1% of lookups failed and nothing was skipped |
degraded | Usable, but some names are unknown: 1-20% of lookups failed, -max-queries skipped candidates, or the run was interrupted |
unreliable | Over 20% of lookups failed (the reliability guard's threshold), the guard aborted the scan, or the target could not be scanned |
The overall verdict is the worst target verdict. queries_sent counts DNS messages actually dialed, including retries; it is 0 in simulation mode. SERVFAIL and other errors Go's resolver does not distinguish are counted under other. With -r, each target also reports pool_hits, confirmed and unconfirmed (hits the trusted resolver denied, so they were dropped), and the top level gains resolvers: per-resolver lookups, found, nxdomain, failed and benched counts; resolver is then the trusted resolver. excluded counts candidates -exclude kept out of scope; they are never queried. status is one of ok, failed, interrupted, skipped or not_run. The schema number changes only when a field is renamed, removed or changes meaning; new fields can appear at any time.
Gate a CI job on the verdict with jq:
subenum -w wordlist.txt -stats run.json example.com > found.txt
jq -e '.verdict == "complete"' run.json > /dev/null || { jq -r .reason run.json; exit 1; }
./subenum -tui
No 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.
The engine is importable, so a recon pipeline or custom tool can use it without shelling out:
go get github.com/TMHSDigital/subenum/pkg/subenum
results, stats, err := subenum.Scan(ctx, subenum.Config{
Domain: "example.com", // URLs, ports and IDNs are normalized
Rate: 100, // queries per second on the wire
Types: []string{"A", "AAAA", "CNAME"},
})
Every zero value in Config takes the same default as the CLI, so Config{Domain: "example.com"} is a complete configuration and uses the bundled wordlist. Scan returns a finished scan; Run returns a channel that streams results, notices and progress and always ends with the final Stats. Set Simulate for a run that sends no DNS queries. Wildcard filtering, rate limiting, retries, takeover hints, the reliability guard and the per-outcome accounting are all the same code the CLI runs, and the DNS internals stay unexported.
API reference and runnable examples: pkg.go.dev/github.com/TMHSDigital/subenum/pkg/subenum. The package follows semantic versioning from v1.0.0; until then it may change between minor releases, while the CLI stays stable.
| Key | Action |
|---|---|
tab / shift+tab / ↑ ↓ | Navigate fields |
space | Toggle Simulate / Force |
ctrl+r | Start scan |
ctrl+c | Abort a running scan; quit the form or a finished scan |
r | New scan - restores last-used values |
q | Quit after scan completes |
| Layer | Components |
|---|---|
| Core Engine | Go 1.26 · net.Resolver · context · sync/atomic |
| Concurrency | goroutines · channels · sync.WaitGroup · sync.Mutex |
| TUI | Bubble Tea · Bubbles (textinput, viewport, progress) · Lip Gloss |
| Infrastructure | Docker · distroless static nonroot · Make · docker-compose |
| CI/CD | GitHub Actions · CodeQL · Dependabot · golangci-lint v2 |
| Quality | go test -race · gosec · govet · staticcheck |
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/ # Source of the website (GitHub Pages)
│ ├── assets/ # Brand, CSS, JS, TUI screenshot
│ ├── ARCHITECTURE.md
│ ├── CONTRIBUTING.md
│ ├── DEVELOPER_GUIDE.md
│ ├── start.md, cli.md, library.md, labs.md, monitoring.md, docker.md
│ ├── _data/nav.yml # Site navigation
│ ├── _config.yml
│ └── index.html # Landing page
├── examples/
│ ├── sample_wordlist.txt
│ ├── advanced_usage.md
│ └── demo.sh
├── internal/
│ ├── dns/ # ResolveTypes, ResolveDomainWithRetry, CheckWildcard, SimulateResolve
│ ├── 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.
GPL-3.0 License · Security Policy · TM Hospitality Strategies
-timeout| 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 |
| Go library | pkg/subenum embeds the same engine in other programs: Scan for a finished run, Run to stream events |
CNAME-o <file> | n/a | Write results to file in addition to stdout |
-format <fmt> | text | Output format: text, json, jsonl (one object per line, streamed), or csv |
-show-records | false | In text format, append each result's records as TYPE=value |
-rate <qps> | 0 | Max DNS queries per second on the wire, all workers combined, including every record type, retry, wildcard probe and the preflight (0 = unlimited) |
-type <list> | A,AAAA | Comma-separated record types to look up: A, AAAA, CNAME |
-recursive | false | Recursively enumerate subdomains of discovered subdomains |
-permute | false | After each scan, scan permutations of discovered names (api → api-dev, dev-api, dev.api, api2, ...) |
-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. Off by default when stderr is not a terminal; passing -progress there prints whole progress lines instead of redrawing one |
-silent | false | Pipeline mode: bare result names on stdout, only errors (and a one-line simulation warning) on stderr. See Pipelines |
-simulate | false | Simulation mode: no real DNS queries |
-hit-rate <n> | 15 | Simulated resolution rate, percent (1-100), applied uniformly to every name |
-seed <n> | 0 | Simulation seed; the same seed reproduces the same results (line order can vary unless -t 1). 0 picks a random seed and prints it |
-simulate-zone <file> | n/a | Lab mode: answer every query from a scenario file via a local DNS server on 127.0.0.1; no traffic leaves the machine. See Labs |
-diff <file> | n/a | Compare with a previous results file (any format): print only names added (+) or removed (-), exit 4 when anything changed. See Monitoring |
-r <file> | n/a | Resolver pool: one ip or ip:port per line. Lookups rotate over the healthy resolvers (a resolver failing over half its recent lookups is benched for 30s), retries go to a different resolver, and every hit is re-validated against -dns-server, whose answer is the one reported. NXDOMAIN answers are checked too: each resolver is periodically asked about a name -dns-server confirmed (a canary), and an NXDOMAIN from one that has not passed a canary is confirmed by a second resolver. A resolver contradicted three times is flagged and dropped for the run. Preflight and wildcard checks use -dns-server |
-exclude <list> | n/a | Comma-separated out-of-scope names: exact (vpn.example.com) or *.parent (every name below parent, not parent itself). Excluded names are never queried, probed or expanded, and are counted as excluded |
-exclude-file <file> | n/a | Same patterns, one per line (# comments allowed), e.g. a bug-bounty program's out-of-scope list |
-stats <file> | n/a | Write a JSON run-quality report (outcomes, queries sent, verdict); see Run-quality report |
-sarif <file> | n/a | Write takeover candidates as SARIF 2.1.0 for GitHub code scanning; needs CNAME in -type |
-tui | false | Launch the interactive Terminal UI |
-print-config | false | Print every setting's effective value and source (flag, env, config or default), then exit |
-version | n/a | Print version and exit |
-retries <n> | n/a | Deprecated - alias for -attempts, prints a warning |
-resume <file> | n/a | Resume an interrupted run from its state file; takes no other arguments |
-seeds <file> | n/a | Results file (any -format) whose names also seed -permute; implies -permute |
-state <file> | subenum-resume.json | Where an interrupted run saves its state for -resume |