How Holehe Extracts Module Functions: Dynamic Discovery in holehe/core.py
Holehe extracts module functions by recursively importing all submodules from the holehe.modules package using import_submodules(), then filtering and converting them into callable check functions via get_functions() in holehe/core.py.
The open-source OSINT tool Holehe dynamically discovers email-checking routines without maintaining a hard-coded list of services. Understanding how module functions are extracted in Holehe reveals the elegant architecture that powers its extensible plugin system. This process relies on Python’s pkgutil and importlib to transform directory structures into executable check functions at runtime.
Step 1: Recursively Importing Submodules with import_submodules()
Holehe begins module extraction by walking the entire holehe.modules package tree. The import_submodules() function in holehe/core.py (lines 37-47) uses pkgutil.walk_packages() to discover every module nested within the package directories.
This utility accepts a package name or object and returns a dictionary mapping full module names to imported module objects. When recursive=True, it descends into subpackages automatically, ensuring that deeply nested service modules—such as holehe.modules.social_media.facebook—are discovered regardless of their depth in the hierarchy.
# holehe/core.py (lines 37-47)
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
Step 2: Converting Modules to Callable Functions with get_functions()
Once modules are loaded, get_functions() transforms these objects into executable check routines. Located immediately after the import utility in holehe/core.py (lines 50-63), this function filters the module dictionary and extracts specific functions by name.
The filtering logic targets modules with more than three dot-separated components (i.e., holehe.modules.<category>.<service>). For each qualifying module, it retrieves the function whose name matches the final segment of the module path from module.__dict__[site].
Additionally, if the user invokes Holehe with the --no-password-recovery flag, get_functions() explicitly excludes Adobe, Mail.ru, Odnoklassniki, and Samsung checks by inspecting their string representations.
# holehe/core.py (lines 50-63)
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]
if args is not None and args.nopasswordrecovery:
if "adobe" not in str(modu.__dict__[site]) \
and "mail_ru" not in str(modu.__dict__[site]) \
and "odnoklassniki" not in str(modu.__dict__[site]) \
and "samsung" not in str(modu.__dict__[site]):
websites.append(modu.__dict__[site])
else:
websites.append(modu.__dict__[site])
return websites
Runtime Execution Flow
At startup, the main orchestration routine chains these two utilities to build the complete list of checks. The process executes in two sequential calls:
modules = import_submodules('holehe.modules')
functions = get_functions(modules, args)
Each function returned in the list represents an asynchronous check for a specific web service. These functions are subsequently passed to launch_module() for concurrent execution against the target email address.
Practical Implementation Example
You can leverage these same utilities to manually load and execute specific checks. The following example demonstrates dynamic loading of all available modules and manual invocation of a check function:
# Example: Dynamically load all checking functions
from holehe.core import import_submodules, get_functions
# Load every submodule under holehe.modules
all_modules = import_submodules('holehe.modules')
# Convert them into callable async functions
check_functions = get_functions(all_modules) # → list of async functions
# Run a single check (e.g., Facebook) manually
import asyncio, httpx
async def run_one():
async with httpx.AsyncClient() as client:
await check_functions[0]('target@example.com', client, [])
asyncio.run(run_one())
Key Files and Architecture
The module extraction system relies on specific files within the megadose/holehe repository:
holehe/core.py: Implementsimport_submodules(),get_functions(), and the main orchestration logic.holehe/modules/__init__.py: Package marker enablingpkgutil.walk_packages()to discover subpackages.- Service modules (e.g.,
holehe/modules/social_media/facebook.py): Individual files containing the async functions extracted by this process.
This architecture ensures that adding a new email-checking service requires only creating a new Python module in the appropriate subdirectory. The dynamic discovery system automatically incorporates new modules without registry updates or configuration changes.
Summary
- Dynamic discovery:
import_submodules()usespkgutil.walk_packages()to recursively load all modules underholehe.modules. - Function extraction:
get_functions()filters modules by depth and extracts callables matching the module's final name segment. - Optional filtering: The
--no-password-recoveryflag excludes specific services (Adobe, Mail.ru, Odnoklassniki, Samsung) by string inspection. - Zero-configuration extensibility: New services added to the modules directory are automatically available without code changes to the loader.
Frequently Asked Questions
How does Holehe discover new modules without hard-coding them?
Holehe discovers modules dynamically using pkgutil.walk_packages() in the import_submodules() function. This walks the holehe.modules package directory at runtime, importing every Python file it finds and returning them as module objects, eliminating the need for a static registry.
What is the purpose of the --no-password-recovery flag in get_functions()?
The --no-password-recovery flag filters out services that specifically perform password recovery checks. When args.nopasswordrecovery is true, get_functions() excludes Adobe, Mail.ru, Odnoklassniki, and Samsung by checking if these strings appear in the function's representation, preventing potentially disruptive account recovery attempts.
Why does get_functions() check for more than three components in the module name?
The depth check (len(module.split(".")) > 3) ensures that only actual service modules are processed, not category packages. Valid service modules follow the pattern holehe.modules.<category>.<service> (four components), while parent packages have fewer segments. This distinction prevents attempts to extract functions from container directories.
Which file contains the core logic for module extraction in Holehe?
The core extraction logic resides in holehe/core.py. This file defines both import_submodules() (lines 37-47) for recursive module loading and get_functions() (lines 50-63) for converting loaded modules into callable check functions.
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 →