How Holehe Handles User-Agent Rotation: A Deep Dive into the Source Code
Holehe rotates User-Agent headers by randomly selecting from a curated catalogue of realistic browser strings stored in holehe/localuseragent.py, applying a different fingerprint on every HTTP request via random.choice().
Holehe is an open-source OSINT tool that checks for account registration across multiple platforms. Unlike tools that rely on static headers, the megadose/holehe repository implements dynamic User-Agent rotation to avoid detection and rate limiting during reconnaissance operations.
The User-Agent Catalogue Architecture
Storage Location and Data Structure
The tool maintains a JSON-encoded dictionary of authentic browser strings in holehe/localuseragent.py. This file exports a single ua object that organizes User-Agent strings by browser family.
The structure groups strings under the browsers key, partitioning them into categories such as:
chrome– Desktop Chrome browsersfirefox– Mozilla Firefox variantsios– Mobile Safari on iPhonesafari– macOS Safari browsers
This centralized storage ensures consistency across all modules while simplifying updates.
Import Pattern Across Modules
Every HTTP request module in the codebase follows an identical import pattern to access the rotation pool:
from holehe.localuseragent import ua
import random
This lightweight dependency chain avoids external libraries, keeping the footprint minimal while providing cryptographically adequate randomization through Python's standard library.
Per-Request Rotation Implementation
Random Selection Strategy
Instead of setting a User-Agent at session initialization, individual modules invoke random.choice() at the moment of request construction. This guarantees that successive calls to the same service present different client fingerprints.
The Facebook module (holehe/modules/social_media/facebook.py) demonstrates Chrome-family rotation:
# holehe/modules/social_media/facebook.py
from holehe.localuseragent import ua
import random
headers = {
"User-Agent": random.choice(ua["browsers"]["chrome"]),
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.0",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
}
The Instagram module (holehe/modules/social_media/instagram.py) utilizes Firefox strings for its requests:
# holehe/modules/social_media/instagram.py
headers = {
"User-Agent": random.choice(ua["browsers"]["firefox"]),
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1",
"Connection": "keep-alive",
}
Execution Timing and Performance
The random.choice() call executes per request, not per session. This design choice ensures maximum entropy in the header fingerprint but remains computationally trivial—Python's random module selects from the list in O(1) time with negligible overhead.
Extending the Browser Pool
Adding support for new browser families requires only modifying the JSON structure in holehe/localuseragent.py. For example, to add Microsoft Edge support:
# In holehe/localuseragent.py
{
"browsers": {
"chrome": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
# ... existing Chrome strings
],
"firefox": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0)...",
# ... existing Firefox strings
],
"edge": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.0 Edg/120.0.0.0"
]
}
}
Once defined, any module can immediately utilize the new category:
headers = {
"User-Agent": random.choice(ua["browsers"]["edge"]),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
This centralized architecture eliminates the need to patch individual modules when updating User-Agent strings.
Summary
- Holehe stores its User-Agent catalogue in
holehe/localuseragent.pyas a JSON-structured dictionary accessed via theuaobject. - Randomization occurs at request time through
random.choice(ua["browsers"][family]), ensuring per-request rotation rather than per-session consistency. - Multiple modules like
facebook.pyandinstagram.pyimport the sameuadictionary but select from different browser families (Chrome vs. Firefox) based on service requirements. - Extending the pool requires only editing the JSON in
localuseragent.py, automatically propagating new strings to all modules without code changes.
Frequently Asked Questions
Does Holehe rotate User-Agents automatically for every request?
Yes. Each HTTP request module calls random.choice() immediately before sending the request, selecting a fresh User-Agent string from the appropriate browser family. This provides automatic rotation without requiring configuration flags or session management.
Where does Holehe store its User-Agent strings?
The complete catalogue resides in holehe/localuseragent.py, which exports a ua dictionary containing browser strings organized by family (Chrome, Firefox, iOS, Safari). This single-file architecture simplifies maintenance and ensures consistency across all OSINT modules.
Can I add custom User-Agent strings to Holehe?
Yes. Modify the JSON object in holehe/localuseragent.py to include additional entries under existing browser families or create new categories (e.g., "edge" or "android"). All modules importing ua will immediately have access to the expanded rotation pool without requiring individual updates.
Why does Holehe use random.choice instead of a sequential rotation?
The random.choice() method provides stochastic distribution that mimics organic traffic patterns better than predictable sequential cycling. Since the selection executes in constant time O(1), it introduces no measurable latency while maximizing fingerprint entropy to evade rate limiting and detection systems.
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 →