Cases / #67 · 2026-10-23 · Medium
Akamai and HUMAN: what gets through? Four browsers, three networks, the vendors' own sites
akamai.com (Akamai Bot Manager) and humansecurity.com (HUMAN, formerly PerimeterX), each protected by its own vendor, loaded once per cell by headless Chromium, headful Chrome, headful Edge and Camoufox through your own proxy and sticky cdpfleet-resi exits (Tier 1 and Tier 4). The outcome per cell, the HTTP status, how many sensor posts the vendor's script sent, what Akamai's `_abck` cookie said before and after them, whether HUMAN issued `_px3`, and what a returning visitor with a profile sees.
Chromium Google Chrome Microsoft Edge Camoufox
Run on production on 2026-10-23: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go
The problem
Akamai Bot Manager and HUMAN both decide in two steps: the first response already depends on the request and the IP, and then a script in the page collects signals from the browser and posts them back (a "sensor"), which updates the cookie that later requests carry. A refusal shows as a 403 with an "Access Denied" page (Akamai) or a "Press & Hold" challenge (HUMAN). This case measures, on each vendor's own home page, which setup gets which outcome, and watches the cookies around the sensor posts. It is a dated measurement, not an evasion guide: nothing is clicked, held or solved, every cell is loaded once, and each vendor tunes its own site as it likes. For the signals a page can read, see What a page can read about your browser.
What we used, and why
| What | Why |
|---|---|
| Four setups | Headless Chromium, headful Google Chrome, headful Microsoft Edge (all Chromium) and Camoufox (based on Firefox source; not affiliated with Mozilla), launched with record: false. |
proxy: your own vs cdpfleet-resi-country-us-session-<id> | Your own proxy (PROXY_URL, rotating) against a sticky residential exit shared by the four setups. |
cdpfleet-resi-tier-4-… | The cheaper Tier 4 pool, with headful Chrome and Camoufox. |
| A fresh context per page | Every load is a first visit: no cookie from one page or setup reaches the next. |
page.on("request") | Counts the POST requests the vendor's script sends: to akamai.com's own host for Akamai, to px-cloud.net / px-cdn.net for HUMAN. |
context.cookies() | Akamai's _abck carries a status between its first two ~: -1 until a sensor post is accepted. Only that field is recorded, at load and after 10 s, never the cookie itself. For HUMAN, whether _px3 was issued. |
page.evaluate() once a second | Classifies the page: "Access Denied" = blocked, HUMAN's Press & Hold (#px-captcha) or an Akamai challenge = challenge, otherwise the real page. Nothing is clicked. |
profile | A returning visitor: the same profile and the same sticky exit in a second session, keeping the _abck the first visit was given. |
How it works
- For each network (your own proxy, then a sticky Tier 1 exit) and each of the four setups, launch one session and load both home pages once each, in a fresh context.
- Record the HTTP status and the cookies, watch the page once a second for 10 s while the vendor's script posts its sensor data, then record the outcome and the cookies again.
- Load both pages on a sticky Tier 4 exit with headful Chrome and Camoufox.
- With headful Chrome, load akamai.com with a new
profile, end the session, wait for the profile to be saved, and load it again with the same profile and exit. Delete the profile.
The code
The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:
CDPFLEET_API_KEY— your API key (dashboard → API keys)PROXY_URL— your proxy, e.g.http://user:[email protected]:8000
// npm install [email protected]
// env: CDPFLEET_API_KEY, PROXY_URL (one page load per page per setup and network; nothing is clicked or solved)
import { randomBytes } from 'node:crypto';
import { chromium, firefox } from 'playwright';
const KEY = process.env.CDPFLEET_API_KEY;
const RUN = randomBytes(3).toString('hex');
// Sticky residential exits: every setup in a comparison uses the same session name, so the same exit IP.
const NETWORKS = {
own: { label: 'own proxy (rotating)', proxy: process.env.PROXY_URL },
tier1: { label: 'cdpfleet-resi Tier 1, sticky US', proxy: `cdpfleet-resi-country-us-session-c67${RUN}-lifetime-30` },
tier4: { label: 'cdpfleet-resi Tier 4, sticky US', proxy: `cdpfleet-resi-tier-4-country-us-session-c67${RUN}-lifetime-30` },
};
const SETUPS = {
chromium: { setup: 'Chromium, headless', engine: 'chromium', type: chromium, headless: true },
chrome: { setup: 'Google Chrome, headful', engine: 'chrome', type: chromium, headless: false },
edge: { setup: 'Microsoft Edge, headful', engine: 'edge', type: chromium, headless: false },
camoufox: { setup: 'Camoufox, defaults', engine: 'camoufox', type: firefox, headless: true },
};
// Each vendor's own home page, which that vendor protects with its own product.
const PAGES = {
akamai: { url: 'https://www.akamai.com/', sensor: (u) => new URL(u).host === 'www.akamai.com' },
human: { url: 'https://www.humansecurity.com/', sensor: (u) => /(^|\.)px-(cloud|cdn)\.net$/.test(new URL(u).host) },
};
// What the page shows right now, in the lab's words. The same expression runs in every language.
const STATE = `(() => {
const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 400)).replace(/\\s+/g, ' ');
const blocked = /access denied|you don't have permission to access|request blocked|reference #\\d/i.test(t);
const challenge = !!document.querySelector('#px-captcha, #sec-if-cpt-container, #sec-cpt-if') || /press (&|and) hold|challenge validation/i.test(t);
return blocked ? 'blocked' : challenge ? 'challenge' : 'page';
})()`;
// 503 means the fleet is momentarily busy: wait Retry-After and try again.
async function launch(s, network, profile) {
for (let attempt = 1; ; attempt++) {
const res = await fetch(`https://starter.cdpfleet.com/${s.engine}/session`, {
method: 'POST',
headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
body: JSON.stringify({ proxy: NETWORKS[network].proxy, headless: s.headless, record: false, ...(profile ? { profile } : {}) }),
});
if (res.ok) return s.type.connect((await res.json()).wsUrl, { headers: { 'x-api-key': KEY } });
if (![429, 503].includes(res.status) || attempt === 5) throw new Error(`launch ${res.status} ${await res.text()}`);
await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 5) * 1000));
}
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// Akamai's _abck carries a status between its first two "~": -1 until a sensor post is accepted. Only that
// field is kept, never the cookie itself. HUMAN issues _px3 once its collector accepts the browser.
async function signals(context) {
const jar = await context.cookies();
const abck = jar.find((c) => c.name === '_abck')?.value;
return { abck: abck ? abck.split('~')[1] ?? '?' : null, px3: jar.some((c) => c.name === '_px3') };
}
// One load, then watch the page once a second for 10 s without touching it, so the vendor's script
// can post its sensor data; a network failure is retried once.
async function visit(context, key) {
const page = PAGES[key];
const tab = await context.newPage();
let posts = 0;
tab.on('request', (r) => { try { if (r.method() === 'POST' && page.sensor(r.url())) posts++; } catch {} });
let status = null;
try {
try {
status = (await tab.goto(page.url, { waitUntil: 'domcontentloaded', timeout: 45000 }))?.status() ?? null;
} catch {
status = (await tab.goto(page.url, { waitUntil: 'domcontentloaded', timeout: 45000 }))?.status() ?? null;
}
} catch {
await tab.close();
return { outcome: 'error' };
}
const atLoad = await signals(context);
let seen = new Set();
for (let i = 0; i < 10; i++) {
const s = await tab.evaluate(STATE).catch(() => null); // null while the page navigates
if (s) seen.add(s);
if (s === 'blocked') break;
await sleep(1000);
}
const last = await tab.evaluate(STATE).catch(() => null);
const outcome = seen.has('blocked') || last === 'blocked' ? 'blocked'
: last === 'challenge' ? 'challenge' : seen.has('challenge') ? 'cleared' : 'content';
const after = await signals(context);
await tab.close();
const cell = { outcome, status, sensor_posts: posts };
if (key === 'akamai') Object.assign(cell, { abck_at_load: atLoad.abck, abck_after: after.abck });
else cell.px3 = after.px3;
return cell;
}
// One session per setup and network; a fresh context per page so every load is a first visit.
async function row(setupKey, network, pages) {
const s = SETUPS[setupKey];
const browser = await launch(s, network);
const out = { setup: s.setup, network: NETWORKS[network].label };
try {
for (const key of pages) {
const context = await browser.newContext();
out[key] = await visit(context, key);
await context.close();
}
} finally {
await browser.close();
}
return out;
}
async function api(method, path) {
const res = await fetch(`https://cdpfleet.com${path}`, { method, headers: { 'x-api-key': KEY } });
return res.json().catch(() => ({}));
}
const profileInfo = async (name) => (await api('GET', '/v1/me/profiles')).profiles?.find((p) => p.name === name);
// A returning visitor: the same profile and the same sticky exit, two sessions a few seconds apart.
async function returning(setupKey, network, key) {
const name = `case67-${RUN}`;
const visits = [];
for (let i = 0; i < 2; i++) {
const browser = await launch(SETUPS[setupKey], network, name);
try {
visits.push(await visit(browser.contexts()[0], key)); // the profile's own context; a newContext() is not saved
} finally {
await browser.close();
}
// The node saves the profile after the browser exits: wait until it is free again.
for (let j = 0; j < 60 && (await profileInfo(name))?.in_use !== false; j++) await sleep(2000);
}
await api('DELETE', `/v1/me/profiles/${name}`);
return { setup: SETUPS[setupKey].setup, network: NETWORKS[network].label, page: key, first: visits[0], second: visits[1] };
}
const matrix = [];
for (const network of ['own', 'tier1']) {
for (const setupKey of ['chromium', 'chrome', 'edge', 'camoufox']) matrix.push(await row(setupKey, network, Object.keys(PAGES)));
}
const tier4 = [];
for (const setupKey of ['chrome', 'camoufox']) tier4.push(await row(setupKey, 'tier4', Object.keys(PAGES)));
const profile = await returning('chrome', 'tier1', 'akamai');
console.log(JSON.stringify({ measured_at: new Date().toISOString().slice(0, 10), matrix, tier4, profile }, null, 2));
# pip install playwright==1.60.0 requests
# env: CDPFLEET_API_KEY, PROXY_URL (one page load per page per setup and network; nothing is clicked or solved)
import json
import os
import re
import secrets
import time
from datetime import datetime, timezone
from urllib.parse import urlparse
import requests
from playwright.sync_api import sync_playwright
KEY = os.environ["CDPFLEET_API_KEY"]
RUN = secrets.token_hex(3)
# Sticky residential exits: every setup in a comparison uses the same session name, so the same exit IP.
NETWORKS = {
"own": {"label": "own proxy (rotating)", "proxy": os.environ["PROXY_URL"]},
"tier1": {"label": "cdpfleet-resi Tier 1, sticky US", "proxy": f"cdpfleet-resi-country-us-session-c67{RUN}-lifetime-30"},
"tier4": {"label": "cdpfleet-resi Tier 4, sticky US", "proxy": f"cdpfleet-resi-tier-4-country-us-session-c67{RUN}-lifetime-30"},
}
SETUPS = {
"chromium": {"setup": "Chromium, headless", "engine": "chromium", "type": "chromium", "headless": True},
"chrome": {"setup": "Google Chrome, headful", "engine": "chrome", "type": "chromium", "headless": False},
"edge": {"setup": "Microsoft Edge, headful", "engine": "edge", "type": "chromium", "headless": False},
"camoufox": {"setup": "Camoufox, defaults", "engine": "camoufox", "type": "firefox", "headless": True},
}
# Each vendor's own home page, which that vendor protects with its own product.
PAGES = {
"akamai": {"url": "https://www.akamai.com/", "sensor": lambda u: urlparse(u).hostname == "www.akamai.com"},
"human": {"url": "https://www.humansecurity.com/", "sensor": lambda u: bool(re.search(r"(^|\.)px-(cloud|cdn)\.net$", urlparse(u).hostname or ""))},
}
# What the page shows right now, in the lab's words. The same expression runs in every language.
STATE = r"""(() => {
const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 400)).replace(/\s+/g, ' ');
const blocked = /access denied|you don't have permission to access|request blocked|reference #\d/i.test(t);
const challenge = !!document.querySelector('#px-captcha, #sec-if-cpt-container, #sec-cpt-if') || /press (&|and) hold|challenge validation/i.test(t);
return blocked ? 'blocked' : challenge ? 'challenge' : 'page';
})()"""
# 503 means the fleet is momentarily busy: wait Retry-After and try again.
def launch(p, s, network, profile=None):
body = {"proxy": NETWORKS[network]["proxy"], "headless": s["headless"], "record": False}
if profile:
body["profile"] = profile
for attempt in range(1, 6):
res = requests.post(f"https://starter.cdpfleet.com/{s['engine']}/session", headers={"x-api-key": KEY}, json=body, timeout=120)
if res.ok:
return getattr(p, s["type"]).connect(res.json()["wsUrl"], headers={"x-api-key": KEY})
if res.status_code not in (429, 503) or attempt == 5:
raise RuntimeError(f"launch {res.status_code} {res.text}")
time.sleep(int(res.headers.get("retry-after") or 5))
# Akamai's _abck carries a status between its first two "~": -1 until a sensor post is accepted. Only that
# field is kept, never the cookie itself. HUMAN issues _px3 once its collector accepts the browser.
def signals(context):
jar = context.cookies()
abck = next((c["value"] for c in jar if c["name"] == "_abck"), None)
parts = abck.split("~") if abck else None
return {"abck": (parts[1] if len(parts) > 1 else "?") if parts else None, "px3": any(c["name"] == "_px3" for c in jar)}
def state(tab):
try:
return tab.evaluate(STATE)
except Exception:
return None # while the page navigates
# One load, then watch the page once a second for 10 s without touching it, so the vendor's script
# can post its sensor data; a network failure is retried once.
def visit(context, key):
page = PAGES[key]
tab = context.new_page()
posts = [0]
def on_request(r):
try:
if r.method == "POST" and page["sensor"](r.url):
posts[0] += 1
except Exception:
pass
tab.on("request", on_request)
status = None
try:
try:
res = tab.goto(page["url"], wait_until="domcontentloaded", timeout=45000)
except Exception:
res = tab.goto(page["url"], wait_until="domcontentloaded", timeout=45000)
status = res.status if res else None
except Exception:
tab.close()
return {"outcome": "error"}
at_load = signals(context)
seen = set()
for _ in range(10):
s = state(tab)
if s:
seen.add(s)
if s == "blocked":
break
time.sleep(1)
last = state(tab)
if "blocked" in seen or last == "blocked":
outcome = "blocked"
elif last == "challenge":
outcome = "challenge"
else:
outcome = "cleared" if "challenge" in seen else "content"
after = signals(context)
tab.close()
cell = {"outcome": outcome, "status": status, "sensor_posts": posts[0]}
if key == "akamai":
cell.update(abck_at_load=at_load["abck"], abck_after=after["abck"])
else:
cell["px3"] = after["px3"]
return cell
# One session per setup and network; a fresh context per page so every load is a first visit.
def row(p, setup_key, network, pages):
s = SETUPS[setup_key]
browser = launch(p, s, network)
out = {"setup": s["setup"], "network": NETWORKS[network]["label"]}
try:
for key in pages:
context = browser.new_context()
out[key] = visit(context, key)
context.close()
finally:
browser.close()
return out
def api(method, path):
try:
return requests.request(method, f"https://cdpfleet.com{path}", headers={"x-api-key": KEY}, timeout=60).json()
except ValueError:
return {}
def profile_info(name):
return next((x for x in api("GET", "/v1/me/profiles").get("profiles", []) if x["name"] == name), None)
# A returning visitor: the same profile and the same sticky exit, two sessions a few seconds apart.
def returning(p, setup_key, network, key):
name = f"case67-{RUN}"
visits = []
for _ in range(2):
browser = launch(p, SETUPS[setup_key], network, name)
try:
visits.append(visit(browser.contexts[0], key)) # the profile's own context; a new_context() is not saved
finally:
browser.close()
# The node saves the profile after the browser exits: wait until it is free again.
for _ in range(60):
info = profile_info(name)
if info and info["in_use"] is False:
break
time.sleep(2)
api("DELETE", f"/v1/me/profiles/{name}")
return {"setup": SETUPS[setup_key]["setup"], "network": NETWORKS[network]["label"], "page": key, "first": visits[0], "second": visits[1]}
with sync_playwright() as p:
matrix = [row(p, setup_key, network, list(PAGES)) for network in ("own", "tier1") for setup_key in ("chromium", "chrome", "edge", "camoufox")]
tier4 = [row(p, setup_key, "tier4", list(PAGES)) for setup_key in ("chrome", "camoufox")]
profile = returning(p, "chrome", "tier1", "akamai")
print(json.dumps({"measured_at": datetime.now(timezone.utc).strftime("%Y-%m-%d"), "matrix": matrix, "tier4": tier4, "profile": profile}, indent=2))
// Maven: com.microsoft.playwright:playwright:1.60.0, com.google.code.gson:gson:2.11.0
// Run with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1.
// env: CDPFLEET_API_KEY, PROXY_URL (one page load per page per setup and network; nothing is clicked or solved)
import com.google.gson.*;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.WaitUntilState;
import java.net.URI;
import java.net.http.*;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.regex.Pattern;
public class Main {
static final String KEY = System.getenv("CDPFLEET_API_KEY");
static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().serializeNulls().create();
static final HttpClient HTTP = HttpClient.newHttpClient();
static final String RUN = String.format("%06x", new SecureRandom().nextInt(1 << 24));
record Network(String label, String proxy) {}
record Setup(String name, String engine, String type, boolean headless) {}
record TestPage(String url, Predicate<String> sensor) {}
// Sticky residential exits: every setup in a comparison uses the same session name, so the same exit IP.
static final Map<String, Network> NETWORKS = Map.of(
"own", new Network("own proxy (rotating)", System.getenv("PROXY_URL")),
"tier1", new Network("cdpfleet-resi Tier 1, sticky US", "cdpfleet-resi-country-us-session-c67" + RUN + "-lifetime-30"),
"tier4", new Network("cdpfleet-resi Tier 4, sticky US", "cdpfleet-resi-tier-4-country-us-session-c67" + RUN + "-lifetime-30"));
static final Map<String, Setup> SETUPS = Map.of(
"chromium", new Setup("Chromium, headless", "chromium", "chromium", true),
"chrome", new Setup("Google Chrome, headful", "chrome", "chromium", false),
"edge", new Setup("Microsoft Edge, headful", "edge", "chromium", false),
"camoufox", new Setup("Camoufox, defaults", "camoufox", "firefox", true));
static final Pattern PX_HOST = Pattern.compile("(^|\\.)px-(cloud|cdn)\\.net$");
// Each vendor's own home page, which that vendor protects with its own product.
static final Map<String, TestPage> PAGES = new LinkedHashMap<>();
static {
PAGES.put("akamai", new TestPage("https://www.akamai.com/", h -> h.equals("www.akamai.com")));
PAGES.put("human", new TestPage("https://www.humansecurity.com/", h -> PX_HOST.matcher(h).find()));
}
// What the page shows right now, in the lab's words. The same expression runs in every language.
static final String STATE = """
(() => {
const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 400)).replace(/\\s+/g, ' ');
const blocked = /access denied|you don't have permission to access|request blocked|reference #\\d/i.test(t);
const challenge = !!document.querySelector('#px-captcha, #sec-if-cpt-container, #sec-cpt-if') || /press (&|and) hold|challenge validation/i.test(t);
return blocked ? 'blocked' : challenge ? 'challenge' : 'page';
})()""";
static Playwright playwright;
// 503 means the fleet is momentarily busy: wait Retry-After and try again.
static Browser launch(Setup s, String network, String profile) throws Exception {
JsonObject body = new JsonObject();
body.addProperty("proxy", NETWORKS.get(network).proxy());
body.addProperty("headless", s.headless());
body.addProperty("record", false);
if (profile != null) body.addProperty("profile", profile);
for (int attempt = 1; ; attempt++) {
HttpResponse<String> res = HTTP.send(HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/" + s.engine() + "/session"))
.header("x-api-key", KEY).header("content-type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString())).build(), HttpResponse.BodyHandlers.ofString());
if (res.statusCode() == 200) {
String wsUrl = JsonParser.parseString(res.body()).getAsJsonObject().get("wsUrl").getAsString();
BrowserType type = s.type().equals("firefox") ? playwright.firefox() : playwright.chromium();
return type.connect(wsUrl, new BrowserType.ConnectOptions().setHeaders(Map.of("x-api-key", KEY)));
}
if ((res.statusCode() != 429 && res.statusCode() != 503) || attempt == 5) throw new RuntimeException("launch " + res.statusCode() + " " + res.body());
Thread.sleep(Long.parseLong(res.headers().firstValue("retry-after").orElse("5")) * 1000);
}
}
// Akamai's _abck carries a status between its first two "~": -1 until a sensor post is accepted. Only that
// field is kept, never the cookie itself. HUMAN issues _px3 once its collector accepts the browser.
static String abck(BrowserContext context) {
return context.cookies().stream().filter(c -> c.name.equals("_abck")).findFirst()
.map(c -> { String[] parts = c.value.split("~", -1); return parts.length > 1 ? parts[1] : "?"; }).orElse(null);
}
static boolean px3(BrowserContext context) {
return context.cookies().stream().anyMatch(c -> c.name.equals("_px3"));
}
static String state(Page tab) {
try {
return (String) tab.evaluate(STATE);
} catch (PlaywrightException err) {
return null; // while the page navigates
}
}
// One load, then watch the page once a second for 10 s without touching it, so the vendor's script
// can post its sensor data; a network failure is retried once.
static JsonObject visit(BrowserContext context, String key) throws Exception {
TestPage page = PAGES.get(key);
Page tab = context.newPage();
AtomicInteger posts = new AtomicInteger();
tab.onRequest(r -> {
try {
String host = URI.create(r.url()).getHost();
if (r.method().equals("POST") && host != null && page.sensor().test(host)) posts.incrementAndGet();
} catch (IllegalArgumentException ignored) {
}
});
JsonObject cell = new JsonObject();
Page.NavigateOptions nav = new Page.NavigateOptions().setWaitUntil(WaitUntilState.DOMCONTENTLOADED).setTimeout(45000);
Response res;
try {
try {
res = tab.navigate(page.url(), nav);
} catch (PlaywrightException first) {
res = tab.navigate(page.url(), nav);
}
} catch (PlaywrightException err) {
tab.close();
cell.addProperty("outcome", "error");
return cell;
}
String atLoad = abck(context);
Set<String> seen = new HashSet<>();
for (int i = 0; i < 10; i++) {
String s = state(tab);
if (s != null) seen.add(s);
if ("blocked".equals(s)) break;
Thread.sleep(1000);
}
String last = state(tab);
String outcome = seen.contains("blocked") || "blocked".equals(last) ? "blocked"
: "challenge".equals(last) ? "challenge" : seen.contains("challenge") ? "cleared" : "content";
cell.addProperty("outcome", outcome);
if (res != null) cell.addProperty("status", res.status()); else cell.add("status", JsonNull.INSTANCE);
cell.addProperty("sensor_posts", posts.get());
if (key.equals("akamai")) {
cell.addProperty("abck_at_load", atLoad);
cell.addProperty("abck_after", abck(context));
} else {
cell.addProperty("px3", px3(context));
}
tab.close();
return cell;
}
// One session per setup and network; a fresh context per page so every load is a first visit.
static JsonObject row(String setupKey, String network, List<String> pages) throws Exception {
Setup s = SETUPS.get(setupKey);
Browser browser = launch(s, network, null);
JsonObject out = new JsonObject();
out.addProperty("setup", s.name());
out.addProperty("network", NETWORKS.get(network).label());
try {
for (String key : pages) {
BrowserContext context = browser.newContext();
out.add(key, visit(context, key));
context.close();
}
} finally {
browser.close();
}
return out;
}
static JsonObject api(String method, String path) throws Exception {
HttpResponse<String> res = HTTP.send(HttpRequest.newBuilder(URI.create("https://cdpfleet.com" + path))
.header("x-api-key", KEY).method(method, HttpRequest.BodyPublishers.noBody()).build(), HttpResponse.BodyHandlers.ofString());
try {
return JsonParser.parseString(res.body()).getAsJsonObject();
} catch (RuntimeException err) {
return new JsonObject();
}
}
static JsonObject profileInfo(String name) throws Exception {
JsonObject body = api("GET", "/v1/me/profiles");
if (!body.has("profiles")) return null;
for (JsonElement p : body.getAsJsonArray("profiles")) if (p.getAsJsonObject().get("name").getAsString().equals(name)) return p.getAsJsonObject();
return null;
}
// A returning visitor: the same profile and the same sticky exit, two sessions a few seconds apart.
static JsonObject returning(String setupKey, String network, String key) throws Exception {
String name = "case67-" + RUN;
List<JsonObject> visits = new ArrayList<>();
for (int i = 0; i < 2; i++) {
Browser browser = launch(SETUPS.get(setupKey), network, name);
try {
visits.add(visit(browser.contexts().get(0), key)); // the profile's own context; a newContext() is not saved
} finally {
browser.close();
}
// The node saves the profile after the browser exits: wait until it is free again.
for (int j = 0; j < 60; j++) {
JsonObject info = profileInfo(name);
if (info != null && !info.get("in_use").getAsBoolean()) break;
Thread.sleep(2000);
}
}
api("DELETE", "/v1/me/profiles/" + name);
JsonObject out = new JsonObject();
out.addProperty("setup", SETUPS.get(setupKey).name());
out.addProperty("network", NETWORKS.get(network).label());
out.addProperty("page", key);
out.add("first", visits.get(0));
out.add("second", visits.get(1));
return out;
}
public static void main(String[] args) throws Exception {
JsonObject result = new JsonObject();
try (Playwright pw = Playwright.create()) {
playwright = pw;
List<String> keys = new ArrayList<>(PAGES.keySet());
JsonArray matrix = new JsonArray();
for (String network : List.of("own", "tier1"))
for (String setupKey : List.of("chromium", "chrome", "edge", "camoufox")) matrix.add(row(setupKey, network, keys));
JsonArray tier4 = new JsonArray();
for (String setupKey : List.of("chrome", "camoufox")) tier4.add(row(setupKey, "tier4", keys));
JsonObject profile = returning("chrome", "tier1", "akamai");
result.addProperty("measured_at", Instant.now().toString().substring(0, 10));
result.add("matrix", matrix);
result.add("tier4", tier4);
result.add("profile", profile);
}
System.out.println(GSON.toJson(result));
}
}
// dotnet add package Microsoft.Playwright --version 1.60.0
// env: CDPFLEET_API_KEY, PROXY_URL (one page load per page per setup and network; nothing is clicked or solved)
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Playwright;
var key = Environment.GetEnvironmentVariable("CDPFLEET_API_KEY")!;
var run = Convert.ToHexString(RandomNumberGenerator.GetBytes(3)).ToLowerInvariant();
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", key);
using var playwright = await Playwright.CreateAsync();
// Sticky residential exits: every setup in a comparison uses the same session name, so the same exit IP.
var networks = new Dictionary<string, (string Label, string? Proxy)>
{
["own"] = ("own proxy (rotating)", Environment.GetEnvironmentVariable("PROXY_URL")),
["tier1"] = ("cdpfleet-resi Tier 1, sticky US", $"cdpfleet-resi-country-us-session-c67{run}-lifetime-30"),
["tier4"] = ("cdpfleet-resi Tier 4, sticky US", $"cdpfleet-resi-tier-4-country-us-session-c67{run}-lifetime-30"),
};
var setups = new Dictionary<string, (string Name, string Engine, IBrowserType Type, bool Headless)>
{
["chromium"] = ("Chromium, headless", "chromium", playwright.Chromium, true),
["chrome"] = ("Google Chrome, headful", "chrome", playwright.Chromium, false),
["edge"] = ("Microsoft Edge, headful", "edge", playwright.Chromium, false),
["camoufox"] = ("Camoufox, defaults", "camoufox", playwright.Firefox, true),
};
// Each vendor's own home page, which that vendor protects with its own product.
var pxHost = new Regex(@"(^|\.)px-(cloud|cdn)\.net$");
var pages = new (string Key, string Url, Func<string, bool> Sensor)[]
{
("akamai", "https://www.akamai.com/", h => h == "www.akamai.com"),
("human", "https://www.humansecurity.com/", h => pxHost.IsMatch(h)),
};
// What the page shows right now, in the lab's words. The same expression runs in every language.
const string State = """
(() => {
const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 400)).replace(/\s+/g, ' ');
const blocked = /access denied|you don't have permission to access|request blocked|reference #\d/i.test(t);
const challenge = !!document.querySelector('#px-captcha, #sec-if-cpt-container, #sec-cpt-if') || /press (&|and) hold|challenge validation/i.test(t);
return blocked ? 'blocked' : challenge ? 'challenge' : 'page';
})()
""";
// 503 means the fleet is momentarily busy: wait Retry-After and try again.
async Task<IBrowser> Launch(string setupKey, string network, string? profile = null)
{
var s = setups[setupKey];
var body = new JsonObject { ["proxy"] = networks[network].Proxy, ["headless"] = s.Headless, ["record"] = false };
if (profile != null) body["profile"] = profile;
for (var attempt = 1; ; attempt++)
{
var res = await http.PostAsJsonAsync($"https://starter.cdpfleet.com/{s.Engine}/session", body);
if (res.IsSuccessStatusCode)
{
var wsUrl = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("wsUrl").GetString()!;
return await s.Type.ConnectAsync(wsUrl, new() { Headers = new Dictionary<string, string> { ["x-api-key"] = key } });
}
var code = (int)res.StatusCode;
if ((code != 429 && code != 503) || attempt == 5) throw new Exception($"launch {code} {await res.Content.ReadAsStringAsync()}");
await Task.Delay(TimeSpan.FromSeconds(res.Headers.RetryAfter?.Delta?.TotalSeconds ?? 5));
}
}
// Akamai's _abck carries a status between its first two "~": -1 until a sensor post is accepted. Only that
// field is kept, never the cookie itself. HUMAN issues _px3 once its collector accepts the browser.
async Task<(string? Abck, bool Px3)> Signals(IBrowserContext context)
{
var jar = await context.CookiesAsync();
var abck = jar.FirstOrDefault(c => c.Name == "_abck")?.Value;
var parts = abck?.Split('~');
return (parts == null ? null : parts.Length > 1 ? parts[1] : "?", jar.Any(c => c.Name == "_px3"));
}
async Task<string?> PageState(IPage tab)
{
try { return await tab.EvaluateAsync<string>(State); }
catch (PlaywrightException) { return null; } // while the page navigates
}
// One load, then watch the page once a second for 10 s without touching it, so the vendor's script
// can post its sensor data; a network failure is retried once.
async Task<JsonObject> Visit(IBrowserContext context, string pageKey)
{
var page = pages.First(p => p.Key == pageKey);
var tab = await context.NewPageAsync();
var posts = 0;
tab.Request += (_, r) =>
{
if (r.Method == "POST" && Uri.TryCreate(r.Url, UriKind.Absolute, out var u) && page.Sensor(u.Host)) Interlocked.Increment(ref posts);
};
var nav = new PageGotoOptions { WaitUntil = WaitUntilState.DOMContentLoaded, Timeout = 45000 };
IResponse? res;
try
{
try { res = await tab.GotoAsync(page.Url, nav); }
catch (PlaywrightException) { res = await tab.GotoAsync(page.Url, nav); }
}
catch (Exception)
{
await tab.CloseAsync();
return new JsonObject { ["outcome"] = "error" };
}
var atLoad = await Signals(context);
var seen = new HashSet<string>();
for (var i = 0; i < 10; i++)
{
var s = await PageState(tab);
if (s != null) seen.Add(s);
if (s == "blocked") break;
await Task.Delay(1000);
}
var last = await PageState(tab);
var outcome = seen.Contains("blocked") || last == "blocked" ? "blocked"
: last == "challenge" ? "challenge" : seen.Contains("challenge") ? "cleared" : "content";
var after = await Signals(context);
await tab.CloseAsync();
var cell = new JsonObject { ["outcome"] = outcome, ["status"] = res?.Status, ["sensor_posts"] = posts };
if (pageKey == "akamai") { cell["abck_at_load"] = atLoad.Abck; cell["abck_after"] = after.Abck; }
else cell["px3"] = after.Px3;
return cell;
}
// One session per setup and network; a fresh context per page so every load is a first visit.
async Task<JsonObject> Row(string setupKey, string network, string[] keys)
{
var browser = await Launch(setupKey, network);
var row = new JsonObject { ["setup"] = setups[setupKey].Name, ["network"] = networks[network].Label };
try
{
foreach (var k in keys)
{
var context = await browser.NewContextAsync();
row[k] = await Visit(context, k);
await context.CloseAsync();
}
}
finally
{
await browser.CloseAsync();
}
return row;
}
async Task<JsonElement?> ProfileInfo(string name)
{
var body = await http.GetFromJsonAsync<JsonElement>("https://cdpfleet.com/v1/me/profiles");
if (!body.TryGetProperty("profiles", out var list)) return null;
foreach (var p in list.EnumerateArray()) if (p.GetProperty("name").GetString() == name) return p;
return null;
}
// A returning visitor: the same profile and the same sticky exit, two sessions a few seconds apart.
async Task<JsonObject> Returning(string setupKey, string network, string pageKey)
{
var name = $"case67-{run}";
var visits = new List<JsonObject>();
for (var i = 0; i < 2; i++)
{
var browser = await Launch(setupKey, network, name);
try
{
visits.Add(await Visit(browser.Contexts[0], pageKey)); // the profile's own context; a NewContextAsync() is not saved
}
finally
{
await browser.CloseAsync();
}
// The node saves the profile after the browser exits: wait until it is free again.
for (var j = 0; j < 60; j++)
{
var info = await ProfileInfo(name);
if (info is { } p && !p.GetProperty("in_use").GetBoolean()) break;
await Task.Delay(2000);
}
}
await http.DeleteAsync($"https://cdpfleet.com/v1/me/profiles/{name}");
return new JsonObject
{
["setup"] = setups[setupKey].Name, ["network"] = networks[network].Label, ["page"] = pageKey,
["first"] = visits[0], ["second"] = visits[1],
};
}
var keys = pages.Select(p => p.Key).ToArray();
var matrix = new JsonArray();
foreach (var network in new[] { "own", "tier1" })
foreach (var setupKey in new[] { "chromium", "chrome", "edge", "camoufox" })
matrix.Add(await Row(setupKey, network, keys));
var tier4 = new JsonArray();
foreach (var setupKey in new[] { "chrome", "camoufox" }) tier4.Add(await Row(setupKey, "tier4", keys));
var profile = await Returning("chrome", "tier1", "akamai");
var result = new JsonObject { ["measured_at"] = DateTime.UtcNow.ToString("yyyy-MM-dd"), ["matrix"] = matrix, ["tier4"] = tier4, ["profile"] = profile };
Console.WriteLine(result.ToJsonString(new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }));
// go get github.com/playwright-community/[email protected]
// Driver: build playwright-core 1.60.0 from npm and set PLAYWRIGHT_DRIVER_PATH (see /docs/quickstart).
// env: CDPFLEET_API_KEY, PROXY_URL (one page load per page per setup and network; nothing is clicked or solved)
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/playwright-community/playwright-go"
)
var key = os.Getenv("CDPFLEET_API_KEY")
var runID = func() string { b := make([]byte, 3); rand.Read(b); return hex.EncodeToString(b) }()
type network struct{ Label, Proxy string }
// Sticky residential exits: every setup in a comparison uses the same session name, so the same exit IP.
var networks = map[string]network{
"own": {"own proxy (rotating)", os.Getenv("PROXY_URL")},
"tier1": {"cdpfleet-resi Tier 1, sticky US", "cdpfleet-resi-country-us-session-c67" + runID + "-lifetime-30"},
"tier4": {"cdpfleet-resi Tier 4, sticky US", "cdpfleet-resi-tier-4-country-us-session-c67" + runID + "-lifetime-30"},
}
type setup struct {
Name, Engine, Type string
Headless bool
}
var setups = map[string]setup{
"chromium": {"Chromium, headless", "chromium", "chromium", true},
"chrome": {"Google Chrome, headful", "chrome", "chromium", false},
"edge": {"Microsoft Edge, headful", "edge", "chromium", false},
"camoufox": {"Camoufox, defaults", "camoufox", "firefox", true},
}
var pxHost = regexp.MustCompile(`(^|\.)px-(cloud|cdn)\.net$`)
type testPage struct {
URL string
Sensor func(host string) bool
}
// Each vendor's own home page, which that vendor protects with its own product.
var pages = map[string]testPage{
"akamai": {"https://www.akamai.com/", func(h string) bool { return h == "www.akamai.com" }},
"human": {"https://www.humansecurity.com/", func(h string) bool { return pxHost.MatchString(h) }},
}
var pageOrder = []string{"akamai", "human"}
// What the page shows right now, in the lab's words. The same expression runs in every language.
const state = `(() => {
const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 400)).replace(/\s+/g, ' ');
const blocked = /access denied|you don't have permission to access|request blocked|reference #\d/i.test(t);
const challenge = !!document.querySelector('#px-captcha, #sec-if-cpt-container, #sec-cpt-if') || /press (&|and) hold|challenge validation/i.test(t);
return blocked ? 'blocked' : challenge ? 'challenge' : 'page';
})()`
type cell struct {
Outcome string `json:"outcome"`
Status *int `json:"status,omitempty"`
SensorPosts *int `json:"sensor_posts,omitempty"`
AbckAtLoad *string `json:"abck_at_load,omitempty"`
AbckAfter *string `json:"abck_after,omitempty"`
Px3 *bool `json:"px3,omitempty"`
}
type row struct {
Setup string `json:"setup"`
Network string `json:"network"`
Akamai *cell `json:"akamai,omitempty"`
Human *cell `json:"human,omitempty"`
}
var pw *playwright.Playwright
// 503 means the fleet is momentarily busy: wait Retry-After and try again.
func launch(s setup, net, profile string) playwright.Browser {
body := map[string]any{"proxy": networks[net].Proxy, "headless": s.Headless, "record": false}
if profile != "" {
body["profile"] = profile
}
payload, _ := json.Marshal(body)
for attempt := 1; ; attempt++ {
req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/"+s.Engine+"/session", bytes.NewReader(payload))
req.Header.Set("x-api-key", key)
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
msg, _ := io.ReadAll(res.Body)
res.Body.Close()
if res.StatusCode == 200 {
var session struct{ WsURL string }
json.Unmarshal(msg, &session)
bt := pw.Chromium
if s.Type == "firefox" {
bt = pw.Firefox
}
browser, err := bt.Connect(session.WsURL, playwright.BrowserTypeConnectOptions{Headers: map[string]string{"x-api-key": key}})
if err != nil {
log.Fatal(err)
}
return browser
}
if (res.StatusCode != 429 && res.StatusCode != 503) || attempt == 5 {
log.Fatalf("launch %d %s", res.StatusCode, msg)
}
wait, _ := strconv.Atoi(res.Header.Get("retry-after"))
if wait == 0 {
wait = 5
}
time.Sleep(time.Duration(wait) * time.Second)
}
}
// Akamai's _abck carries a status between its first two "~": -1 until a sensor post is accepted. Only that
// field is kept, never the cookie itself. HUMAN issues _px3 once its collector accepts the browser.
func signals(ctx playwright.BrowserContext) (*string, bool) {
cookies, _ := ctx.Cookies()
var abck *string
px3 := false
for _, c := range cookies {
if c.Name == "_abck" {
parts := strings.Split(c.Value, "~")
v := "?"
if len(parts) > 1 {
v = parts[1]
}
abck = &v
}
if c.Name == "_px3" {
px3 = true
}
}
return abck, px3
}
func pageState(tab playwright.Page) string {
v, err := tab.Evaluate(state) // fails while the page navigates
if err != nil {
return ""
}
s, _ := v.(string)
return s
}
// One load, then watch the page once a second for 10 s without touching it, so the vendor's script
// can post its sensor data; a network failure is retried once.
func visit(ctx playwright.BrowserContext, k string) *cell {
page := pages[k]
tab, err := ctx.NewPage()
if err != nil {
log.Fatal(err)
}
var posts int64
tab.OnRequest(func(r playwright.Request) {
if u, err := url.Parse(r.URL()); err == nil && r.Method() == "POST" && page.Sensor(u.Hostname()) {
atomic.AddInt64(&posts, 1)
}
})
nav := playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded, Timeout: playwright.Float(45000)}
res, err := tab.Goto(page.URL, nav)
if err != nil {
if res, err = tab.Goto(page.URL, nav); err != nil {
tab.Close()
return &cell{Outcome: "error"}
}
}
var status *int
if res != nil {
s := res.Status()
status = &s
}
atLoad, _ := signals(ctx)
seen := map[string]bool{}
for i := 0; i < 10; i++ {
s := pageState(tab)
if s != "" {
seen[s] = true
}
if s == "blocked" {
break
}
time.Sleep(time.Second)
}
last := pageState(tab)
outcome := "content"
switch {
case seen["blocked"] || last == "blocked":
outcome = "blocked"
case last == "challenge":
outcome = "challenge"
case seen["challenge"]:
outcome = "cleared"
}
after, px3 := signals(ctx)
tab.Close()
n := int(atomic.LoadInt64(&posts))
c := &cell{Outcome: outcome, Status: status, SensorPosts: &n}
if k == "akamai" {
c.AbckAtLoad, c.AbckAfter = atLoad, after
} else {
c.Px3 = &px3
}
return c
}
// One session per setup and network; a fresh context per page so every load is a first visit.
func measure(setupKey, net string, keys []string) row {
s := setups[setupKey]
browser := launch(s, net, "")
defer browser.Close()
r := row{Setup: s.Name, Network: networks[net].Label}
for _, k := range keys {
ctx, err := browser.NewContext()
if err != nil {
log.Fatal(err)
}
c := visit(ctx, k)
if k == "akamai" {
r.Akamai = c
} else {
r.Human = c
}
ctx.Close()
}
return r
}
func api(method, path string) map[string]any {
req, _ := http.NewRequest(method, "https://cdpfleet.com"+path, nil)
req.Header.Set("x-api-key", key)
out := map[string]any{}
if res, err := http.DefaultClient.Do(req); err == nil {
json.NewDecoder(res.Body).Decode(&out)
res.Body.Close()
}
return out
}
func profileInfo(name string) map[string]any {
list, _ := api("GET", "/v1/me/profiles")["profiles"].([]any)
for _, p := range list {
if m := p.(map[string]any); m["name"] == name {
return m
}
}
return nil
}
type returnVisit struct {
Setup string `json:"setup"`
Network string `json:"network"`
Page string `json:"page"`
First *cell `json:"first"`
Second *cell `json:"second"`
}
// A returning visitor: the same profile and the same sticky exit, two sessions a few seconds apart.
func returning(setupKey, net, k string) returnVisit {
name := "case67-" + runID
visits := []*cell{}
for i := 0; i < 2; i++ {
browser := launch(setups[setupKey], net, name)
visits = append(visits, visit(browser.Contexts()[0], k)) // the profile's own context; a NewContext() is not saved
browser.Close()
// The node saves the profile after the browser exits: wait until it is free again.
for j := 0; j < 60; j++ {
if info := profileInfo(name); info != nil && info["in_use"] == false {
break
}
time.Sleep(2 * time.Second)
}
}
api("DELETE", "/v1/me/profiles/"+name)
return returnVisit{setups[setupKey].Name, networks[net].Label, k, visits[0], visits[1]}
}
func main() {
var err error
pw, err = playwright.Run(&playwright.RunOptions{SkipInstallBrowsers: true})
if err != nil {
log.Fatal(err)
}
defer pw.Stop()
matrix := []row{}
for _, net := range []string{"own", "tier1"} {
for _, setupKey := range []string{"chromium", "chrome", "edge", "camoufox"} {
matrix = append(matrix, measure(setupKey, net, pageOrder))
}
}
tier4 := []row{}
for _, setupKey := range []string{"chrome", "camoufox"} {
tier4 = append(tier4, measure(setupKey, "tier4", pageOrder))
}
profile := returning("chrome", "tier1", "akamai")
out, _ := json.MarshalIndent(map[string]any{"measured_at": time.Now().UTC().Format("2006-01-02"), "matrix": matrix, "tier4": tier4, "profile": profile}, "", " ")
fmt.Println(string(out))
}
What we got
| Setup | Network | akamai.com (Akamai) | humansecurity.com (HUMAN) | Measured |
|---|---|---|---|---|
| Chromium, headless | own proxy (rotating) | blocked (HTTP 403) | challenge (HTTP 403, _px3, 5 sensor posts) | 2026-10-23 |
| Google Chrome, headful | own proxy (rotating) | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | content (HTTP 200, _px3, 4 sensor posts) | 2026-10-23 |
| Microsoft Edge, headful | own proxy (rotating) | content (HTTP 200, _abck -1 → -1, 2 sensor posts) | content (HTTP 200, _px3, 7 sensor posts) | 2026-10-23 |
| Camoufox, defaults | own proxy (rotating) | content (HTTP 200, _abck -1 → -1, 1 sensor posts) | challenge (HTTP 403, _px3, 3 sensor posts) | 2026-10-23 |
| Chromium, headless | cdpfleet-resi Tier 1, sticky US | blocked (HTTP 403) | challenge (HTTP 403, _px3, 5 sensor posts) | 2026-10-23 |
| Google Chrome, headful | cdpfleet-resi Tier 1, sticky US | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | content (HTTP 200, _px3, 7 sensor posts) | 2026-10-23 |
| Microsoft Edge, headful | cdpfleet-resi Tier 1, sticky US | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | challenge (HTTP 403, _px3, 5 sensor posts) | 2026-10-23 |
| Camoufox, defaults | cdpfleet-resi Tier 1, sticky US | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | challenge (HTTP 403, _px3, 3 sensor posts) | 2026-10-23 |
| Google Chrome, headful | cdpfleet-resi Tier 4, sticky US | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | content (HTTP 200, _px3, 8 sensor posts) | 2026-10-23 |
| Camoufox, defaults | cdpfleet-resi Tier 4, sticky US | content (HTTP 200, _abck -1 → -1, 3 sensor posts) | challenge (HTTP 403, _px3, 3 sensor posts) | 2026-10-23 |
| Google Chrome, headful, returning (profile) | cdpfleet-resi Tier 1, sticky US | 1st visit: content (HTTP 200, _abck -1 → -1, 3 sensor posts) → 2nd: content (HTTP 200, _abck -1 → -1, 3 sensor posts) | — | 2026-10-23 |
From the Node.js run on 2026-10-23. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.
Raw output (Node.js)
{
"measured_at": "2026-10-23",
"matrix": [
{
"setup": "Chromium, headless",
"network": "own proxy (rotating)",
"akamai": {
"outcome": "blocked",
"status": 403,
"sensor_posts": 0,
"abck_at_load": null,
"abck_after": null
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 5,
"px3": true
}
},
{
"setup": "Google Chrome, headful",
"network": "own proxy (rotating)",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "content",
"status": 200,
"sensor_posts": 4,
"px3": true
}
},
{
"setup": "Microsoft Edge, headful",
"network": "own proxy (rotating)",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 2,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "content",
"status": 200,
"sensor_posts": 7,
"px3": true
}
},
{
"setup": "Camoufox, defaults",
"network": "own proxy (rotating)",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 1,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 3,
"px3": true
}
},
{
"setup": "Chromium, headless",
"network": "cdpfleet-resi Tier 1, sticky US",
"akamai": {
"outcome": "blocked",
"status": 403,
"sensor_posts": 0,
"abck_at_load": null,
"abck_after": null
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 5,
"px3": true
}
},
{
"setup": "Google Chrome, headful",
"network": "cdpfleet-resi Tier 1, sticky US",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "content",
"status": 200,
"sensor_posts": 7,
"px3": true
}
},
{
"setup": "Microsoft Edge, headful",
"network": "cdpfleet-resi Tier 1, sticky US",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 5,
"px3": true
}
},
{
"setup": "Camoufox, defaults",
"network": "cdpfleet-resi Tier 1, sticky US",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 3,
"px3": true
}
}
],
"tier4": [
{
"setup": "Google Chrome, headful",
"network": "cdpfleet-resi Tier 4, sticky US",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "content",
"status": 200,
"sensor_posts": 8,
"px3": true
}
},
{
"setup": "Camoufox, defaults",
"network": "cdpfleet-resi Tier 4, sticky US",
"akamai": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"human": {
"outcome": "challenge",
"status": 403,
"sensor_posts": 3,
"px3": true
}
}
],
"profile": {
"setup": "Google Chrome, headful",
"network": "cdpfleet-resi Tier 1, sticky US",
"page": "akamai",
"first": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
},
"second": {
"outcome": "content",
"status": 200,
"sensor_posts": 3,
"abck_at_load": "-1",
"abck_after": "-1"
}
}
}Takeaways
- Akamai turned headless Chromium away at the door; every other setup got the page. On 2026-10-23, in five runs (one per language), akamai.com answered headless Chromium with 403 "Access Denied" in all 10 of its cells, before any sensor script ran and without setting
_abck. Headful Chrome, headful Edge and Camoufox got the real page (HTTP 200) in every cell but one network error, on your own rotating proxy, the sticky Tier 1 exit and Tier 4 alike. The first request carries the decision for headless Chromium: its headless tells are visible before any script runs (case 16). _abckstayed unvalidated, and the page was served anyway. On every load that got akamai.com, the script posted 1–5 sensor payloads within 10 s, yet_abck's status field read-1both at load and 10 s later, in all five runs. So on this site the home page doesn't wait for a validated cookie:-1means "not validated yet", and what a later request with it would get is up to the site's settings. A returning visitor with a profile kept its_abckand got the page again, still at-1.- HUMAN challenged headless Chromium every time, and usually Camoufox too. humansecurity.com answered headless Chromium with a 403 Press & Hold challenge in all 10 of its cells. Camoufox was challenged in 12 of its 15 cells, at least once in every run, on every network. Headful Chrome and Edge got the real page on your own proxy in all 10 of their cells there; on the sticky residential exits they got it in 10 of 15, so the same setup was challenged on one exit and let through on another.
- The two vendors disagreed about Camoufox. Akamai served it the page in every cell, HUMAN challenged it in most. A setup that works on one vendor's site says little about the next: measure per site, as case 64 does for Cloudflare.
- HUMAN sets its cookie even when it challenges.
_px3was issued on 49 of 50 loads, challenged or not, after 2–14 posts to HUMAN's collector; a cookie alone says nothing about whether the visit passed. - These are each vendor's own sites on one day. Akamai and HUMAN tune their customers' sites per customer, and change their checks often. This case loads only the two vendors' own home pages, once per cell, and clicks nothing. Which vendor protects a site is in case 62, what fingerprint test pages see in case 63, and the signals themselves in What a page can read about your browser.