How to Use the Login Method for Account Detection in Holehe: A Complete Guide

The login method in Holehe queries a service's authentication endpoint with an email address and analyzes the response for account-existence indicators without requiring a password.

Holehe is an open-source OSINT tool by megadose/holehe that checks whether an email address is registered across hundreds of online services. Unlike public profile lookups, the login method exploits authentication endpoints—login forms, APIs, or mobile endpoints—to infer account existence based on subtle response differences. This technique is particularly valuable when services hide registration status from public view.


Understanding the Login Method Architecture

Holehe's modular design separates detection strategies into distinct methods. Each module declares its approach via a class-level method attribute, allowing the core engine to route requests appropriately.

How Methods Are Classified

Method Endpoint Targeted Typical Indicator
login Authentication/login endpoint "Incorrect password," "email exists," or session cookies
register Sign-up/registration page "Email already taken"
forgot Password reset flow "Email sent" vs "user not found"
profile Public user profile Direct 200 OK or 404 response

The login method is preferred for stealth and coverage: it mimics legitimate user behavior, bypasses rate-limiting on public APIs, and works even when profile pages are disabled. When method = "login" is set, the module's run() coroutine crafts a POST request with the target email (and often a dummy password) to the service's login endpoint.


Core Engine Flow in holehe/core.py

The detection orchestration happens in holehe/core.py. The Holehe class performs these steps:

  1. Module Discovery – Scans holehe/modules/ and its subdirectories for Python files containing detection classes.
  2. Method Filtering – If methods=["login"] is passed, only modules with method = "login" are loaded.
  3. Client Initialization – Creates an httpx.AsyncClient with rotation headers, timeout settings, and optional proxy configuration.
  4. Concurrent Execution – Gathers all module run() coroutines with asyncio.gather(), respecting max_connections limits.
  5. Result Aggregation – Returns standardized dictionaries with status (FOUND, NOT_FOUND, UNKNOWN), message, and metadata.

# From holehe/core.py - simplified orchestration flow

async def run(self):
    client = httpx.AsyncClient(
        headers=self.rotation_headers,
        timeout=self.timeout,
        proxies=self.proxy
    )
    tasks = [
        module.run(client, self.email) 
        for module in self.loaded_modules 
        if module.method in self.target_methods
    ]
    results = await asyncio.gather(*tasks)
    return self._normalize(results)

Login Method Implementation Examples

Each login-method module follows a consistent pattern: define the url, build request data, post to the endpoint, and parse the response for existence signals.

Snapchat Module (holehe/modules/social_media/snapchat.py)

The Snapchat implementation demonstrates a typical login-method flow against an internal API:


# holehe/modules/social_media/snapchat.py

import httpx

class Snapchat:
    name = "snapchat"
    method = "login"
    url = "https://accounts.snapchat.com/accounts/merlin/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        # Snapchat requires specific headers and a CSRF token

        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Requested-With": "XMLHttpRequest"
        }
        
        data = {
            "username": email,
            "password": "ThisIsADummyPassword123!",  # Intentionally wrong

            "continue": "/"
        }
        
        resp = await client.post(Snapchat.url, data=data, headers=headers)
        json_resp = resp.json()
        
        # Snapchat returns specific error codes for existing vs non-existing

        if json_resp.get("status_code") == "login_incorrect_password":
            return {
                "status": "FOUND",
                "message": "Account exists (invalid password response)"
            }
        elif json_resp.get("status_code") == "login_invalid_user":
            return {
                "status": "NOT_FOUND",
                "message": "No account with this email"
            }
        return {"status": "UNKNOWN", "message": "Ambiguous response"}

Key insight: Snapchat distinguishes between "user not found" and "wrong password" at the API level—exactly the leak the login method exploits.

Patreon Module (holehe/modules/social_media/patreon.py)

Patreon uses a GraphQL endpoint for login, requiring JSON payloads:


# holehe/modules/social_media/patreon.py

import httpx
import json

class Patreon:
    name = "patreon"
    method = "login"
    url = "https://www.patreon.com/api/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        payload = {
            "data": {
                "type": "user",
                "attributes": {
                    "email": email,
                    "password": "WrongPasswordForDetection123"
                }
            }
        }
        
        resp = await client.post(
            Patreon.url,
            json=payload,
            headers={"Content-Type": "application/vnd.api+json"}
        )
        
        # Patreon returns 400 with specific error structure for existing accounts

        if resp.status_code == 400:
            errors = resp.json().get("errors", [])
            for error in errors:
                if error.get("code") == "invalid_password":
                    return {"status": "FOUND", "message": "Account exists"}
                if error.get("code") == "email_not_found":
                    return {"status": "NOT_FOUND"}
        
        return {"status": "UNKNOWN"}

