# How Holehe's Password Recovery Detection Method Works: A Technical Deep Dive

> Discover how Holehe's password recovery detection works. This technical deep dive explains how Holehe identifies registered emails by analyzing forgot password flows without authentication. Learn more about the megadose/holehe ...

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: deep-dive
- Published: 2026-08-23

---

**Holehe detects accounts by programmatically invoking services' "forgot password" flows, analyzing responses for error codes or masked recovery data that reveal whether an email is registered—all without authenticating.**

The open-source email checker Holehe categorizes its detection techniques by strategy. The **password recovery detection method** stands out as a non-intrusive approach that leverages public password-reset endpoints to verify account existence. This article explains how Holehe implements this method, from module selection to response parsing, based on the megadose/holehe source code.

## How Holehe Selects Password Recovery Modules

Holehe's modular architecture loads and filters detection strategies dynamically. The framework handles password-recovery modules through a specific pipeline in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py).

### Module Discovery and Filtering

When you run Holehe, the core system executes three key steps:

1. **Load all modules** using `import_submodules("holehe.modules")` — this recursively imports every Python file in the modules directory
2. **Extract coroutine functions** via `get_functions()` which scans imported modules for async functions matching the required signature
3. **Filter by method type** — unless you pass `--no-password-recovery`, modules with `method = "password recovery"` are included in the scan

This filtering logic appears around line 8 of the core execution flow, where the code checks each module's `method` attribute against the user's CLI flags.

### The Async Execution Model

Selected modules run as concurrent async tasks. The `launch_module()` coroutine spawns each check with three standard arguments:

```python
await module_function(email, client, out)

```

- `email`: The target email address to investigate
- `client`: A shared `httpx.AsyncClient` instance for connection pooling
- `out`: A list that collects result dictionaries from all modules

This design allows dozens of password-recovery checks to run in parallel without blocking.

## Anatomy of a Password Recovery Module

