← Back to Blog

How to Avoid Automation Detection through Timing Attacks: Protection in Selenium and Puppeteer

Timing attacks are one of the most complex methods of detecting automation. We discuss how to protect against the analysis of execution time of actions in the browser.

πŸ“…January 10, 2026

Timing attacks are a method of bot detection based on analyzing the time it takes to perform actions in the browser. Modern anti-fraud systems from Facebook, Google, TikTok, and other platforms analyze not only what you do but also how quickly you do it. Too fast clicks, instant page loads, and lack of natural pauses all indicate automation. In this article, we will explore technical methods to protect against timing attacks for developers working with Selenium, Puppeteer, and anti-detect browsers.

What are timing attacks and how do they work

A timing attack is a method of detecting automation based on measuring the time intervals between user actions. Anti-fraud systems collect telemetry: how much time has passed from page load to the first click, how quickly the user scrolls, and whether there are pauses when filling out forms. This data is compared with behavioral models of real people.

The main timing metrics that protection systems analyze include:

  • Time to First Interaction (TTFI) β€” the time from page load to the first action (click, scroll, text input). Bots usually start acting instantly after the DOM loads, while humans take 0.5-3 seconds.
  • Click timing patterns β€” intervals between clicks. Automated scripts often click at the same frequency (e.g., exactly every 2 seconds), while humans do so chaotically.
  • Typing speed consistency β€” typing speed. Bots input text instantly or with a constant delay between characters, while humans do so at variable speeds with pauses.
  • Mouse movement velocity β€” cursor movement speed. Selenium by default instantly teleports the cursor to the desired point, while humans move the mouse with acceleration and deceleration.
  • Scroll behavior β€” patterns of scrolling the page. Bots often scroll exactly a fixed number of pixels, while humans do so unevenly, with stops.

Detection systems use machine learning to analyze these metrics. They build a behavior profile and calculate the probability that the user is a bot. If the timing patterns are too perfect or too fast, it raises a red flag.

Important: Timing attacks are particularly effective against mass automation. If you run 100 browsers with identical timing patterns, the anti-fraud system can easily detect them through statistical anomalies.

Methods of detecting automation through timing patterns

Modern anti-fraud systems use several layers of analysis of timing characteristics. Let's look at specific techniques used by Facebook, Google, Cloudflare, and other platforms.

1. Performance API Analysis

Browsers provide a Performance API that collects detailed telemetry of page loading. Anti-fraud systems analyze:

// Example of Performance API data
performance.timing = {
  navigationStart: 1234567890000,
  domLoading: 1234567890150,      // +150ms
  domInteractive: 1234567890300,  // +300ms
  domComplete: 1234567890500,     // +500ms
  loadEventEnd: 1234567890600     // +600ms
}

// Suspicious patterns for bots:
// - Too fast loading (domComplete < 200ms)
// - Perfectly even intervals between events
// - No delays in loading external resources

Headless browsers (especially older versions of Puppeteer and Selenium) often show abnormally fast values for navigationStart and domLoading because they do not load images, fonts, and other resources in the same way as a regular browser.

2. Event Timing Analysis

JavaScript trackers monitor the timestamps of all events (clicks, mouse movements, key presses) and analyze the patterns:

// Example of collecting event telemetry
const events = [];

document.addEventListener('click', (e) => {
  events.push({
    type: 'click',
    timestamp: performance.now(),
    x: e.clientX,
    y: e.clientY
  });
});

// Analyzing suspicious patterns:
// - Clicks occur exactly every N milliseconds
// - No micro-movements of the mouse before the click
// - The first click occurs instantly after the page loads

3. Keystroke Dynamics

When filling out forms, anti-fraud systems analyze keystroke dynamics β€” a unique biometric indicator for each person:

  • Dwell time β€” the time a key is held down (from keydown to keyup). For humans, it varies from 50 to 200 ms, while for bots, it is constant.
  • Flight time β€” the time between releasing one key and pressing the next. For humans, it ranges from 100 to 500 ms with variations, while for bots, it is a fixed delay.
  • Typing rhythm β€” the overall rhythm of typing. Humans pause at punctuation, correct mistakes, while bots do not.

Example of detection: If you use element.send_keys("text") in Selenium, all text is entered in 1-2 milliseconds β€” this instantly indicates automation.

Imitating human-like delays in code

The first level of protection against timing attacks is to add delays between actions. However, it is important not just to insert time.sleep(2), but to imitate natural behavior.

Basic imitation of delays in Python (Selenium)

import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By

def human_delay(min_sec=0.5, max_sec=2.0):
    """Random delay imitating a human"""
    delay = random.uniform(min_sec, max_sec)
    time.sleep(delay)

driver = webdriver.Chrome()
driver.get("https://example.com")

# Delay before the first action (human reads the page)
human_delay(1.5, 4.0)

# Click on the element
button = driver.find_element(By.ID, "submit-btn")
button.click()

# Delay before the next action
human_delay(0.8, 2.5)

Advanced imitation with normal distribution

Uniform distribution looks unnatural. Human delays follow a normal distribution with outliers:

import numpy as np

def realistic_delay(mean=1.5, std_dev=0.5, min_val=0.3, max_val=5.0):
    """
    Delay with normal distribution
    mean: average delay time
    std_dev: standard deviation
    min_val, max_val: boundaries (to avoid extreme values)
    """
    delay = np.random.normal(mean, std_dev)
    delay = max(min_val, min(max_val, delay))  # Limit the range
    time.sleep(delay)
    return delay

# Usage
realistic_delay(mean=2.0, std_dev=0.7)  # Average 2 sec, but with variations

Contextual delays

Different actions require different times. Create delay profiles for different scenarios:

class HumanBehavior:
    """Delay profiles for different types of actions"""
    
    @staticmethod
    def page_load_delay():
        """Delay after page load (reading content)"""
        return realistic_delay(mean=2.5, std_dev=1.0, min_val=1.0, max_val=6.0)
    
    @staticmethod
    def before_click():
        """Delay before clicking (searching for the element visually)"""
        return realistic_delay(mean=0.8, std_dev=0.3, min_val=0.3, max_val=2.0)
    
    @staticmethod
    def before_typing():
        """Delay before starting to type"""
        return realistic_delay(mean=1.2, std_dev=0.5, min_val=0.5, max_val=3.0)
    
    @staticmethod
    def between_form_fields():
        """Delay between form fields"""
        return realistic_delay(mean=0.6, std_dev=0.2, min_val=0.2, max_val=1.5)

# Usage in the script
driver.get("https://example.com/login")
HumanBehavior.page_load_delay()

username_field = driver.find_element(By.ID, "username")
HumanBehavior.before_typing()
# ... input username ...

HumanBehavior.between_form_fields()

password_field = driver.find_element(By.ID, "password")
HumanBehavior.before_typing()
# ... input password ...

Randomization of action execution time

Identical delays between actions are a statistical anomaly. If you run 100 instances of the script, and they all click the button exactly 2.5 seconds after loading β€” this is easily detectable. Randomization is needed at multiple levels.

1. Randomization of action order

Add variability to the sequence of actions. For example, before filling out a form, sometimes scroll the page, sometimes do not:

def fill_form_naturally(driver):
    # 30% chance to scroll the page before filling out
    if random.random() < 0.3:
        driver.execute_script("window.scrollBy(0, 200)")
        human_delay(0.5, 1.5)
    
    # 20% chance to click in a random place (imitating reading)
    if random.random() < 0.2:
        body = driver.find_element(By.TAG_NAME, "body")
        body.click()
        human_delay(0.3, 0.8)
    
    # Main action β€” filling out the form
    username_field = driver.find_element(By.ID, "username")
    type_like_human(username_field, "myusername")

2. Variable typing speed

Instead of instant text input, imitate character-by-character typing with variable speed:

def type_like_human(element, text):
    """Input text imitating human typing speed"""
    for char in text:
        element.send_keys(char)
        
        # Basic delay between characters
        base_delay = random.uniform(0.05, 0.15)
        
        # Additional pauses on spaces and punctuation
        if char in [' ', '.', ',', '!', '?']:
            base_delay += random.uniform(0.1, 0.3)
        
        # Random "thoughtfulness" (5% chance of a long pause)
        if random.random() < 0.05:
            base_delay += random.uniform(0.5, 1.5)
        
        time.sleep(base_delay)
    
    # Sometimes make a typo and correct it (10% chance)
    if random.random() < 0.1:
        time.sleep(random.uniform(0.2, 0.5))
        element.send_keys(Keys.BACKSPACE)
        time.sleep(random.uniform(0.1, 0.3))
        element.send_keys(text[-1])  # Re-enter the last character

3. Puppeteer: slow text input

