Subdominator: passive subdomain enumeration across 72 OSINT sources
SubDominator helps you discover subdomains associated with a target domain efficiently and with minimal impact for your Bug Bounty
At a glance
- What is it?
- Subdominator is a Python tool that discovers subdomains for a target domain by querying 72 passive OSINT sources at once, from certificate transparency logs to search engines and threat intelligence feeds. It runs on asyncio with a disk-backed findings cache and a SQLite database, and it never sends traffic to the target.
- Who is it for?
- Subdominator suits a bug bounty or asset discovery workflow where you need the widest possible passive view of a domain's subdomains and cannot afford to touch the target's infrastructure. It does not suit a task that needs the subdomains resolved, alive or mapped to services, since the sources it queries are archives and indexes rather than a scanner.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 17 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Passive means the target never sees a packet
Subdominator is described as a high-performance passive subdomain enumeration engine, and the repository description frames the goal as discovering subdomains associated with a target domain efficiently and with minimal impact for a bug bounty.
That word passive is the whole design constraint, and it is worth being precise about what it buys and what it costs. Every source the tool queries is an index or an archive that already collected the information: certificate transparency logs record every certificate issued for a domain, search engines have indexed pages, threat intelligence feeds publish the domains they have seen, DNS datasets archive resolutions. Nothing is sent to the target's own infrastructure, so there is no scan to detect and no rate limit to trip on the target side.
The cost is equally clear. What comes back is a list of names that some third party has seen. It is not a list of hosts that resolve, is not a list of hosts that answer, and definitely not a list of services. Anything that needs a live answer still needs a separate tool.
The scale claim is stated in the README. The tool handles 100K+ subdomains per run without memory spikes, thanks to a disk-backed findings cache, and it is built on Python's async and await stack with asyncio and aiohttp. The claim about scale is about the number of names collected, not about the number of live hosts, which is a different and usually much smaller number.
The audience is a bug bounty hunter or an asset discovery engineer. The topic tags say as much: bugbounty, information-gathering, reconnaissance, subdomain-enumeration and subdomain-gathering.
72 sources, four of them off unless you ask for them
The README states that the tool leverages 72 OSINT sources, and the resource selection flags give you control over which ones run.
The --all flag uses all 72 resources, including the ones that are disabled by default. The --include-resources flag takes a comma-separated list of resources to use, and --exclude-resources takes a list to skip. The --list-resources flag, with the short form -ls, prints the resource catalog along with the authentication requirements for each one, which is the flag to run first on any new setup.
Four providers are disabled by default: commoncrawl, github, virustotal and waybackarchive. The stated reason is that they are slow or rate-limited. The --dork option, short -dk, takes a custom search dork for Google and the supported engines, which is how you push the search-engine based providers toward a specific pattern rather than the default query.
Two details in the configuration section are easy to miss and quite useful. First, several providers share one key: coderog, rapidfinder and rapidscan all consume a rapidapi key, whoisxml consumes a whoisxmlapi key, and zoomeyeapi consumes a zoomeye key. The aliases are declared in a table so you can write one key entry instead of three.
Second, the key values are lists, and the README states that Subdominator picks one at random per run. The github example shows two tokens, and the reason to think about this is rate limits: rotating across keys spreads load rather than exhausting one key on the first run.
The key configuration lives in a YAML file that the tool creates automatically on first run at a fixed path under the user's config directory, and the --show-config-path flag prints the active path and exits, which is the safe way to confirm where the tool is actually reading from.
Concurrency, retries and backoff are the runtime knobs
The runtime flags are where the tool's performance characteristics are set, and they are conventional enough to guess correctly.
--concurrency, short -c, sets the number of parallel resource slots, with a default of 8. --timeout, short -t, is the HTTP timeout in seconds, defaulting to 20.0. --retries, short -rt, is the number of retries per failed request, defaulting to 3, and --retry-backoff, short -rb, is the backoff multiplier in seconds between retries, defaulting to 1.0.
--recursive-depth, short -rd, sets the maximum recursion depth for discovered subdomains, and the default is 0. So by default the tool is a single-pass collector: it queries the sources for the target domain and reports what they have. Set the depth higher and the tool takes the subdomains it found and treats them as new targets, which multiplies the work but also the surface.
The proxy flag, short -p, takes an HTTP proxy URL, and the insecure flag, short -k, skips SSL certificate verification. Both have environment variable equivalents, and the README is explicit that CLI flags always win over environment variables when both are set.
The environment variable prefix is SUBDOMINATOR_, and the table maps four of them: SUBDOMINATOR_SSL_VERIFY=false, SUBDOMINATOR_PROXY, SUBDOMINATOR_TIMEOUT and SUBDOMINATOR_CONCURRENCY. The standard HTTP_PROXY, HTTPS_PROXY and NO_PROXY variables are also respected automatically through aiohttp's trust_env, with no prefix needed.
That last point is the one to remember, because it means the tool behaves the way every other HTTP client on your machine already behaves. If you set a corporate proxy in your shell profile, Subdominator will use it without being told.
Installing the tool and running a first scan
The shortest path to a working tool is the uv route, which the README marks as recommended and fastest.
uv tool install subdominatoruv keeps the tool in its own environment, so it does not disturb the Python environment of whatever project you are working in, and the command gives you a subdominator executable on your path.
If you prefer plain pip, the traditional route upgrades an existing installation.
pip install --upgrade subdominatorBuilding from source is a clone and a sync.
git clone https://github.com/RevoltSecurities/Subdominator.git
cd Subdominator
uv syncIf you want the HTML report, add the reports extra at install time, either with pip or with uv.
pip install "subdominator[reports]"
# or
uv tool install "subdominator[reports]"The first thing to do after installing is find out what the tool expects from you, and the list command prints the resource catalog together with the authentication requirements for each provider, and the config path flag points it at a configuration file of your own.
subdominator -d example.com -cp /path/to/my-config.yamlThen run it against a target. The domain flag takes a single target, and the domain-list flag, short -dL, takes a file with one domain per line for bulk mode.
subdominator [flags]On the first run the tool creates the provider configuration template at ~/.config/subdominator/provider-config.yaml, and you add your API keys there before the sources that need them return anything. The --show-config-path flag prints the active path and exits, which is worth doing once to confirm where the tool is reading from, especially if you have more than one home directory or a containerised setup.
For a first run, leave the recursion depth at its default of 0 and keep the default provider set, add keys only for the sources you actually want to use, and check the health diagnostics before a long bulk run.
Findings land in SQLite and in an interactive shell
The persistence layer is what makes this more than a one-shot script.
The README lists SQLite persistence as a feature: findings are saved automatically to a local SQLite database, and you can query and export them through the interactive shell. The dependency list backs that up, with aiosqlite for the async SQLite driver, sqlalchemy for the ORM layer, and asyncpg for PostgreSQL, which suggests the same storage abstraction can point at a Postgres instance rather than a local file.
The interactive audit shell is described as letting you search, filter, export and manage historical findings. The word historical is the point. A subdomain found months ago and one found today end up in the same store, so a shell query can answer questions like which names appeared since the last run, which came from a source you have just started using, or what the footprint looked like before a change.
The output formats are the other half. Plain text goes to a file with the output flag, JSONL is described as a stream, and there is a JSON report and an HTML report. The HTML report is behind an optional extra, because jinja2 is the only dependency in the reports extra, and the install instructions give the two ways to pull it in.
The persistence choice also explains the memory claim. The disk-backed findings cache and the SQLite store are the same idea seen from two sides: keep the working set small by putting results on disk rather than in RAM, which is what lets the tool hold a large number of names in one run without the process growing with the result set.
Health diagnostics before a run, and a Docker path with no Python setup
Two operational details are worth calling out because they save time in practice.
The first is health diagnostics, described as verifying connectivity and API key status before a run. In a tool that fans out to 72 different services with different authentication schemes, the failure mode without this is a run that appears to work and quietly returns nothing from the half of the sources whose keys are missing or expired. Checking first turns a silent coverage gap into a visible error.
The second is the Docker path. The README lists Docker-ready as a feature, with pre-built images on GHCR and no local Python setup required. The repository has a Docker/ directory at the top level. Since the tool requires Python 3.13 or newer, and the pyproject pins requires-python to >=3.13, using the published image also avoids the problem of a system Python that is too old.
The installation options are otherwise layered by preference. The uv tool install route is marked as recommended and as fastest, because uv keeps the tool in its own environment and does not disturb the Python environment of whatever project you are in. The traditional pip route upgrades an existing install, and the from source route clones the repository and runs uv sync.
The tool itself is packaged with hatchling, the wheel target packages the src/subdominator directory, and the console script is registered as subdominator pointing at subdominator.cli.app:main. The test configuration sets the pythonpath to src and uses strict asyncio mode, with pytest, pytest-asyncio and pytest-anyio in the dev group.
The latest release is v3.0.3, tagged on 2026-10-07, with a RELEASES.md file at the repository root, and the last push to the repository was the same day.
Where Subdominator is the wrong tool
The first wrong-tool case is anything that needs live hosts. Subdominator returns names that archives have seen. If your task is to know which of those resolve, which answer on port 443, or which serve an admin panel, you need a resolver and a scanner afterwards, and the passive list is a candidate list rather than an inventory. The tool is deliberately not that, and the README describes it as passive throughout.
The second case is a target with a small public footprint. If a domain has a handful of subdomains and none of them are interesting, the 72 sources add latency and API-key management without adding names. The cost of the approach is highest exactly where the payoff is lowest, which is the opposite of how a scanning tool behaves.
The third case is an organisation that cannot have API keys on disk. The provider configuration is a plain YAML file in the user's config directory holding keys in clear text, and the tool picks one key at random per run. There is no mention of a secret manager, an encrypted store, or key rotation. For a personal bug bounty machine that is a non-issue, and for a shared engineering box it is a decision that needs making consciously.
The fourth case is a scope-sensitive environment. Passive collection of a domain's subdomains is still reconnaissance, and in many bug bounty programmes the scope document lists what you may enumerate and what you may not. A tool that queries 72 external indexes about your client's entire domain surface can surface hosts that are not in scope at all, and the 100K+ names per run figure is an argument for reviewing the output before you act on it.
One more limitation to set expectations about. The resource set is a moving target, because it depends on third-party APIs, free tiers and public endpoints. The README notes that a provider list exists and that a diagnostics command checks key status, which is the right way to find out what is actually working, but it also means coverage on a given day is not the same as coverage on another day.
Editorial conclusion
Subdominator suits a bug bounty or asset discovery workflow where you need the widest possible passive view of a domain's subdomains and cannot afford to touch the target's infrastructure. It does not suit a task that needs the subdomains resolved, alive or mapped to services, since the sources it queries are archives and indexes rather than a scanner. Before adopting, run subdominator -ls to see which of the 72 resources need an API key and which are disabled by default, and note that provider-config.yaml on disk holds your keys in plain text, so that file needs the same care as any other credential store.
Frequently asked questions
What is Subdominator and how does it find subdomains?
Subdominator is a Python passive subdomain enumeration engine that queries 72 OSINT sources, including certificate transparency logs, search engines, threat intelligence feeds and DNS archives, to map a target domain's subdomains. It is built on asyncio and aiohttp and never sends traffic to the target.
How do I install Subdominator?
The recommended route is uv tool install subdominator, which is described as fastest. The traditional route is pip install --upgrade subdominator, and you can also clone the repository and run uv sync. HTML report support is an optional extra installed with pip install "subdominator[reports]", and Python 3.13 or newer is required.
How do I add API keys to Subdominator?
On first run the tool creates a template at ~/.config/subdominator/provider-config.yaml, and you add your keys under each provider. Each entry is a list so you can supply multiple keys, and the tool picks one at random per run. You can point it at a different file with --config-path.
Which Subdominator providers are disabled by default?
commoncrawl, github, virustotal and waybackarchive are disabled by default because they are slow or rate-limited. You enable them explicitly with --all or by naming them in --include-resources, and --list-resources prints the catalog with the authentication requirements for each one.
Does Subdominator resolve or scan the subdomains it finds?
No. The tool is a passive enumeration engine, so it returns names that third-party indexes and archives have already seen rather than hosts that are currently reachable. Subdomain discovery here is a candidate list, not a live inventory.
What license is Subdominator released under?
Subdominator is MIT licensed, with the licence declared in the project metadata and a LICENSE file at the root of the repository. The package version is 3.0.3 and it is published on PyPI.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/revoltsecurities-subdominator)Was this useful?