The Humanize Feature Deep Dive: How CloakBrowser Mimics Real User Behavior
An in-depth analysis of CloakBrowser's humanize feature source code, revealing the implementation of Bezier mouse curves, per-character keystroke timing, scroll physics, and actionability pre-checks.
Overview
Browser automation challenges extend beyond static fingerprint detection (Canvas, WebGL, navigator.webdriver) to behavioral analysis. Modern anti-bot systems like DataDome, Shape Security, and Akamai analyze not just "who you are" but "how you operate" -- mouse movement paths, typing rhythm, scroll patterns, click position distribution, and more. These behavioral signals can distinguish automation programs from real users even when browser fingerprints are perfect.
CloakBrowser's humanize=True feature is designed specifically to address this challenge. It is not a simple "random delay" solution, but a complete behavioral simulation layer that deeply patches Playwright/Puppeteer page methods. When humanize=True is enabled, all mouse, keyboard, and scroll interactions are automatically replaced with implementations that simulate human behavior.
This article analyzes the source code in the cloakbrowser/human/ directory at CloakHQ/CloakBrowser to explain the implementation details.
Architecture Overview
The Humanize module resides in cloakbrowser/human/ and contains the following core files:
| File | Responsibility |
|---|---|
config.py | Configuration dataclass, presets (default/careful), config merging |
mouse.py / mouse_async.py | Bezier curve mouse movement, click simulation (sync/async) |
keyboard.py / keyboard_async.py | Per-character typing, typo simulation, shift symbol handling (sync/async) |
scroll.py / scroll_async.py | Accelerate-cruise-decelerate scrolling, overshoot and correction (sync/async) |
actionability.py / actionability_async.py | Actionability pre-checks: visible, stable, enabled, pointer-events pass |
__init__.py | Entry point: patches page methods with humanized versions |
Patching Mechanism
When you call launch(humanize=True), CloakBrowser's patching flow works as follows:
- Page-level: In
patch_page()orpatch_page_async(), replace page methods (click,fill,type,hover,check, etc.) with humanized implementations - Original methods preserved: Stored in
page._originalfor scenarios needing raw speed - Locator class patching: Via
_patch_locator_class_sync()and_patch_locator_class_async(), the PlaywrightLocatorclass is patched at the class level - Frame support: Since v0.4.10, iframe interactions also use humanize behavior (fixing Issue #428)
# Simplified patching flow from __init__.py
page._original = originals # Save original methods
page._human_cfg = cfg # Save humanize config
page.click = _human_click # Replace with humanized version
page.fill = _human_fill
# ... more method replacementsHumanConfig: All Tunable Parameters
The HumanConfig dataclass in config.py centralizes all behavioral parameters. Here are the core parameters and their meanings:
Keyboard Parameters
| Parameter | Default | Description |
|---|---|---|
typing_delay | 70ms | Base delay per character |
typing_delay_spread | 40ms | Random deviation range for delay |
typing_pause_chance | 0.1 (10%) | Chance of "thinking pause" mid-typing |
typing_pause_range | (400, 1000)ms | Duration of thinking pause |
mistype_chance | 0.02 (2%) | Probability of mistyping a character |
mistype_delay_notice | (100, 300)ms | Delay after noticing a typo |
mistype_delay_correct | (50, 150)ms | Delay after correcting a typo |
field_switch_delay | (800, 1500)ms | Time to switch between input fields |
key_hold | (15, 35)ms | Key hold duration |
Mouse Parameters
| Parameter | Default | Description |
|---|---|---|
mouse_steps_divisor | 8 | Controls movement step count |
mouse_min_steps | 25 | Minimum movement steps |
mouse_max_steps | 80 | Maximum movement steps |
mouse_wobble_max | 1.5px | Path wobble amplitude |
mouse_overshoot_chance | 0.15 (15%) | Overshoot probability |
mouse_overshoot_px | (3, 6)px | Overshoot pixel range |
mouse_burst_size | (3, 5) | Burst packet size |
mouse_burst_pause | (8, 18)ms | Pause between bursts |
click_aim_delay_input | (60, 140)ms | Aim delay for input elements |
click_aim_delay_button | (80, 200)ms | Aim delay for buttons |
click_hold_input | (40, 100)ms | Click hold for input elements |
click_hold_button | (60, 150)ms | Click hold for buttons |
Scroll Parameters
| Parameter | Default | Description |
|---|---|---|
scroll_delta_base | (80, 130) | Base scroll increment |
scroll_delta_variance | 0.2 | Increment random variance |
scroll_pause_fast | (30, 80)ms | Fast scroll pause |
scroll_pause_slow | (80, 200)ms | Slow scroll pause |
scroll_accel_steps | (2, 3) | Acceleration step count |
scroll_decel_steps | (2, 3) | Deceleration step count |
scroll_overshoot_chance | 0.1 (10%) | Scroll overshoot probability |
scroll_overshoot_px | (50, 150)px | Overshoot pixel range |
scroll_settle_delay | (300, 600)ms | Post-scroll settle delay |
Preset Comparison: default vs careful
# Key differences in the careful preset (from _careful_config())
careful = HumanConfig(
typing_delay=100, # Slower typing
typing_delay_spread=50,
typing_pause_chance=0.15, # More thinking pauses
mouse_overshoot_chance=0.10,
click_aim_delay_input=(80, 180), # Longer aim time
click_aim_delay_button=(120, 280),
scroll_pause_fast=(100, 200), # Slower scrolling
idle_between_actions=True, # Idle micro-movements between actions
idle_between_duration=(0.4, 1.0), # Idle duration (seconds)
)The careful preset philosophy: slower, more deliberate, more thoughtful pauses. Suitable for high-security targets like DataDome-protected sites.
Bezier Curve Mouse Movement
The mouse humanize implementation lives in mouse.py's _bezier() and human_move() functions.
Cubic Bezier Curve
def _bezier(p0: Point, p1: Point, p2: Point, p3: Point, t: float) -> Point:
"""Compute a point on a cubic Bezier curve."""
u = 1 - t
uu = u * u
uuu = uu * u
tt = t * t
ttt = tt * t
return Point(
uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x,
uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.y,
)CloakBrowser uses cubic Bezier curves to simulate mouse paths, with two control points (p1, p2):
- Start point (p0): Current mouse position
- End point (p3): Target element position
- Control points (p1, p2): Generated randomly in
_random_control_points()based on the direction from start to end, introducing curvature and directional bias
Control Point Generation
def _random_control_points(start: Point, end: Point) -> Tuple[Point, Point]:
dx = end.x - start.x
dy = end.y - start.y
dist = math.hypot(dx, dy) or 1
px = -dy / dist # Perpendicular unit vector
py = dx / dist
bias1 = rand(-0.3, 0.3) * dist # Random offset
bias2 = rand(-0.3, 0.3) * dist
return (
Point(start.x + dx * 0.25 + px * bias1, start.y + dy * 0.25 + py * bias1),
Point(start.x + dx * 0.75 + px * bias2, start.y + dy * 0.75 + py * bias2),
)The elegance of this approach:
- Control points are offset at 25% and 75% along the path
- Offset direction is perpendicular to the path direction (
px, py), producing natural curves - Offset amount is random (-30% to +30%), ensuring different paths each time
Easing Function
def _ease_in_out(t: float) -> float:
"""Cubic ease-in-out function."""
if t < 0.5:
return 4 * t * t * t
return 1 - pow(-2 * t + 2, 3) / 2The _ease_in_out() function makes mouse movement slower at start and end points, faster in the middle -- matching the acceleration pattern of human hand movement.
Wobble and Overshoot
# Add random wobble to path points
wobble_amp = math.sin(math.pi * progress) * cfg.mouse_wobble_max
wx = pt.x + (random.random() - 0.5) * 2 * wobble_amp
wy = pt.y + (random.random() - 0.5) * 2 * wobble_ampWobble amplitude is largest in the middle of the path (sin(pi * progress)), simulating the instability of human hand movement.
# Overshoot: 15% chance of slightly overshooting then correcting
if random.random() < cfg.mouse_overshoot_chance:
overshoot_dist = rand_range(cfg.mouse_overshoot_px)
angle = math.atan2(end_y - start_y, end_x - start_x)
raw.move(round(end_x + math.cos(angle) * overshoot_dist),
round(end_y + math.sin(angle) * overshoot_dist))
sleep_ms(rand(30, 70))
raw.move(round(end_x + (random.random() - 0.5) * 4),
round(end_y + (random.random() - 0.5) * 4))Overshoot simulates how humans often slightly overshoot a target then correct -- a common signal used by behavioral detection systems.
Burst Pauses
Mouse movement is not continuously smooth but divided into "burst packets":
burst_counter += 1
if burst_counter >= burst_size and i < steps:
sleep_ms(rand_range(cfg.mouse_burst_pause))
burst_counter = 0This simulates micro-pauses in human mouse movement -- human motor control naturally consists of a series of rapid micro-movements.
Per-Character Keyboard Simulation
Keyboard humanize is implemented in keyboard.py's human_type() function.
Character-by-Character Timing
def human_type(page, raw, text, cfg, cdp_session=None):
for i, ch in enumerate(text):
# Non-ASCII characters (Cyrillic, CJK, emoji) -- use insertText
if not ch.isascii():
sleep_ms(rand_range(cfg.key_hold))
raw.insert_text(ch)
if i < len(text) - 1:
_inter_char_delay(cfg)
continue
# Mistype simulation
if random.random() < cfg.mistype_chance and ch.isalnum():
wrong = _get_nearby_key(ch)
_type_normal_char(raw, wrong, cfg)
sleep_ms(rand_range(cfg.mistype_delay_notice))
raw.down("Backspace")
sleep_ms(rand_range(cfg.key_hold))
raw.up("Backspace")
sleep_ms(rand_range(cfg.mistype_delay_correct))
# Handle uppercase letters
if ch.isupper() and ch.isalpha():
_type_shifted_char(page, raw, ch, cfg)
# Handle shift symbols
elif ch in SHIFT_SYMBOLS:
_type_shift_symbol(page, raw, ch, cfg, cdp_session)
else:
_type_normal_char(raw, ch, cfg)
if i < len(text) - 1:
_inter_char_delay(cfg)Inter-Character Delay
def _inter_char_delay(cfg):
"""Simulate variations in typing rhythm."""
base = cfg.typing_delay
spread = cfg.typing_delay_spread
delay = base + random.gauss(0, spread) # Gaussian distribution delay
sleep_ms(max(10, delay))
# Thinking pause: 10% chance of pausing mid-typing
if random.random() < cfg.typing_pause_chance:
sleep_ms(rand_range(cfg.typing_pause_range))Typing rhythm uses a Gaussian distribution (normal distribution) for natural variation instead of fixed delays. This is a key behavioral feature distinguishing humans from machines.
Typos and Self-Correction
_neighbors = {
'a': 'sqwz', 'b': 'vghn', 'c': 'xdfv', 'd': 'sfecx',
# ... more keyboard adjacency mappings
}
def _get_nearby_key(ch: str) -> str:
"""Return a random character from an adjacent key (simulating a typo)."""
lower = ch.lower()
if lower in NEARBY_KEYS:
neighbors = NEARBY_KEYS[lower]
wrong = random.choice(neighbors)
return wrong.upper() if ch.isupper() else wrong
return chTypo simulation uses a QWERTY keyboard adjacency map, selecting keys next to the target character -- simulating real typo patterns.
CDP Stealth Path
For shift symbols (@, #, $, %, etc.), CloakBrowser provides two paths:
- Stealth path (when CDP session is available): Uses
Input.dispatchKeyEventCDP command, producingisTrusted=trueevents with noevaluatestack trace - Fallback path (no CDP session): Uses
insertText+page.evaluate, potentially detectable viaisTrusted=false
The stealth path is one of CloakBrowser's key differentiators from simple JS injection solutions.
Scroll Physics Simulation
Scroll humanize is implemented in scroll.py's human_scroll_into_view() function.
Three-Phase Scroll Model
CloakBrowser's scroll simulation uses a realistic physics model:
Accelerate → Cruise → Decelerate → Overshoot → Correct → Settle# Acceleration phase
if i < accel_steps:
delta = rand(80, 100)
pause = rand_range(cfg.scroll_pause_slow)
# Deceleration phase
elif i >= total_clicks - decel_steps:
delta = rand(60, 90)
pause = rand_range(cfg.scroll_pause_slow)
# Cruise phase
else:
delta = rand_range(cfg.scroll_delta_base)
pause = rand_range(cfg.scroll_pause_fast)- Acceleration (2-3 steps): Slow start, smaller increments
- Cruise: Full-speed scrolling, larger increments
- Deceleration (2-3 steps): Slow down when approaching target
Smooth Wheel Events
def _smooth_wheel(raw: RawMouse, delta: int, cfg: HumanConfig) -> None:
"""Break one logical scroll into a burst of small wheel events (simulating real inertia)."""
abs_d = abs(delta)
sign = 1 if delta > 0 else -1
sent = 0
while sent < abs_d:
step_size = rand(20, 40)
chunk = min(step_size, abs_d - sent)
raw.wheel(0, round(chunk) * sign)
sent += chunk
sleep_ms(rand(8, 20))Each logical scroll is broken into multiple small wheel events (20-40px per step), with 8-20ms pauses between steps -- simulating the physical inertia of a mouse wheel.
Overshoot and Correction
# Overshoot: 10% chance of scrolling past target then correcting
if random.random() < cfg.scroll_overshoot_chance:
overshoot_px = round(rand_range(cfg.scroll_overshoot_px)) * direction
_smooth_wheel(raw, overshoot_px, cfg)
sleep_ms(rand_range(cfg.scroll_settle_delay))
corrections = rand_int_range((1, 2))
for _ in range(corrections):
corr_delta = round(rand(40, 80)) * -direction
_smooth_wheel(raw, corr_delta, cfg)
sleep_ms(rand(100, 250))Scroll overshoot simulates the common human behavior of scrolling past the target then scrolling back.
Actionability Pre-checks
In actionability.py, CloakBrowser implements Playwright-style actionability checks to ensure elements are interactive before humanize operations:
- Visible: Element is in the viewport and not hidden
- Stable: Element position is no longer changing (no animations)
- Enabled: Element is not disabled
- Pointer events pass: No other element intercepting the click above the target
These checks use CDP isolated execution contexts (_SyncIsolatedWorld / _AsyncIsolatedWorld) for DOM queries, avoiding evaluation traces in the page's main execution environment.
# Using CDP isolated world for DOM queries
class _SyncIsolatedWorld:
def evaluate(self, expression: str) -> Any:
# Creates isolated context via CDP's Page.createIsolatedWorld
# Results leave no "eval at evaluate :302:" traces in Error.stack
...Custom Configuration Examples
from cloakbrowser import launch_async
browser = await launch_async(
headless=False,
humanize=True,
human_config={
"mistype_chance": 0.05, # 5% typo rate
"typing_delay": 120, # Slower typing (120ms/char)
"typing_pause_chance": 0.15, # More thinking pauses
"idle_between_actions": True, # Idle micro-movements between actions
"idle_between_duration": [0.5, 1.5], # Idle duration (seconds)
"mouse_overshoot_chance": 0.20, # More overshoot
"scroll_overshoot_chance": 0.15, # More scroll overshoot
}
)Each method call can also temporarily override configuration:
page.fill("input#email", "user@example.com",
human_config={"typing_delay": 200, "mistype_chance": 0.1})Behavioral Detection Test Results
According to the CloakBrowser README, with humanize=True enabled, the deviceandbrowserinfo.com behavioral detection test passes all 24/24 signals, displaying "You are human!".
| Detection Test | Without humanize | With humanize |
|---|---|---|
| deviceandbrowserinfo.com behavioral | Partial pass | 24/24 all pass |
| DataDome behavioral analysis | May block | Usually passes |
| Mouse movement pattern detection | Straight line teleport (obvious bot) | Bezier curves |
| Keyboard input pattern detection | Instant burst | Per-character + variable delay |
Performance Considerations
Enabling humanize=True increases operation latency because each interaction adds:
- Bezier curve path calculation (~0.1-0.5ms)
- Multi-point mouse movement (25-80 steps depending on distance)
- Inter-character delay (70-150ms per character)
- Actionability checks (1-3 DOM queries)
For bulk data extraction operations that do not require behavioral simulation, bypass via the original methods:
# Original methods (no humanize) for fast operations
page._original.fill("input#quantity", "100")
page._original.click("button#submit")Conclusion
CloakBrowser's humanize feature is a carefully engineered behavioral simulation layer that achieves highly realistic human operation patterns in browser automation through Bezier curve mouse paths, per-character keyboard timing, three-phase scroll physics models, and actionability pre-checks. Its source code implementation in the cloakbrowser/human/ directory demonstrates deep understanding of hand kinematics, typing dynamics, and scroll physics.
Unlike simple random delay solutions, the CloakBrowser humanize module implements statistical distributions, random variations, and physics models at every level, making automated behavior statistically indistinguishable from real users. For production anti-scraping environments that need to bypass behavioral detection systems, humanize=True is an indispensable configuration option.
This article is based on CloakBrowser v0.4.10 (July 2026) source code. The relevant source code is available in the cloakbrowser/human/ directory.
Need an enterprise proxy plan?
We can tailor architecture to your target domains, concurrency, and reliability goals.