WordPress Module (holehe/modules/cms/wordpress.py)

For self-hosted WordPress sites, the login method targets wp-login.php:


# holehe/modules/cms/wordpress.py

import httpx
from urllib.parse import urljoin

class Wordpress:
    name = "wordpress"
    method = "login"
    # Base URL provided per-site; shown as template

    url_template = "{base}/wp-login.php"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str, base_url: str) -> dict:
        login_url = Wordpress.url_template.format(base=base_url)
        
        data = {
            "log": email,
            "pwd": "DummyPasswordDetection",
            "wp-submit": "Log In"
        }
        
        resp = await client.post(login_url, data=data)
        
        # WordPress redirects to wp-admin on successful login (we won't succeed)

        # But error messages reveal existence:

        if "Unknown username" in resp.text or "Invalid username" in resp.text:
            return {"status": "NOT_FOUND", "message": "Username/email not recognized"}
        if "The password you entered" in resp.text:
            return {"status": "FOUND", "message": "Valid user, wrong password"}
        
        return {"status": "UNKNOWN"}

Using the Login Method: Command-Line Interface

The holehe CLI provides straightforward access to method-specific scanning.

Basic Email Check (All Methods)

holehe alice@example.com

This runs all detection methods—login, register, forgot, and profile—sequentially per service.

Restrict to Login Method Only

holehe alice@example.com --method login

Or using the shorthand:

holehe alice@example.com -m login

Performance benefit: Login-only scans typically complete 3-4x faster because they skip registration-page parsing and profile enumeration.

Multiple Methods Selection

holehe alice@example.com -m login -m forgot

Output Formatting with Login Results

holehe alice@example.com --method login --output json > results.json

The JSON output includes method attribution for each finding:

{
  "snapchat": {
    "status": "FOUND",
    "method": "login",
    "message": "Account exists (invalid password response)",
    "timestamp": "2024-01-15T09:23:11Z"
  },
  "patreon": {
    "status": "NOT_FOUND",
    "method": "login",
    "message": "No account with this email"
  }
}

Programmatic Usage: Python API

For integration into larger OSINT pipelines, import the Holehe class directly and constrain methods programmatically.

Basic Programmatic Call

import asyncio
from holehe.core import Holehe

async def check_login_method(email: str):
    """
    Check email against all services using login-method detection only.
    """
    scanner = Holehe(
        email=email,
        methods=["login"],           # Restrict to login method

        max_connections=10,          # Concurrent requests

        timeout=15.0
    )
    
    results = await scanner.run()
    return results

# Execute

output = asyncio.run(check_login_method("alice@example.com"))

# Process results

for service, data in output.items():
    if data["status"] == "FOUND":
        print(f"[FOUND] {service}: {data['message']}")

Customizing the HTTP Client

from holehe.core import Holehe
import httpx

async def check_with_proxy(email: str, proxy_url: str):
    # Holehe accepts a pre-configured client or proxy string

    scanner = Holehe(
        email=email,
        methods=["login"],
        proxy={"http://": proxy_url, "https://": proxy_url},
        rotation_headers={
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.0"
        }
    )
    return await scanner.run()

Handling Rate Limits and Retries

from holehe.core import Holehe
import asyncio

async def resilient_check(email: str, retries: int = 3):
    for attempt in range(retries):
        try:
            scanner = Holehe(
                email=email,
                methods=["login"],
                max_connections=5,  # Lower concurrency to avoid bans

                timeout=30.0
            )
            return await scanner.run()
        except httpx.ConnectTimeout:
            await asyncio.sleep(2 ** attempt)  # Exponential backoff

    
    raise RuntimeError("Maximum retries exceeded")

Building Custom Login-Method Modules

To extend Holehe with new services, create a module following the established pattern.

Module Template


# holehe/modules/newservice/acmecorp.py

import httpx

