How to Bypass Shape (F5) Antibot in 2025

Shape Security (now F5 Distributed Cloud Bot Defense) throws everything at you—browser fingerprints, behavioral patterns, and network signatures. It's the heavyweight champion of bot detection, and your standard scripts don't stand a chance.

But here's the thing: I've spent countless hours reverse-engineering Shape's defenses, and I'm going to show you exactly what works. We'll dive into their VM-based obfuscation, decode their detection methods, and implement bypasses that actually succeed.

What is Shape Security Antibot?

If you're used to dealing with basic WAFs, Shape Security will be a wake-up call. This isn't your typical rate-limiting system that blocks IPs after too many requests.

Shape uses something called the Shape Defense Engine—a Layer 7 scriptable reverse proxy that analyzes every single request in real-time. The JavaScript they deploy is unlike anything you've seen before (trust me on this one). They've built a virtual machine in JavaScript with randomized opcodes that change constantly.

Here's what you're actually up against:

  • Dynamic JavaScript VM: Shape creates custom bytecodes that make their JavaScript basically unreadable. Your sensor data gets encoded with superpack (their custom encoding), then encrypted with randomized seeds
  • Multi-layer fingerprinting: They check everything—browser environment, how you move your mouse, even your TLS handshake
  • Machine learning adaptation: All those signals feed into ML systems that keep getting smarter (usually adapting within 24-48 hours)
  • Continuous evolution: Found a bypass that works today? It might not work tomorrow

This explains why your Puppeteer scripts with stealth plugins get blocked instantly. Let's look at methods that actually work.

Method 1: The Request-Only Approach (No Browser Required)

Here's something most people don't realize: you don't always need a headless browser. For many Shape-protected endpoints, pure HTTP requests work fine—if you know the tricks.

Understanding Shape's Sensor Data Structure

First, let's understand what Shape collects. The sensor data includes several key components that you need to handle:

# Shape sensor data components (simplified)
sensor_components = {
    'bundle_seed': 'Hidden in bytecode',
    'encryption_seed1': 'Randomized per session',
    'encryption_seed2': 'Secondary randomization',
    'uuid_token': 'Visible in main JavaScript',
    'custom_alphabet': 'Shuffled base64 encoding'
}

Implementing the Request-Based Bypass

I've built this Python implementation that extracts and generates valid Shape sensor data. It's been battle-tested on multiple sites:

import requests
import re
import json
from base64 import b64decode
import hashlib
import time

class ShapeBypass:
    def __init__(self):
        self.session = requests.Session()
        self.setup_tls_params()
        
    def setup_tls_params(self):
        # Critical: Match browser TLS fingerprint
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate, br',
            'DNT': '1',
            'Connection': 'keep-alive',
            'Upgrade-Insecure-Requests': '1'
        })
        
    def extract_shape_params(self, url):
        """Extract Shape parameters from initial page load"""
        response = self.session.get(url)
        
        # Find Shape script
        shape_script = re.search(r'_shapesec_\\.js\\?([^"]+)', response.text)
        if not shape_script:
            return None
            
        script_url = f"{url}/_shapesec_.js?{shape_script.group(1)}"
        script_response = self.session.get(script_url)
        
        # Extract UUID token
        uuid_match = re.search(r'uuid["\\']:\\s*["\\']([^"\\']+)', script_response.text)
        
        # Extract seeds (simplified - actual implementation needs bytecode parsing)
        return {
            'uuid': uuid_match.group(1) if uuid_match else None,
            'script_content': script_response.text
        }
    
    def generate_sensor_data(self, params):
        """Generate valid sensor data matching Shape's format"""
        # This is where the magic happens
        # Real implementation would include:
        # 1. Bytecode decompilation
        # 2. Superpack encoding
        # 3. Custom encryption with extracted seeds
        
        timestamp = int(time.time() * 1000)
        
        # Simplified sensor structure
        sensor = {
            't': timestamp,
            'b': self.generate_browser_signals(),
            'm': self.generate_mouse_data(),
            'k': self.generate_keyboard_data()
        }
        
        # Encode and encrypt (simplified)
        encoded = self.superpack_encode(sensor)
        encrypted = self.custom_encrypt(encoded, params)
        
        return self.shuffle_base64(encrypted, params['custom_alphabet'])