In Puppeteer, there is a built-in option delay for the type() method, but it needs to be randomized:

// Basic usage (NOT recommended β€” fixed delay)
await page.type('#username', 'myusername', { delay: 100 });

// Correct approach β€” randomization for each character
async function typeWithVariableSpeed(page, selector, text) {
  await page.click(selector);
  
  for (const char of text) {
    await page.keyboard.type(char);
    
    // Random delay from 50 to 150 ms
    let delay = Math.random() * 100 + 50;
    
    // Additional pause on spaces
    if (char === ' ') {
      delay += Math.random() * 200 + 100;
    }
    
    // Random long pauses (5% chance)
    if (Math.random() < 0.05) {
      delay += Math.random() * 1000 + 500;
    }
    
    await page.waitForTimeout(delay);
  }
}

// Usage
await typeWithVariableSpeed(page, '#username', 'myusername');

Natural mouse movement and scrolling speed

Selenium and Puppeteer do not move the mouse cursor by default β€” they instantly teleport it to the desired point and click. This is one of the most obvious signs of automation. To imitate human mouse movement, special libraries are needed.

The pyautogui library for smooth mouse movement

The pyautogui library allows you to move the cursor along a BΓ©zier curve with acceleration and deceleration:

import pyautogui
from selenium.webdriver.common.action_chains import ActionChains

def move_mouse_naturally(driver, element):
    """Smooth mouse movement to the element imitating a human"""
    # Get the coordinates of the element
    location = element.location
    size = element.size
    
    # Target point (center of the element + slight randomness)
    target_x = location['x'] + size['width'] / 2 + random.randint(-5, 5)
    target_y = location['y'] + size['height'] / 2 + random.randint(-5, 5)
    
    # Smooth movement with variable speed
    # duration β€” movement time (0.5-1.5 sec for realism)
    duration = random.uniform(0.5, 1.5)
    
    # tweening β€” acceleration function (easeInOutQuad imitates human movement)
    pyautogui.moveTo(target_x, target_y, duration=duration, tween=pyautogui.easeInOutQuad)
    
    # Small pause before clicking (humans do not click instantly)
    time.sleep(random.uniform(0.05, 0.15))
    
    # Click
    element.click()

Important: This method only works if Selenium controls a real browser window (not headless). For headless mode, mouse movement is useless as anti-fraud systems do not see the cursor.

Puppeteer: imitating mouse movement

In Puppeteer, you can use the ghost-cursor library for realistic cursor movement:

// Installation: npm install ghost-cursor
const { createCursor } = require("ghost-cursor");
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  const cursor = createCursor(page);
  
  await page.goto("https://example.com");
  
  // Smooth movement to the element and click
  const button = await page.$("#submit-btn");
  await cursor.click(button);  // The cursor moves along a BΓ©zier curve!
  
  // Alternatively β€” move to coordinates
  await cursor.move("#username");  // Just move the cursor without clicking
  await page.waitForTimeout(300);
  await cursor.click();  // Click at the current position
})();

Imitating scrolling

A person does not scroll the page exactly 500 pixels at a time. Scrolling should be uneven, with pauses and sometimes scrolling back:

def scroll_like_human(driver, target_position=None):
    """
    Imitating human scrolling
    target_position: target position in pixels (if None β€” scroll to the end)
    """
    current_position = driver.execute_script("return window.pageYOffset;")
    
    if target_position is None:
        # Scroll to the end of the page
        target_position = driver.execute_script("return document.body.scrollHeight;")
    
    while current_position < target_position:
        # Random scroll step (100-400 pixels)
        scroll_step = random.randint(100, 400)
        current_position += scroll_step
        
        # Scroll
        driver.execute_script(f"window.scrollTo(0, {current_position});")
        
        # Pause between scrolls (human reads content)
        time.sleep(random.uniform(0.3, 1.2))
        
        # Sometimes scroll back a bit (10% chance)
        if random.random() < 0.1:
            back_scroll = random.randint(50, 150)
            current_position -= back_scroll
            driver.execute_script(f"window.scrollTo(0, {current_position});")
            time.sleep(random.uniform(0.2, 0.6))

# Usage
scroll_like_human(driver, target_position=2000)  # Scroll to 2000px

Page load timing and AJAX requests

Anti-fraud systems analyze not only user actions but also page load characteristics. Headless browsers often load abnormally quickly because they do not load images, execute some scripts, or render CSS.

