Akamai Bot Manager internals Measured notes on the sensor, the payload and the edge

Automation markers

What the payload says about you when a driver is present. Measured by planting one marker before any page script, decoding the first payload, and diffing it against a clean run, with addresses that also move between two identical clean runs dropped as noise.

The 32-bit mask

Generation 2 reports the whole automation set as one integer at -115[28]. Generation 3 reports it as signals. One bit per marker:

bitvalueset by
01window.__nightmare
12window.cdc_adoQpoasnfa76pfcZLmcfl_Array
24window.cdc_adoQpoasnfa76pfcZLmcfl_Promise
532window._Selenium_IDE_Recorder
664window.__$webdriverAsyncExecutor
7128window.__driver_evaluate
8256window.__driver_unwrapped
9512window.__fxdriver_evaluate
101024window.__fxdriver_unwrapped
112048window.__lastWatirAlert
124096window.__lastWatirConfirm
138192window.__lastWatirPrompt
1416384window.__phantomas
1532768window.__selenium_evaluate
1665536window.__selenium_unwrapped
17131072window.__webdriverFuncgeb
18262144window.__webdriver__chr
19524288window.__webdriver_evaluate
201048576window.__webdriver_script_fn
212097152window.__webdriver_script_func
224194304window.__webdriver_script_function
238388608window.__webdriver_unwrapped
2416777216window.awesomium
2533554432window.callSelenium
2667108864window.calledPhantom
27134217728window.calledSelenium
28268435456window.domAutomationController
29536870912window.watinExpressionError
301073741824window.watinExpressionResult
312147483648window.spynner_additional_js_loaded

Bits 3 and 4 were not triggered by any marker planted so far, so two names in the build's own list are still unaccounted for. fmget_targets came back as value 2, the same bit as the ChromeDriver _Array name; either it shares a bit or that single run was noise.

Note that the mask is emitted as a signed 32-bit integer, so bit 31 appears in a payload as -2147483648.

Markers with their own fields

Several things report outside the mask, in more than one place at once:

address (generation 2)cleantripped by
-115[29]0navigator.webdriver true
-122[2]0navigator.webdriver true
-122[3]0window.webdriver truthy
-100[9]0window.webdriver truthy
-122[0]0window.$cdc_asdjflasutopfhvcZLmcfl_
-100[8]0window._phantom
-100[21]cpen:0window.callPhantom
-100[7]5navigator.plugins.length, so an emptied array reads 0

The generation 3 equivalents are named rather than positional: din.wdr for navigator.webdriver, din.pha for _phantom, din.dau for domAutomation, sde for the $cdc_ probes on both window and document, din.npl for the plugin count and din.adp for the marker list.

How it is detected: the same fact is reported through two or three independent addresses. Hiding navigator.webdriver while leaving window.webdriver set, or the reverse, produces a payload that contradicts itself, which is a stronger signal than either flag alone.

Planting watinExpressionError moves more than one field

One marker in the sweep moved the pointer event log as well as the mask: with it installed, -102 went absent and -109 gained five fields. That is worth knowing because it means the mask is not the only consequence of a marker, and a diff limited to -115[28] will under-report.

What did not move anything

window.driver, window.selenium, a patched Function.prototype.toString, a replaced Navigator.prototype.vendor getter, a Proxy-wrapped userAgent getter, a patched navigator.permissions.query, a deleted window.chrome, an emptied navigator.languages and hardwareConcurrency of 1 all produced nothing outside the noise on the property tested.

Read that carefully. It does not mean those are undetectable. The build holds stripProxyFromErrors and the string at newHandler.<computed> [as apply], and it calls Function.prototype.toString 34 times per load. It means those checks either report through a field that also moves on its own, or feed a verdict this property never acts on, or run on a path a monitoring configuration does not reach.

The mask is not what gates

Setting the automation mask by hand in an otherwise correct payload and sending it to two production endpoints that do gate on the payload: both served the request. One is a login POST that refuses a payload with a moved key, and it serves the same payload with the automation mask set.

So on the endpoints measured, the payload check is "does this decode into a coherent session", not "does this session admit to being automated". The mask is collected and reported; it is not what the gate reads. That may be scoring rather than gating, and a property configured differently may well act on it.