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.

16Yun Engineering TeamJul 12, 20267 min read

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:

FileResponsibility
config.pyConfiguration dataclass, presets (default/careful), config merging
mouse.py / mouse_async.pyBezier curve mouse movement, click simulation (sync/async)
keyboard.py / keyboard_async.pyPer-character typing, typo simulation, shift symbol handling (sync/async)
scroll.py / scroll_async.pyAccelerate-cruise-decelerate scrolling, overshoot and correction (sync/async)
actionability.py / actionability_async.pyActionability pre-checks: visible, stable, enabled, pointer-events pass
__init__.pyEntry point: patches page methods with humanized versions

Patching Mechanism

When you call launch(humanize=True), CloakBrowser's patching flow works as follows:

  1. Page-level: In patch_page() or patch_page_async(), replace page methods (click, fill, type, hover, check, etc.) with humanized implementations
  2. Original methods preserved: Stored in page._original for scenarios needing raw speed
  3. Locator class patching: Via _patch_locator_class_sync() and _patch_locator_class_async(), the Playwright Locator class is patched at the class level
  4. 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 replacements

HumanConfig: All Tunable Parameters

The HumanConfig dataclass in config.py centralizes all behavioral parameters. Here are the core parameters and their meanings:

Keyboard Parameters

ParameterDefaultDescription
typing_delay70msBase delay per character
typing_delay_spread40msRandom deviation range for delay
typing_pause_chance0.1 (10%)Chance of "thinking pause" mid-typing
typing_pause_range(400, 1000)msDuration of thinking pause
mistype_chance0.02 (2%)Probability of mistyping a character
mistype_delay_notice(100, 300)msDelay after noticing a typo
mistype_delay_correct(50, 150)msDelay after correcting a typo
field_switch_delay(800, 1500)msTime to switch between input fields
key_hold(15, 35)msKey hold duration

Mouse Parameters

ParameterDefaultDescription
mouse_steps_divisor8Controls movement step count
mouse_min_steps25Minimum movement steps
mouse_max_steps80Maximum movement steps
mouse_wobble_max1.5pxPath wobble amplitude
mouse_overshoot_chance0.15 (15%)Overshoot probability
mouse_overshoot_px(3, 6)pxOvershoot pixel range
mouse_burst_size(3, 5)Burst packet size
mouse_burst_pause(8, 18)msPause between bursts
click_aim_delay_input(60, 140)msAim delay for input elements
click_aim_delay_button(80, 200)msAim delay for buttons
click_hold_input(40, 100)msClick hold for input elements
click_hold_button(60, 150)msClick hold for buttons

Scroll Parameters

ParameterDefaultDescription
scroll_delta_base(80, 130)Base scroll increment
scroll_delta_variance0.2Increment random variance
scroll_pause_fast(30, 80)msFast scroll pause
scroll_pause_slow(80, 200)msSlow scroll pause
scroll_accel_steps(2, 3)Acceleration step count
scroll_decel_steps(2, 3)Deceleration step count
scroll_overshoot_chance0.1 (10%)Scroll overshoot probability
scroll_overshoot_px(50, 150)pxOvershoot pixel range
scroll_settle_delay(300, 600)msPost-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) / 2

The _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_amp

Wobble 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 = 0

This 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 ch

Typo 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:

  1. Stealth path (when CDP session is available): Uses Input.dispatchKeyEvent CDP command, producing isTrusted=true events with no evaluate stack trace
  2. Fallback path (no CDP session): Uses insertText + page.evaluate, potentially detectable via isTrusted=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 TestWithout humanizeWith humanize
deviceandbrowserinfo.com behavioralPartial pass24/24 all pass
DataDome behavioral analysisMay blockUsually passes
Mouse movement pattern detectionStraight line teleport (obvious bot)Bezier curves
Keyboard input pattern detectionInstant burstPer-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.