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:

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() uses pkgutil.walk_packages() to recursively load all modules under holehe.modules.
  • Function extraction: get_functions() filters modules by depth and extracts callables matching the module's final name segment.
  • Optional filtering: The --no-password-recovery flag 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →