Skip to content

The Akamai client

clients/akamai warms an Akamai Bot Manager session without a browser and then carries it: it fetches the page, finds the sensor script the page names, runs that script unmodified inside a wre-live realm with the wre-sandbox browser surface, posts what the script builds, and hands the session’s cookies to whatever request you want to make next.

Nothing about the payload is reimplemented. The vendor’s own script computes it, which is why a rebuild of the sensor does not break this client.

wre client describe akamai

solve does the whole warm-up:

  1. GET the page with navigation headers, keeping cookies in a per-session jar.
  2. Read the surface out of the HTML: the sensor script, the pixel client, the bazadebezolkohpepadr seed and the config bits the script’s own URL carries.
  3. GET the sensor script with script headers and the page as referer.
  4. Mount the page in the sandbox: the profile’s device values, the page’s own HTML with its forms and inputs, document.cookie wired to the jar, requests wired to the host.
  5. Run the script, fire readystatechange, DOMContentLoaded and load, play a pointer, click and key stream, then let the clock run.
  6. Read bmak.get_telemetry(), post the payload it carries to the collection endpoint, and repeat for as many rounds as asked.

After that, request sends anything you like with the session’s cookies, and --telemetry attaches a fresh akamai-bm-telemetry header built from the same realm.

{ "op": "solve", "params": { "url": "https://login.example.com/" } }
{ "op": "request", "params": { "url": "https://api.example.com/orders", "telemetry": true } }

The other ops: discover reports the surface without running anything, page hands back the page HTML and its form fields, payload builds a fresh payload from the open session, post posts one, cookies reads the jar, pow answers a proof-of-work challenge, pixel runs the pixel client, reset drops the sandbox.

field default what it does
page_url none page the session warms against when an op does not name one
profile first in the library sandbox profile id from wre sandbox list
random_profile false pick a captured profile at random instead
random_machine true put every session on a different Mac: chip, cores and memory, picked together
fingerprint from the user agent transport fingerprint as profile[:platform]
user_agent the profile’s overrides what the sandbox and the transport both claim
behaviour true play a pointer, click and key stream into the page
typed a word off the seed what the visitor types into the page’s first text field
pixel true run the pixel client when the page serves one
live_xhr true let the sensor’s own requests leave the sandbox
wire_pace false hold each request until the wall clock reaches the sandbox’s
warp 16 how much faster the page’s clock runs than the wall while a wait is running
load_posts_ms 4000 ceiling on waiting for the script’s own load posts to be answered
wait_ms 6000 ceiling on the wait after the input stream, which ends when the edge validates the jar
rounds 2 payloads the host posts per solve, when live_xhr is off
init_cost_ms 25 clock charge applied when the script writes bmak.startTs

The two waits are ceilings on a condition, not durations: the first ends when the script’s own load posts have been answered, the second when the edge has validated the jar. A session the edge believes early pays neither, which is most of why a solve takes about a second and a half.

live_xhr is the one worth understanding. With it on, which is the default, every request the script makes goes out for real, the way it does in a browser. With it off the sandbox answers the script’s own XMLHttpRequest with 201 {"success":true} and the host posts the payload the script built instead, once per round. The edge reads those two differently: measured against Xero’s login endpoint, and again against the Lee County search, the same payload from the same profile over the same transport is served one way and refused the other.

The sandbox profile is where the payload’s device fields come from. The built-in one is a whole captured Chrome on macOS, so a fresh install is served, but it carries no page layout and a capture of your own is still better. wre sandbox capture serves a page you open in the browser you want to replay, --open drives a real Chrome instead; the client picks the first profile in profiles/ unless told otherwise, and falls back to the built-in when the library is empty.

What a machine renders is left exactly as captured, because two Macs running the same macOS with the same fonts draw the same canvas: text rasterises on the CPU. A canvas hash that moves per session belongs to no machine at all, and the edge notices. What does differ between Macs is the chip, its core count and its memory, so random_machine puts each session on one of sixteen real combinations from M1 to M4 Pro and rewrites the renderer string, hardwareConcurrency and deviceMemory together. run.machine reports what a session picked.

Keep the transport and the profile in step. The client sends the profile’s user agent and lets wre-net pick the closest emulation profile for it. If the captured Chrome is newer than any profile wreq carries, the handshake lands on the newest one it has while the header says what the device said.

Against https://login.xero.com/identity/user/login, which refuses a request whose session never posted a believable payload and answers a credential error when it accepts one:

arm login answer
no sensor run at all 403, Akamai access denied
the word garbage posted as the payload 403
what this client builds 200, the credential error page

clients/akamai/tests/gate.rs is those three arms; it reaches the live endpoint and is #[ignore]d by default.

cargo test -p wre-client-akamai --test gate -- --ignored --test-threads=1

_abck stays at field 2 -1 through all of it, including the arm that passes, so whatever that endpoint reads it is not the cookie’s validation state. Every payload post answers 201 regardless of what is in it, so a 201 there means nothing on its own.

The offline suite in clients/akamai/tests/session.rs runs the same client against a local edge that serves its own sensor script, and covers discovery, the sandbox run, the posts, the pixel client, the cookie jar and a protected endpoint that refuses a session it did not warm.

The challenge arrives in _abck field 4 as <id>-<salt>-<difficulty>-<delay>-<slice>[-<version>], several separated by ||. The pow op parses them, takes the first (preferring version 2), and searches ten rounds: round n uses difficulty + n as the modulus and looks for a nonce where sha256(token + startTs + salt + modulus + nonce), read as a big-endian integer, is zero modulo that modulus. The answer is formatted as the four ;-separated lists the script publishes.

{ "op": "pow", "params": { "abck": "<cookie>", "start_ts": 1760000000000 } }

No host we have looked at asks for one: field 4 is -1 everywhere, and the config bit that enables the client-side solver is off. The search and the answer format are checked against the format the script parses and against themselves, never against a live challenge.

When the page carries a bazadebezolkohpepadr seed and an /akam/<gen>/<hash> script alongside an obfuscated sensor, that script is the pixel client. The client seeds the realm, runs it in the same sandbox, and replays the form-encoded POST it produces to pixel_<hash>, where the hash is (77 ^ seed).toString(16). As with the sensor, nothing about that payload is rebuilt here.

sec_cpt is parsed, not solved. The cookie, the challenge state in _abck and a challenge interstitial in the page are all reported in solve’s challenge field, and that is where it stops: no host in the research set ever served one, so there is nothing to write an answer against that would not be a guess.

The built-in profile has no speech synthesis voices and no audio render hash, so a run on it records those two misses. A captured profile fills them.