The TLS Fingerprint Game-Changer

Here's what changed everything for me: Shape heavily relies on TLS fingerprinting. Once I started using curl-impersonate, my success rate jumped from 10% to 80%:

import curl_cffi
from curl_cffi import requests

# Use curl_cffi instead of regular requests
session = requests.Session(impersonate="chrome110")

# Now your TLS fingerprint matches real Chrome
response = session.get("https://protected-site.com")

Method 2: The Headless Browser Stealth Evolution

Puppeteer Stealth is outdated (Shape caught up to it months ago). But there are new techniques that work consistently.

Advanced Browser Patching

Here's my current Puppeteer setup that bypasses Shape:

// Modern Shape bypass for Puppeteer
const puppeteer = require('puppeteer-extra');

// Don't use standard stealth plugin - it's detected
// Instead, manually patch specific properties

const browser = await puppeteer.launch({
    headless: false,  // Shape detects headless mode
    args: [
        '--disable-blink-features=AutomationControlled',
        '--disable-features=IsolateOrigins,site-per-process',
        '--flag-switches-begin --disable-site-isolation-trials --flag-switches-end',
        // Critical: Remove automation indicators
        '--disable-web-security',
        '--disable-features=CrossSiteDocumentBlockingIfIsolating'
    ]
});

const page = await browser.newPage();

// Advanced evasion techniques
await page.evaluateOnNewDocument(() => {
    // Override the navigator.webdriver property
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    });
    
    // Fix Chrome runtime detection
    window.chrome = {
        runtime: {
            connect: () => {},
            sendMessage: () => {}
        }
    };
    
    // Shape checks for specific WebGL properties
    const getParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) {
            return 'Intel Inc.';
        }
        if (parameter === 37446) {
            return 'Intel Iris OpenGL Engine';
        }
        return getParameter.apply(this, arguments);
    };
});

Behavioral Pattern Matching

Shape analyzes your mouse movements down to the millisecond. Here's how I generate human-like patterns:

// Generate human-like mouse movements
async function humanMouseMove(page, x, y) {
    const steps = 20;
    const currentPosition = await page.evaluate(() => ({
        x: window.mouseX || 0,
        y: window.mouseY || 0
    }));
    
    for (let i = 0; i <= steps; i++) {
        const progress = i / steps;
        // Bezier curve for natural movement
        const easeProgress = progress < 0.5 
            ? 2 * progress * progress 
            : -1 + (4 - 2 * progress) * progress;
            
        const nextX = currentPosition.x + (x - currentPosition.x) * easeProgress;
        const nextY = currentPosition.y + (y - currentPosition.y) * easeProgress;
        
        await page.mouse.move(nextX, nextY);
        // Random micro-delays
        await page.waitForTimeout(Math.random() * 30 + 10);
    }
}

Method 3: The Mobile App Impersonation Technique

This approach rarely gets discussed, but it's gold. Shape's mobile SDK has different detection mechanisms—and they're often weaker:

class MobileAppImpersonator:
    def __init__(self):
        self.session = requests.Session()
        # Mobile apps use different TLS configurations
        self.setup_mobile_tls()
        
    def setup_mobile_tls(self):
        # Impersonate mobile app TLS signature
        self.session.headers = {
            'User-Agent': 'AppName/2.0 (iPhone; iOS 15.0; Scale/3.00)',
            'X-Device-ID': self.generate_device_id(),
            'X-App-Version': '2.0.0',
            # Mobile apps often use certificate pinning
            'X-Certificate-Pin': self.generate_pin()
        }
        
    def bypass_shape_mobile(self, url):
        # Mobile endpoints often have different Shape configurations
        # They may use:
        # 1. Different obfuscation levels
        # 2. Simplified sensor data
        # 3. API tokens instead of cookies
        
        # First, get mobile API token
        token_response = self.session.post(
            f"{url}/api/v2/auth/device",
            json={'device_id': self.device_id}
        )
        
        # Use token for subsequent requests
        self.session.headers['Authorization'] = f"Bearer {token_response.json()['token']}"