Problem: too fast loading

Compare typical Performance API values for a regular browser and headless:

Metric Regular Browser Headless (suspicious)
domContentLoaded 800-2000 ms 50-200 ms
loadEventEnd 2000-5000 ms 100-500 ms
Number of loaded resources 50-200 (images, CSS, JS) 5-20 (only critical)

Solution: forced loading delay

Add an artificial delay after the page loads to make the metrics look more natural:

def load_page_naturally(driver, url):
    """Loading the page imitating natural loading time"""
    start_time = time.time()
    driver.get(url)
    
    # Wait for the DOM to fully load
    WebDriverWait(driver, 10).until(
        lambda d: d.execute_script("return document.readyState") == "complete"
    )
    
    # Calculate the actual loading time
    actual_load_time = time.time() - start_time
    
    # If loading was too fast (< 1 sec), add a delay
    if actual_load_time < 1.0:
        additional_delay = random.uniform(1.0, 2.5) - actual_load_time
        time.sleep(additional_delay)
    
    # Additional delay for "reading the page"
    time.sleep(random.uniform(0.5, 2.0))

# Usage
load_page_naturally(driver, "https://example.com")

Waiting for AJAX requests

Modern websites load content asynchronously via AJAX. If your script starts acting before all AJAX requests are completed, it looks suspicious. Use explicit waits:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def wait_for_ajax(driver, timeout=10):
    """Wait for all AJAX requests to complete (jQuery)"""
    WebDriverWait(driver, timeout).until(
        lambda d: d.execute_script("return jQuery.active == 0")
    )

# For sites without jQuery β€” wait for a specific element
def wait_for_dynamic_content(driver, selector, timeout=10):
    """Wait for the dynamically loaded element to appear"""
    WebDriverWait(driver, timeout).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, selector))
    )
    # Additional delay after the element appears
    time.sleep(random.uniform(0.3, 0.8))

Configuring protection in anti-detect browsers

If you are working with arbitration, multi-accounting, or other tasks where anonymity is critical, use anti-detect browsers: Dolphin Anty, AdsPower, Multilogin, GoLogin. They have built-in mechanisms to protect against timing attacks, but they need to be configured correctly.

Dolphin Anty: configuring Human Typing

Dolphin Anty has a "Human Typing" feature β€” automatic imitation of human typing. Configuration:

  • Open the browser profile β†’ "Automation" tab
  • Enable "Human Typing Emulation"
  • Configure the parameters:
    • Average typing speed: 150-250 characters/minute (realistic speed)
    • Variation: 30-50% (speed variation between characters)
    • Pause on punctuation: enabled (pauses on punctuation marks)
    • Random typos: 2-5% (random typos with correction)

After this, any text input through the Dolphin API will automatically imitate a human.

AdsPower: configuring Mouse Movement

AdsPower allows you to record the mouse movement patterns of a real user and reproduce them:

  1. Open the profile β†’ "Advanced Settings" β†’ "Mouse Behavior"
  2. Select the "Record Real User" mode:
    • Open the browser in normal mode
    • Perform typical actions (clicks, scrolling, mouse movements)
    • AdsPower will record the movement trajectories
  3. When automating through the AdsPower API, it will reproduce the recorded patterns with variations

Multilogin: Canvas Noise and WebGL Timing

Multilogin adds noise to Canvas and WebGL fingerprints, which affects timing attacks related to rendering:

  • Profile β†’ "Fingerprint settings" β†’ "Canvas"
  • Enable "Canvas Noise" (adds micro-delays to Canvas rendering)
  • Enable "WebGL Metadata Masking" (masks GPU characteristics affecting rendering speed)

This protects against detection through analysis of the execution time of Canvas.toDataURL() and WebGL operations.

Recommendation: If you are working with multi-accounting on Facebook, Instagram, or TikTok, be sure to use anti-detect browsers in conjunction with high-quality residential proxies β€” this minimizes the risk of chain bans and detection by IP.

Advanced techniques to bypass timing detection

For particularly secure platforms (Google, Facebook, banking sites), basic methods may not be sufficient. Let's consider advanced techniques.

1. Overriding Performance API

You can override Performance API methods to return realistic values instead of real ones:

// Script injection to override performance.now()
const script = `
  (function() {
    const originalNow = performance.now.bind(performance);
    let offset = 0;
    let lastValue = 0;
    
    performance.now = function() {
      const realValue = originalNow();
      // Add random noise to the time
      const noise = Math.random() * 2 - 1; // from -1 to +1 ms
      let fakeValue = realValue + offset + noise;
      
      // Ensure monotonicity (time does not go backward)
      if (fakeValue <= lastValue) {
        fakeValue = lastValue + 0.1;
      }
      
      lastValue = fakeValue;
      return fakeValue;
    };
  })();
`;

// Puppeteer: injection when creating the page
await page.evaluateOnNewDocument(script);

Attention: This method may be detected through native method integrity checks. Use it only on sites without strong protection.

2. Throttling CPU and Network

The Chrome DevTools Protocol allows you to artificially slow down the CPU and network to make loading metrics look more natural:

// Puppeteer: slowing down CPU by 2 times
const client = await page.target().createCDPSession();
await client.send('Emulation.setCPUThrottlingRate', { rate: 2 });

// Slowing down the network (emulating 3G)
await page.emulateNetworkConditions({
  offline: false,
  downloadThroughput: 1.5 * 1024 * 1024 / 8, // 1.5 Mbps
  uploadThroughput: 750 * 1024 / 8,          // 750 Kbps
  latency: 40                                 // 40ms delay
});

This will increase the loading time of pages and the execution of JavaScript, making the profile more similar to that of a real user with average internet speed.

3. Imitating background activity

Real users do not stay on one tab β€” they switch between tabs, minimize windows, and get distracted. Imitate this:

async function simulateTabSwitch(page) {
  // Emulate switching to another tab (Page Visibility API)
  await page.evaluate(() => {
    Object.defineProperty(document, 'hidden', {
      get: () => true,
      configurable: true
    });
    
    document.dispatchEvent(new Event('visibilitychange'));
  });
  
  // Pause (user looks at another tab)
  await page.waitForTimeout(Math.random() * 3000 + 2000);
  
  // Return to the tab
  await page.evaluate(() => {
    Object.defineProperty(document, 'hidden', {
      get: () => false,
      configurable: true
    });
    
    document.dispatchEvent(new Event('visibilitychange'));
  });
}

// Usage: randomly "get distracted" while working
if (Math.random() < 0.15) {  // 15% chance
  await simulateTabSwitch(page);
}

4. Using real User Timing marks

Some sites create their own timestamps through the User Timing API. Add realistic marks:

// Creating realistic timing marks
await page.evaluate(() => {
  // Imitating "thinking" before action
  performance.mark('user-started-reading');
  
  setTimeout(() => {
    performance.mark('user-found-button');
    performance.measure('reading-time', 'user-started-reading', 'user-found-button');
  }, Math.random() * 2000 + 1000);
});

5. Proxy rotation to reduce statistical correlation

Even if each instance of your script has randomized delays, anti-fraud systems can detect correlation if all requests come from one IP. Use proxy rotation:

  • For scraping and mass tasks: data center proxies with automatic rotation every 5-10 minutes
  • For working with social media and advertising: residential proxies with session binding (sticky sessions)
  • For mobile platforms (Instagram, TikTok): mobile proxies with timer or on-demand rotation

IP rotation reduces the likelihood that an anti-fraud system can gather enough data for statistical analysis of your timing patterns.

Conclusion

Timing attacks are a complex method of automation detection that requires a comprehensive approach to protection. Key takeaways:

  • Do not use fixed delays β€” always randomize the time between actions using normal distribution
  • Imitate contextual delays β€” different actions require different times (reading a page β‰  clicking a button)
  • Add variability to the sequence of actions β€” random scrolls, clicks, tab switches
  • Use smooth mouse movement β€” libraries like ghost-cursor (Puppeteer) or pyautogui (Selenium)
  • Imitate natural typing speed β€” character-by-character input with pauses on punctuation
  • Configure anti-detect browsers β€” Dolphin Anty, AdsPower, and Multilogin have built-in mechanisms to protect against timing attacks
  • Combine with quality proxies β€” IP rotation reduces the possibility of statistical analysis

Remember: there is no perfect protection. Anti-fraud systems constantly evolve, adding new detection methods. Your task is to make the script's behavior as close to that of a real user as possible and regularly update evasion techniques.

If you are working with browser automation for scraping, testing, or other tasks, we recommend using residential proxies β€” they provide the highest level of anonymity and minimize the risk of detection by IP address. For mobile platforms, mobile proxies are better suited, as they imitate real users of mobile operators.