Every password-recovery module in Holehe follows an identical five-phase pattern. Examining [`holehe/modules/software/adobe.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/software/adobe.py), [`holehe/modules/social_media/odnoklassniki.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/odnoklassniki.py), and [`holehe/modules/products/samsung.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/products/samsung.py) reveals this structure clearly.

### Phase 1: Request Construction

Modules build HTTP requests that mimic legitimate browser behavior:

```python

# From adobe.py (lines 21-27)

headers = {
    "User-Agent": random.choice(ua),
    "Accept": "application/json",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest",
    "Origin": "https://auth.services.adobe.com",
    "Referer": "https://auth.services.adobe.com/"
}

```

Key elements include:
- **Rotating User-Agent strings** from [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py)
- **CSRF tokens** scraped from initial page loads when required
- **Cookies** gathered from session initialization requests
- **Realistic headers** matching the service's expected client fingerprint

Samsung's module ([`samsung.py`](https://github.com/megadose/holehe/blob/main/samsung.py)) demonstrates advanced preparation: it first fetches a CSRF token from an endpoint, then includes it in subsequent password-recovery requests (lines 28-35).

### Phase 2: Recovery Request Execution

Most modules send `POST` requests to password-reset endpoints. Some require multi-step flows:

| Service | Request Pattern | Endpoint Target |
|---------|---------------|----------------|
| Adobe | Single POST | `/ims/account/v3/password/reset` |
| Odnoklassniki | GET → GET | Login page → Recovery page |
| Samsung | POST with CSRF | `/v1/otp` via Samsung Account API |

The Odnoklassniki implementation ([`odnoklassniki.py`](https://github.com/megadose/holehe/blob/main/odnoklassniki.py), lines 24-26) illustrates a two-stage approach:

```python

# First: establish session context

await client.get("https://ok.ru/dk?st.cmd=anonymLogin")

# Then: request recovery form

await client.get("https://ok.ru/dk?st.cmd=anonymPasswordRecovery")

```

### Phase 3: Response Interpretation

This critical phase determines account existence through response analysis. Holehe modules check multiple signal types:

**JSON API responses** (Adobe pattern, lines 30-38):

```python
j = r.json()
if "errorCode" in str(j.keys()):
    # Error codes indicate unknown email

    exists = False
else:
    # Valid response structure = registered account

    exists = True

```

**HTML parsing** (Odnoklassniki pattern, lines 39-42):

```python
soup = BeautifulSoup(response.text, "html.parser")
masked_email_element = soup.find('button', {'data-value': True})
if masked_email_element:
    masked_email = masked_email_element.get_text()

```

**Status code heuristics**: HTTP 404 or specific redirect patterns often indicate unregistered emails.

### Phase 4: Recovery Data Extraction

When services expose masked recovery information, modules capture it in standardized fields:

| Field | Content Type | Example Source |
|-------|-----------|--------------|
| `emailrecovery` | Masked secondary email | Adobe's `secondaryEmail` JSON key (line 59) |
| `phoneNumber` | Masked phone number | Samsung's `get_phone_number()` result (lines 49-55) |
| `others` | Additional metadata | Provider-specific hints |

Adobe's extraction (lines 55-62):

```python
if "secondaryEmail" in response:
    emailrecovery = response["secondaryEmail"]
else:
    emailrecovery = None

```

### Phase 5: Result Aggregation

Every module appends a standardized dictionary to the shared `out` list:

```python
out.append({
    "name": "adobe",
    "domain": "adobe.com",
    "method": "password recovery",
    "rateLimit": rate_limit_flag,
    "exists": exists_boolean,
    "emailrecovery": recovered_email,
    "phoneNumber": recovered_phone,
    "others": additional_data
})

```

## Why Password Recovery Detection Works

The method exploits a fundamental design tension in identity systems: **password-reset endpoints must verify identity without authentication**, making them informationally leaky by necessity.

Services face a UX dilemma when handling forgotten passwords:
- Return "user not found" → leaks that email is unregistered
- Return "check your email" → confirms registration but hides state
- Expose masked recovery options → confirms registration + reveals partial data

Holehe capitalizes on services choosing the second or third option. The method requires **zero credentials**, generates minimal server load, and bypasses services that deliberately obscure registration status on profile/login pages.

## Running Password Recovery Checks

### Default Execution (Includes Password Recovery)

```bash
holehe target@example.com

```

Output format shows recovered data:

```

[+] adobe.com      secondaryEmail@example.com    / 1234**56**78
[+] ok.ru          masked_email@example.com      / +33 6 ** ** 78
[+] samsung.com    N/A                           / +1 ***-***-7890

```

### Excluding Password Recovery for Speed

```bash
holehe target@example.com --no-password-recovery

```

This filters out all modules where `method == "password recovery"`, reducing the module set to registration-based and login-based detection only.

### Programmatic Usage

```python
import asyncio
import httpx
import holehe.core as hh

async def check_with_recovery(email: str):
    """Run Holehe including password-recovery modules."""
    client = httpx.AsyncClient(timeout=10)
    out = []
    
    # Load and filter modules

    modules = hh.import_submodules("holehe.modules")
    websites = hh.get_functions(modules)  # password recovery included by default

    
    # Execute all checks concurrently

    await asyncio.gather(*[
        mod(email, client, out) for mod in websites
    ])
    
    await client.aclose()
    return out

# Run and filter results

results = asyncio.run(check_with_recovery("user@example.com"))
recovery_hits = [r for r in results if r["method"] == "password recovery" and r["exists"]]

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Orchestrates module loading, CLI parsing, and async execution |
| [`holehe/modules/software/adobe.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/software/adobe.py) | JSON API-based password recovery for Adobe ID |
| [`holehe/modules/social_media/odnoklassniki.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/odnoklassniki.py) | HTML-form-based recovery with masked email extraction |
| [`holehe/modules/products/samsung.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/products/samsung.py) | CSRF-protected recovery with phone number masking |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | Rotating User-Agent pool for request fingerprinting |
| [`holehe/instruments.py`](https://github.com/megadose/holehe/blob/main/holehe/instruments.py) | Progress bar and CLI output formatting |

## Summary

Holehe's **password recovery detection method** operates through these core mechanisms:

- **Module flagging**: Services set `method = "password recovery"` to opt into the strategy
- **CLI control**: The `--no-password-recovery` flag filters these modules on demand
- **Request simulation**: Each module replicates legitimate forgot-password flows with proper headers, cookies, and CSRF handling
- **Response classification**: HTTP status codes, JSON error fields, and HTML structure reveal account existence
- **Data extraction**: Masked emails and phone numbers are parsed from successful recovery page responses
- **Rate limit awareness**: HTTP 429 responses and exceptions set `rateLimit=True` to signal temporary blocking

## Frequently Asked Questions

### Does password recovery detection trigger account security alerts?

Generally no. Password recovery requests are indistinguishable from legitimate user actions. However, aggressive scanning from a single IP may trigger rate limiting (`rateLimit=True` in results) or temporary blocks. Space requests appropriately and respect services' terms of service.

### What information can password recovery modules extract?

Modules capture whatever the service voluntarily exposes during the reset flow: **masked email addresses** (e.g., `t***@example.com`), **masked phone numbers** (e.g., `+1 ***-***-7890`), and occasionally **partial names** or **account creation dates**. They never obtain full passwords or unmasked credentials.

### Why do some services use GET requests for password recovery?

Some platforms (like Odnoklassniki) implement password recovery as multi-page flows where the initial request loads a form with session cookies, and subsequent requests submit data. The GET requests establish session context before the actual recovery attempt.

### How does Holehe handle services that always return "email sent" regardless of registration status?

These services are unsuitable for password-recovery detection and typically use alternative methods in Holehe (registration-based or login-based checks). Modules only implement password recovery when the response semantics reliably distinguish registered from unregistered emails.