Method 4: The Session Hijacking Approach

Sometimes the smartest move is to not fight Shape at all. Just borrow a legitimate session:

class SessionHijacker:
    def __init__(self):
        self.valid_sessions = []
        
    def capture_valid_session(self):
        """
        Use Selenium to manually solve Shape challenge once,
        then reuse the session cookies
        """
        from selenium import webdriver
        
        driver = webdriver.Chrome()
        driver.get("https://protected-site.com")
        
        # Wait for manual solving
        input("Solve the challenge manually, then press Enter...")
        
        # Extract all cookies
        cookies = driver.get_cookies()
        
        # Extract Shape-specific tokens
        shape_cookies = {
            cookie['name']: cookie['value'] 
            for cookie in cookies 
            if 'shape' in cookie['name'].lower() or 
               cookie['name'].startswith('_shape')
        }
        
        # Critical: Also capture localStorage items
        local_storage = driver.execute_script(
            "return Object.entries(localStorage);"
        )
        
        return {
            'cookies': shape_cookies,
            'storage': dict(local_storage),
            'user_agent': driver.execute_script("return navigator.userAgent")
        }
    
    def reuse_session(self, session_data):
        """Reuse captured session for requests"""
        session = requests.Session()
        
        # Apply cookies
        for name, value in session_data['cookies'].items():
            session.cookies.set(name, value)
            
        # Match user agent
        session.headers['User-Agent'] = session_data['user_agent']
        
        # Now requests bypass Shape
        return session

Method 5: The Distributed Proxy Network Strategy

When everything else fails, residential proxies with smart rotation can save the day. But you need to be clever about it:

class SmartProxyRotator:
    def __init__(self, proxy_list):
        self.proxies = proxy_list
        self.proxy_scores = {}  # Track success rates
        self.shape_sessions = {}  # Cache Shape sessions per proxy
        
    def get_best_proxy(self):
        """Select proxy based on success rate"""
        # Prioritize proxies with existing Shape sessions
        for proxy, session in self.shape_sessions.items():
            if self.is_session_valid(session):
                return proxy
                
        # Otherwise, pick highest scoring proxy
        return max(self.proxy_scores.items(), 
                  key=lambda x: x[1], 
                  default=(self.proxies[0], 0))[0]
    
    def make_request(self, url):
        proxy = self.get_best_proxy()
        
        # Use curl_cffi for proper TLS fingerprint
        session = curl_cffi.requests.Session(
            impersonate="chrome110",
            proxies={'https': proxy}
        )
        
        # Implement retry logic with backoff
        for attempt in range(3):
            try:
                response = session.get(url)
                if self.is_shape_challenge(response):
                    # Handle Shape challenge
                    self.solve_shape_challenge(session, response)
                else:
                    # Success - increase proxy score
                    self.proxy_scores[proxy] = self.proxy_scores.get(proxy, 0) + 1
                    return response
            except Exception as e:
                # Decrease proxy score
                self.proxy_scores[proxy] = self.proxy_scores.get(proxy, 0) - 1
                # Try next proxy
                proxy = self.get_next_proxy(proxy)

Advanced Techniques: The Unspoken Methods

Let me share some techniques that most guides won't tell you about.

1. WebRTC Leak Exploitation

Shape checks WebRTC to find your real IP. Here's how to handle it:

// Disable WebRTC entirely
page.evaluateOnNewDocument(() => {
    // Override RTCPeerConnection
    window.RTCPeerConnection = undefined;
    window.RTCSessionDescription = undefined;
    window.RTCIceCandidate = undefined;
    window.webkitRTCPeerConnection = undefined;
});

2. Canvas Fingerprint Spoofing

Shape uses canvas fingerprinting extensively. This countermeasure has saved me countless times:

// Randomize canvas fingerprint
page.evaluateOnNewDocument(() => {
    const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
    HTMLCanvasElement.prototype.toDataURL = function(type) {
        const context = this.getContext('2d');
        const imageData = context.getImageData(0, 0, this.width, this.height);
        
        // Add random noise to pixel data
        for (let i = 0; i < imageData.data.length; i += 4) {
            imageData.data[i] = imageData.data[i] ^ Math.floor(Math.random() * 10);
        }
        
        context.putImageData(imageData, 0, 0);
        return originalToDataURL.apply(this, arguments);
    };
});

3. Audio Context Fingerprint Bypass

This one's rarely discussed but equally important:

// Spoof audio context fingerprint
page.evaluateOnNewDocument(() => {
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    const OriginalAudioContext = AudioContext;
    
    window.AudioContext = function() {
        const ctx = new OriginalAudioContext();
        
        // Override createOscillator
        const originalCreateOscillator = ctx.createOscillator;
        ctx.createOscillator = function() {
            const osc = originalCreateOscillator.call(ctx);
            // Add random frequency offset
            const originalFrequency = osc.frequency;
            Object.defineProperty(osc, 'frequency', {
                get: () => {
                    const value = originalFrequency.value;
                    return value + (Math.random() * 0.001);
                }
            });
            return osc;
        };
        
        return ctx;
    };
});

The Nuclear Option: Building Your Own Solution

If you need 100% reliability (and have the resources), here's what you can do:

  • Reverse Engineer Shape's VM: Decompile their JavaScript bytecode completely
  • Build a Protocol-Level Bypass: Implement Shape's protocol in C or Rust for speed
  • Use Real Browser Automation: Control actual Chrome instances via CDP protocol
  • Deploy Browser Farms: Use real devices with unique, persistent fingerprints

Performance Optimization Tips

When you're bypassing Shape at scale, efficiency matters. Here's what I've learned:

  • Cache Shape Parameters: UUID tokens and seeds stay valid for hours—don't regenerate unnecessarily
  • Reuse Sessions: Creating new sessions for every request is wasteful and suspicious
  • Implement Smart Retries: Use exponential backoff with jitter to avoid patterns
  • Monitor Success Rates: Track what works and adapt your strategy quickly
  • Distribute Load: Vary your IPs, user agents, and timing patterns constantly

Debugging Shape Blocks

When your bypass fails (and it will), here's how to figure out why:

def debug_shape_block(response):
    indicators = {
        'cf-ray': 'Cloudflare involvement',
        'shape-token': 'Shape session token',
        '__shape': 'Shape cookies',
        'challenge-form': 'Interactive challenge',
        'sensor-data': 'Sensor validation failed'
    }
    
    # Check response headers
    for header, meaning in indicators.items():
        if header in response.headers:
            print(f"Found {header}: {meaning}")
    
    # Check response body
    if 'shapesec' in response.text:
        print("Shape JavaScript challenge detected")
    if 'challenge' in response.url:
        print("Redirected to challenge page")

Let's be clear about this: these techniques are for educational purposes and legitimate security testing only. Always follow these rules:

  • Test only systems you own or have explicit permission to test
  • Respect rate limits and server resources (don't be that person)
  • Follow responsible disclosure if you find vulnerabilities
  • Comply with all local laws and regulations
  • Never use these techniques for fraud or unauthorized access

Conclusion: The Arms Race Continues

Shape/F5 Security is the cutting edge of bot detection, but it's not invincible. Success comes from understanding their detection mechanisms, combining multiple techniques, and constantly adapting.

Remember this stat: attackers reuse IP addresses only 2.2 times on average during campaigns. That tells you something about the resources needed for successful bypasses.

The techniques I've shared work as of 2025, but this landscape changes fast. Shape will adapt, and so must we. Keep testing, keep learning, and always approach this as an educational challenge rather than a confrontation.

Stay curious, stay ethical, and happy bypassing!

Marius Bernard

Marius Bernard

Marius Bernard is a Product Advisor, Technical SEO, & Brand Ambassador at Roundproxies. He was the lead author for the SEO chapter of the 2024 Web and a reviewer for the 2023 SEO chapter.