How Holehe Rotates User-Agent Strings to Avoid Detection

Holehe rotates User-Agent strings by maintaining a centralized JSON pool of browser fingerprints in holehe/localuseragent.py and selecting entries at random for each HTTP request.

The open-source OSINT tool Holehe relies on mimicking legitimate browser traffic to check whether email addresses are registered across hundreds of online services. Its User-Agent (UA) rotation mechanism is lightweight, deterministic, and implemented consistently across all service modules. This article examines the implementation details based on the megadose/holehe source code.

Where Holehe Stores User-Agent Strings

The foundation of Holehe's rotation system lives in a single file:

  • holehe/localuseragent.py — Contains a JSON-encoded dictionary of realistic browser User-Agent strings loaded at import time.

When Python imports this module, it immediately parses the JSON blob into a dictionary named ua:


# holehe/localuseragent.py#L3

ua = json.loads('''{ … }''')

This ua dictionary organizes strings by browser family. The structure includes keys like "browsers" with nested categories for "chrome", "firefox", "safari", and others. Each category holds dozens of current and legacy UA strings that appear authentic to target services.

How Holehe Implements User-Agent Rotation

Holehe's rotation follows a data-driven, import-and-select pattern repeated across every service module:

  1. Import the pool — Each module pulls the ua dictionary via from holehe.localuseragent import * or from holehe.localuseragent import ua.

  2. Random selection — At request time, code calls random.choice() on the desired browser category.

  3. Header injection — The selected string populates the User-Agent header.

Because selection happens per-request rather than per-session, consecutive checks against the same service may present completely different browser fingerprints. This distribution helps evade simple rate-limiting rules that flag repetitive UA patterns.

Code Example: Basic UA Selection


# Selecting a random Chrome User-Agent

import random
from holehe.localuseragent import ua

def random_chrome_ua():
    return random.choice(ua["browsers"]["chrome"])

# Applied to an HTTP request

import requests

def fetch(url):
    headers = {"User-Agent": random_chrome_ua()}
    resp = requests.get(url, headers=headers)
    return resp.text

User-Agent Handling in Service Modules

Every service check under holehe/modules/ implements the same pattern. Here is how the Facebook module constructs its request headers:


# Simplified excerpt demonstrating the pattern

import random
from holehe.localuseragent import ua
import requests

def check_facebook(email):
    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "text/html,application/xhtml+xml",
    }
    r = requests.get(
        f"https://www.facebook.com/login.php?email={email}",
        headers=headers
    )
    # Response analysis logic follows...

    return r.status_code

The identical pattern appears in holehe/modules/social_media/twitter.py and holehe/modules/social_media/instagram.py. This consistency ensures that no module becomes a detection weak point due to static or missing UA rotation.

Key Design Characteristics

Aspect Implementation
Storage format JSON string embedded in Python module
Loading strategy Parse-on-import (eager, once per process)
Selection method random.choice() from browser-specific lists
Scope of rotation Per-request, not per-session or per-module
Browser coverage Chrome, Firefox, Safari, and others

The eager loading approach avoids repeated JSON parsing overhead. Once imported, the ua dictionary resides in memory for all subsequent requests across the application's lifetime.

Summary

  • Centralized data: All User-Agent strings live in holehe/localuseragent.py as a JSON-encoded dictionary.

  • Random per-request selection: Each HTTP request calls random.choice() on a browser category, producing varied fingerprints.

  • Consistent module pattern: Every service module under holehe/modules/ imports and uses the same ua pool, preventing inconsistent detection exposure.

  • Zero external dependencies: The mechanism relies only on Python's standard random and json modules.

Frequently Asked Questions

Does Holehe rotate User-Agents automatically for every request?

Yes. Each service module selects a fresh User-Agent string via random.choice() when building request headers. This happens independently for every HTTP request, not just once per session or target.

Can I customize the User-Agent pool in Holehe?

The pool is hardcoded in holehe/localuseragent.py as a JSON string. To modify available UAs, edit this file directly and restart the application. There is no runtime configuration interface for UA management.

Why does Holehe use random.choice instead of a round-robin approach?

Random selection better mimics organic traffic patterns and avoids predictable sequences that sophisticated rate-limiting systems might detect. The genuine browser strings in the pool ensure each selection remains plausible.

Which browsers does Holehe's User-Agent pool include?

According to the ua dictionary structure in localuseragent.py, the pool includes strings for Chrome, Firefox, Safari, and other major browsers. Most service modules default to the Chrome category, though the full dictionary is available to any module.

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 →