Detection rules are pure functions. They take the collected signals as input and return a DetectionResult. They do not share state, do not mutate signals, and have no side effects.
interface DetectionResult {
detected: boolean
severity: 'high' | 'medium' | 'low'
reasons: string[] // human-readable explanation of what fired and why
}Most rules require two or more independent signals to fire. This threshold controls false positives: a single unusual data point (a fast typist, a mobile user with no mouse) should not get flagged. Each reason in the reasons array includes the actual measured value alongside the threshold it violated.
isHeadless
Detects headless browsers, CDP-controlled Chromium, and Playwright, Puppeteer, or Selenium drivers.
Fires when any single marker is present. Headless markers are individually reliable enough that the two-signal threshold does not apply here.
Severity: high if 2 or more markers are present, medium if exactly 1.
isScripted
Detects scripted bots that fill inputs with mechanical precision and typically avoid mouse movement.
Fires when 2 or more of these conditions are met:
Severity: high if 3 or more conditions match, medium if exactly 2.
The no-pointer condition is mobile-aware. On a mobile device with touch input, zero mouse activity alone is expected. The rule requires both mouse and touch to be absent before counting it as a signal.
isLLMAgent
Detects LLM agents in their Act phase: composing output internally and pasting it, or typing at machine speed via a framework like browser-use or Playwright.
Fires when 2 or more of these conditions are met:
Severity: always high.
The inference rhythm condition is particularly useful for catching multi-step agent flows. The burst-pause-burst pattern emerges from the LLM's Act and Decide cycle: the model pauses between bursts of output to generate the next chunk. Regular inter-burst gaps, unlike the irregular pauses in a human session, produce low gap variance.
The click precision condition targets Playwright's element.click(), which by default targets the exact center of the bounding box. Human clicks land with natural scatter due to hand movement and cursor positioning imprecision.
isUploadAutomation
Detects programmatic file attachment that bypasses the browser's file picker and drag-and-drop APIs.
Fires when: files were attached to a file input (the files count on the element increased) with no preceding change event from the picker and no drop event. This is the signature of a script that directly sets input.files via a DataTransfer object.
Severity: high.
This rule has no minimum count threshold. Any programmatic file attachment without a picker or drop event is immediately flagged.
Reading results
The detections object in BehaviorPayload contains one DetectionResult per rule:
const { detections } = payload
if (detections.isHeadless.detected) {
console.log('severity:', detections.isHeadless.severity)
console.log('reasons:', detections.isHeadless.reasons)
}The reasons array contains strings like:
"navigator.webdriver is true"
"keystroke dwell variance 0.21ms² (human baseline > 50ms²)"
"first input 12ms after focus (humans need >80ms physiologically)"These are useful for debugging your integration, for logging alongside the payload, and for building risk explanations in your fraud queue UI.
The top-level verdict field on the payload gives a single derived classification from all detection results combined. See Payload for how the verdict is computed.