class AcmeCorp:
    # Required: human-readable service name

    name = "acmecorp"
    
    # Required: method classification

    method = "login"
    
    # Required: endpoint URL

    url = "https://api.acmecorp.com/v2/auth/login"

    @staticmethod
    async def run(client: httpx.AsyncClient, email: str) -> dict:
        """
        Execute login-method detection for AcmeCorp.
        
        Returns dict with keys:
            - status: "FOUND" | "NOT_FOUND" | "ERROR" | "UNKNOWN"
            - message: Human-readable explanation
            - metadata: Optional additional data
        """
        # Build payload with email and deliberately wrong password

        payload = {
            "email": email,
            "password": "ThisIsNotARealPassword123",
            "device_id": "holehe-detection"
        }
        
        try:
            resp = await client.post(
                AcmeCorp.url,
                json=payload,
                headers={"Content-Type": "application/json"}
            )
            
            # Parse service-specific existence indicators

            if resp.status_code == 200:
                # Unexpected success—shouldn't happen with wrong password

                return {
                    "status": "UNKNOWN",
                    "message": "Unexpected successful authentication"
                }
            
            if resp.status_code == 401:
                error_code = resp.json().get("error_code")
                
                if error_code == "INVALID_CREDENTIALS":
                    # Service confirmed email exists but password wrong

                    return {
                        "status": "FOUND",
                        "message": "Account exists (invalid credentials)"
                    }
                elif error_code == "USER_NOT_FOUND":
                    return {
                        "status": "NOT_FOUND",
                        "message": "No account registered with this email"
                    }
            
            # Non-standard response

            return {
                "status": "UNKNOWN",
                "message": f"Unexpected status code: {resp.status_code}"
            }
            
        except httpx.RequestError as exc:
            return {
                "status": "ERROR",
                "message": f"Network error: {str(exc)}"
            }

Module Discovery

Place the file in any subdirectory of holehe/modules/:


holehe/
├── modules/
│   ├── social_media/
│   ├── shopping/
│   └── your_category/          # Create as needed

│       └── acmecorp.py         # Your new module

The core engine automatically discovers classes with method attributes—no registration required.


Security and Ethical Considerations

The login method generates failed authentication attempts against target services. Consider these implications:

  • Rate limiting: Services may throttle or block IPs after repeated login attempts. Use proxy rotation and conservative concurrency.
  • Account lockouts: Some services lock accounts after N failed logins. The dummy password technique generally avoids this, but verify per-service behavior.
  • Terms of Service: Automated login attempts may violate ToS. Ensure compliance with applicable laws and service agreements.
  • False positives: Services changing error messages can cause misclassification. Monitor module reliability and update parsers accordingly.

Holehe mitigates some risks through:

  • Non-destructive probing: Never attempts credential stuffing with real passwords
  • Configurable delays: Built-in jitter between requests via asyncio.sleep()
  • Header rotation: Mimics diverse browser profiles to avoid fingerprinting

Summary

  • The login method queries authentication endpoints with an email and dummy password, inferring account existence from error responses.
  • Core implementation lives in holehe/core.py, which filters modules by their method attribute and orchestrates concurrent httpx requests.
  • Key modules: snapchat.py, patreon.py, and wordpress.py demonstrate distinct login-method patterns—form POSTs, JSON APIs, and CMS endpoints.
  • CLI usage: holehe email@example.com -m login restricts scans to login-method modules for speed and stealth.
  • Python API: Instantiate Holehe(email=..., methods=["login"]) for programmatic integration with custom clients and proxies.
  • Extensibility: Create new modules by defining name, method = "login", url, and an async run() coroutine following the standardized return format.

Frequently Asked Questions

What is the difference between the login method and the register method in Holehe?

The login method targets existing user authentication endpoints, while the register method checks account creation flows for "email already taken" messages. Login methods are generally more reliable because they directly interact with a service's primary identity system rather than secondary registration pages, which some services disable or protect with CAPTCHAs. However, register methods can detect accounts even when login endpoints return generic errors for both missing and existing users.

Why does Holehe use a dummy password for login detection?

A deliberately incorrect password triggers the "wrong password" response—proof the account exists—without risk of actual authentication. Services rarely distinguish between "user not found" and "wrong password" in client-side code, but server responses (JSON error codes, HTTP headers, or redirect patterns) often leak this information. The dummy password approach, implemented across modules like snapchat.py and patreon.py, exploits this information asymmetry safely.

Can I combine the login method with proxy rotation for large-scale scans?

Yes—pass a proxy configuration to the Holehe constructor or set environment variables for httpx to consume. The core engine accepts proxy={"http://": url, "https://": url} or rotates clients externally. For CLI usage, export HTTP_PROXY and HTTPS_PROXY before invoking holehe. Recommended practice: use residential proxies, limit max_connections to 5-10, and implement exponential backoff for 429/403 responses to maintain detection accuracy across IP pools.

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 →