jhaddix/domain: The Reconnaissance Automation Script That Time Left Behind
Hook
With 940 GitHub stars, jhaddix/domain was a go-to reconnaissance automation script for bug bounty hunters—yet it hasn't been meaningfully updated since 2017, relying on a deprecated Recon-ng installation and fragile search engine scrapers that likely haven't worked in years.
Context
In the mid-2010s, subdomain enumeration was a tedious, manual process. Security researchers hunting for bug bounties or conducting penetration tests would spend hours manually querying search engines, running DNS brute force attacks, and consolidating results from multiple tools. Each reconnaissance phase required remembering different command-line syntaxes, managing intermediate files, and manually chaining tools together.
Jason Haddix and Leif Dreizler built jhaddix/domain to solve this workflow friction. The script wrapped Recon-ng—a reconnaissance framework modeled after Metasploit—and automated the execution of its subdomain discovery modules. Rather than manually loading modules, creating workspaces, and exporting results, security professionals could run a single Python script against multiple domains. The tool would orchestrate Google, Bing, Yahoo, Baidu, and Netcraft scrapers, perform DNS brute forcing with wordlists, resolve discovered subdomains to IP addresses, and optionally integrate AltDNS for generating DNS permutations. It represented the philosophy of its era: glue together existing tools with Python scripts to automate repetitive workflows.
Technical Insight
The architecture of jhaddix/domain is straightforward but reveals interesting decisions about tool orchestration in the pre-container era. At its core, the script is a Python wrapper that programmatically drives Recon-ng through its API rather than spawning shell commands. This approach provides better error handling and result extraction compared to parsing stdout.
The main execution flow follows a workspace-per-domain pattern. For each target domain, the script creates a dedicated Recon-ng workspace, loads and executes a predefined sequence of modules, then extracts discovered hosts from the workspace database. Here's a simplified view of how it orchestrates module execution:
# Simplified example based on the tool's approach
import recon
# Initialize Recon-ng
recon_instance = recon.Recon()
# Create workspace for target
recon_instance.do_workspaces(f"create {domain}")
recon_instance.do_workspaces(f"load {domain}")
# Add seed domain
recon_instance.query(f"INSERT INTO domains VALUES ('{domain}')")
# Execute discovery modules in sequence
modules = [
'recon/domains-hosts/google_site_web',
'recon/domains-hosts/bing_domain_web',
'recon/domains-hosts/brute_hosts',
'recon/hosts-hosts/resolve'
]
for module in modules:
recon_instance.do_load(module)
recon_instance.do_run()
# Extract results from workspace database
results = recon_instance.query("SELECT DISTINCT host FROM hosts WHERE host LIKE '%{domain}'")
This orchestration pattern is particularly interesting because it treats Recon-ng as a library rather than a standalone tool. The script doesn't shell out to the recon-ng command-line interface—it imports the framework and calls methods directly. This provides transaction-like guarantees: either all modules complete successfully, or the script can handle failures gracefully without leaving the user in a partially-configured workspace.
The DNS brute force module configuration reveals another design decision. The script allows users to specify custom wordlists via command-line arguments and programmatically sets module options before execution:
# Configure brute force module with custom wordlist
recon_instance.do_load('recon/domains-hosts/brute_hosts')
recon_instance.do_set(f'SOURCE {domain}')
recon_instance.do_set(f'WORDLIST /path/to/custom/wordlist.txt')
recon_instance.do_run()
This parameterization allows the same script to serve different reconnaissance scenarios—light scanning with small wordlists for quick assessments, or exhaustive enumeration with comprehensive dictionaries for thorough audits.
The integration with AltDNS demonstrates primitive tool chaining. After Recon-ng completes its discovery phase, the script exports discovered subdomains to a temporary file, shells out to AltDNS as a subprocess, then imports the permutated results back into the Recon-ng workspace for resolution. This file-based IPC represents the state of tool integration at the time—before modern tools adopted shared output formats like JSON or leveraged message queues for streaming results.
The result aggregation logic consolidates discoveries from all modules by querying the Recon-ng SQLite database directly. This database-centric approach meant that regardless of which module discovered a subdomain, the final output was deduplicated and normalized. However, it also created a tight coupling to Recon-ng's schema, making the script brittle to framework updates.
Gotcha
The primary limitation is time itself. The script clones Recon-ng from a Bitbucket repository that no longer exists and expects a version of the framework that predates its major v5 rewrite in 2019. Modern Recon-ng uses a completely different module naming convention, workspace API, and database schema. Attempting to run jhaddix/domain today will likely result in import errors, module loading failures, or silent data loss as it queries database tables that have been restructured.
The search engine scraping modules face an even more fundamental problem: they almost certainly don't work anymore. Web scraping is inherently fragile, and search engines actively combat automated queries with CAPTCHA challenges, rate limiting, and frequent HTML structure changes. The Google CSE module, for instance, relied on a specific DOM structure and CSS classes that Google has likely changed dozens of times since 2017. Without active maintenance to update selectors and scraping logic, these modules return empty results or crash entirely. Many security practitioners who try to use this tool report that the only reliable modules are the DNS-based ones (brute forcing and resolution), which don't depend on external web interfaces—but those same capabilities are available in simpler, more focused tools.
Dependency management presents another practical challenge. The script assumes AltDNS is installed globally and available in PATH, requires specific Python 2.7 libraries that may conflict with system packages, and expects wordlists in conventional locations without clear documentation about where to obtain them. This installation complexity made sense when the tool was actively used and community knowledge filled the gaps, but newcomers face a steep configuration curve with little support.
Verdict
Use if: You're working in an air-gapped environment with a pre-configured legacy Recon-ng installation from 2016-2017, need a reference implementation for understanding how to programmatically drive reconnaissance frameworks, or are conducting security research on the evolution of OSINT tooling. The script has historical value and demonstrates tool orchestration patterns worth studying. Skip if: You need reliable, production-ready subdomain enumeration for modern security assessments, bug bounty hunting, or red team engagements. The outdated dependencies, broken scraping modules, and lack of maintenance make this impractical for actual reconnaissance work. Modern alternatives like Amass, Subfinder, or even current versions of Recon-ng itself provide better accuracy, performance, and maintainability. Even for learning purposes, studying contemporary tools will teach you patterns that apply to today's infrastructure rather than mid-2010s Python 2 architectures. The 940 stars reflect past influence, not present utility—treat this as a museum piece that documents how far reconnaissance automation has evolved.