How Are Modules Organized in Holehe: A Complete Guide to the Discovery Architecture
Holehe organizes its site-checking modules into a hierarchical holehe/modules package grouped by functional domain, with dynamic discovery via import_submodules in core.py eliminating the need for manual registration.
Holehe, the open-source email enumeration tool by megadose (megadose/holehe), uses a modular architecture that scales to hundreds of services without requiring a centralized registry. Understanding how modules are organized in holehe reveals a design pattern that prioritizes automatic discovery and logical categorization. The architecture separates site-specific logic from orchestration, allowing contributors to add new platforms by simply dropping Python files into categorized subdirectories.
Domain-Based Package Hierarchy
The holehe/modules directory acts as the root package, with subdirectories representing distinct functional categories. Each subdirectory contains individual Python files implementing checks for specific services.
Category Organization
| Directory | Example Modules | Purpose |
|---|---|---|
social_media/ |
twitter.py, instagram.py, facebook.py |
Check email usage on major social networks |
software/ |
docker.py, adobe.py, lastpass.py |
Query SaaS and desktop software accounts |
cms/ |
wordpress.py, gravatar.py, atlassian.py |
Verify presence on content management platforms |
forum/ |
mybb.py, demonforums.py, blitzortung.py |
Test registration on web forums |
shopping/ |
amazon.py, ebay.py, vivino.py |
Detect e-commerce accounts |
payment/ |
venmo.py |
Look for payment service accounts |
crm/ |
hubspot.py, zoho.py, amocrm.py |
Check customer relationship platforms |
company/, music/, sport/, real_estate/, programing/, crowfunding/, medias/, jobs/, transport/ |
Various niche modules | Additional specialized services |
Each module file defines a single async function named after the service (e.g., async def twitter(email, client, out)) that performs the HTTP request and appends a result dictionary to the shared out list.
Dynamic Module Discovery in core.py
Rather than maintaining a static list of supported services, holehe/core.py implements automatic module discovery using Python's pkgutil module.
The import_submodules Function
The import_submodules function recursively walks the package tree to load every module dynamically:
# holehe/core.py
def import_submodules(package, recursive=True):
"""Get all the holehe submodules"""
if isinstance(package, str):
package = importlib.import_module(package)
results = {}
for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
full_name = package.__name__ + '.' + name
results[full_name] = importlib.import_module(full_name)
if recursive and is_pkg:
results.update(import_submodules(full_name))
return results
This function returns a dictionary mapping full module paths (e.g., 'holehe.modules.social_media.twitter') to imported module objects.
Filtering to Callable Functions
The get_functions method transforms the imported modules into a list of callable check functions:
# holehe/core.py
def get_functions(modules, args=None):
"""Transform the modules objects to functions"""
websites = []
for module in modules:
if len(module.split(".")) > 3:
modu = modules[module]
site = module.split(".")[-1]
# optional filtering (e.g. --no-password-recovery)
websites.append(modu.__dict__[site])
return websites
The filter len(module.split(".")) > 3 ensures only modules nested within category directories (not the root modules package itself) are processed.
Standard Module Interface
Every module in the holehe/modules tree follows an identical asynchronous interface pattern.
Required Function Signature
Each module exports an async function matching the filename:
# holehe/modules/social_media/twitter.py (structure example)
from holehe.core import *
from holehe.localuseragent import *
async def twitter(email, client, out):
name = "twitter"
domain = "twitter.com"
method = "register"
try:
resp = await client.get("https://api.twitter.com/...", params={"email": email})
# ... logic to determine if account exists ...
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": False,
"rateLimit": False,
"exists": True, # or False based on response
"emailrecovery": None,
"phoneNumber": None,
"others": None
})
except Exception:
out.append({"name": name, "domain": domain, "method": method,
"frequent_rate_limit": False,
"rateLimit": True, "exists": False,
"emailrecovery": None, "phoneNumber": None, "others": None})
Output Dictionary Schema
The out parameter is a shared list that collects standardized result dictionaries containing:
name: Service identifierdomain: Target domainmethod: Verification method usedexists: Boolean indicating if the email is registeredrateLimit: Boolean indicating if the service temporarily blocked the checkemailrecovery: Partial email address if recovery info is exposedphoneNumber: Partial phone number if exposedothers: Additional metadata
Execution Flow and Error Handling
Once loaded, modules execute through the launch_module wrapper in core.py, which provides standardized exception handling and output normalization.
The launch_module Function
# holehe/core.py
async def launch_module(module, email, client, out):
try:
await module(email, client, out)
except Exception:
name = str(module).split('<function ')[1].split(' ')[0]
out.append({
"name": name,
"domain": data[name],
"rateLimit": False,
"error": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
This wrapper ensures that a crashing module does not interrupt the entire enumeration process. The client parameter is typically an httpx.AsyncClient instance shared across all modules to enable connection pooling.
Extending the Module Library
Adding support for a new service requires no changes to core.py or any registry file.
Creating a New Module
- Create a new Python file in the appropriate category directory (e.g.,
holehe/modules/social_media/example.py) - Import the standard helpers:
from holehe.core import *andfrom holehe.localuseragent import * - Define the async function with the standard signature
- Implement the request logic and append results to
out
The new module is automatically discovered on the next run because import_submodules recursively walks the directory tree at startup.
Summary
- Hierarchical organization: Modules reside in
holehe/modulesgrouped by functional domains likesocial_media,software, andshopping - Automatic discovery: The
import_submodulesfunction incore.pyrecursively imports all Python files without manual registration - Standardized interface: Each module exposes a single async function named after the service that accepts
(email, client, out)parameters - Fault tolerance:
launch_modulewraps each execution to prevent individual service failures from crashing the entire enumeration - Simple extension: Adding new services requires only creating a Python file in the appropriate category directory
Frequently Asked Questions
How does holehe discover new modules automatically?
Holehe uses the import_submodules function in holehe/core.py which utilizes pkgutil.walk_packages to recursively traverse the holehe/modules directory tree. This imports every Python file found in category subdirectories, builds a dictionary of module references, and extracts the callable functions via get_functions. No central registry or configuration file needs updating when new modules are added.
What is the standard function signature for a holehe module?
Every module must define an async function with the signature async def servicename(email, client, out) where email is the target string, client is an httpx.AsyncClient for HTTP requests, and out is a list where the function appends a result dictionary. The function name must match the filename (e.g., twitter.py contains async def twitter).
How are modules categorized in holehe?
Modules are organized into subdirectories under holehe/modules based on the service's primary function. Categories include social_media, software, cms, forum, shopping, payment, crm, company, music, sport, and others. This grouping allows users to mentally map related services and helps contributors place new modules in logical locations.
Can I add custom modules to holehe without modifying core files?
Yes. Simply create a new Python file in any subdirectory of holehe/modules (or create a new category directory) following the standard async function pattern. The dynamic loader will discover and execute your module automatically on the next run. No changes to core.py, __init__.py, or any registry are required unless you need to add special handling logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →