I Built a Browser SDK That Classifies Sessions Into Three Categories, Not Two

·10 min read·1 views

CAPTCHA and fingerprinting were built for clumsy bots. In 2026, LLM agents fill forms with realistic typing cadence, patch out webdriver flags, and solve CAPTCHA for $0.50. Nyasa adds a third actor to the model: AuthorizedAgent. One SDK, 24 signals, six detection rules.

Nyasa: a browser SDK that classifies sessions as Human, AuthorizedAgent, or UnauthorizedBot

Every bot detection system I've seen works with two actors: human or bot. Block the bot, let the human through.

That model is wrong in 2026.

There is a third actor: AI agents acting legitimately on behalf of real users. Shopping assistants. Automated onboarding flows. Fintech integrations. These agents look like bots by every traditional signal: headless browser characteristics, scripted input patterns, no idle pauses. But blocking them means turning away real business.

I built Nyasa to handle all three.

Why existing tools fail now

CAPTCHA was built for bots that couldn't read distorted text. Those bots are dead. CAPTCHA farms solve challenges at $0.50 each. LLM vision models solve them in milliseconds.

Device fingerprinting catches webdriver flags and automation markers. Modern headless browsers patch those out. Playwright, Puppeteer, and every major automation framework have community patches specifically for passing fingerprint checks.

Behavioral analytics (typing cadence, mouse movement) catch scripted bots. But LLM agents don't use scripted input anymore. They type at 60-80 WPM with realistic keystroke intervals, move the mouse in curved paths, and pause at form fields before filling them.

The deeper problem is architectural. Existing systems ask: "Is this a bot?" Nyasa asks: "Who is this session?" The answer is one of three things.

The three-actor model

Nyasa classifies every session as exactly one of:

  • Human: no detection rules fired
  • AuthorizedAgent: holds a valid cryptographic identity claim, automatically bypasses bot rules
  • UnauthorizedBot: one or more detection rules fired

The AuthorizedAgent category is the real addition. An AI shopping assistant built on top of your product shouldn't have to pass a CAPTCHA. It should present a signed identity claim, and your system should recognize and respect it. Nyasa handles that handshake.

Three distinct session verdict types: Human, AuthorizedAgent, and UnauthorizedBot. Both unauthorized LLM agents and headless bots resolve to UnauthorizedBot, but badge labels differentiate them for downstream routing.

The signal stack

24 signals total, split across three layers.

Behavioral signals (13)

SignalWhat it measures
Keystroke dwell and flight timeHow long keys are held, time between keystrokes
Mouse path curvatureDeviation from straight-line movement
Paste vs typed ratioWhether text was typed character by character or bulk-pasted
Click precision (center offset)Distance from click point to element center
Session burst-pause rhythmAlternation between fast activity and idle gaps
Backspace correctionsCorrection frequency during text input
Scroll depthHow far down the page a session goes
Touch mechanicsMulti-touch patterns and pressure distribution
Field-level timingTime spent on each form field before moving on
Input originTyped vs pasted vs dropped vs programmatic fill
Tab visibilityWhether the session loses and regains focus
File upload mechanicsHow files are attached (drag, click, or programmatic)
Session rhythmOverall pace and structure of the session

Fingerprint signals (8)

SignalWhat it catches
Webdriver and CDP markersAutomation framework artifacts in the browser object
Iframe vs parent plugin consistencyMismatches between iframe and parent context
Canvas fingerprint hashRendering environment identifier
WebGL renderer stringSwiftShader and LLVMpipe detection (headless GPU emulation)
Audio fingerprint via OfflineAudioContextAudio processing environment
Incognito detection via storage quota probePrivate browsing mode
Timezone vs navigator.language consistencyRegion mismatch between system and browser config
Persistent device UUID with isNew flagTracks whether this device has been seen before

Network signals (3)

SignalWhat it measures
Page reaction timeTime from page load to first interaction
Connection typeNetwork type reported by Navigator API
Page load timingPerformance timing breakdown

Detection rules

Six rules run against the collected signals. Each rule fires independently. Any fired rule pushes the verdict toward UnauthorizedBot, except isAuthorizedAgent which short-circuits to AuthorizedAgent regardless of other rules.

isHeadless reads the fingerprint layer for automation markers: webdriver properties, CDP exposure, WebGL renderer strings like SwiftShader or LLVMpipe, iframe/parent inconsistencies. If the browser environment looks like a headless runner, this fires.

isScripted reads behavioral signals for bot-like input patterns. Scripted bots fill fields in milliseconds, never backspace, and move between fields in perfect sequence. This rule catches that pattern.

isLLMAgent is the hardest one. I'll cover it in the next section.

isAuthorizedAgent reads the agent identity claim from window.__nyasaAgentSignature or a meta tag. If a valid claim is present, the session is classified as AuthorizedAgent and no further rules are evaluated.

isUploadAutomation checks file upload mechanics. Human uploads involve a file picker dialog or drag interaction. Programmatic uploads bypass both. This rule catches the bypass pattern.

isMultimodalBot looks for cross-signal contradictions: a session that passes one rule but shows soft signals consistent with automation across several others. It reads sibling DetectionResults directly rather than re-sampling signals, which avoids divergence when two rules read the same underlying data at different times.

isLLMAgent deep dive

This is the rule I spent the most time on. LLM agents are genuinely hard to distinguish from fast, focused humans. Seven signals, evaluated together:

  1. Machine-speed keystroke bursts under 20ms. Human dwell times cluster around 80-200ms. Sub-20ms bursts don't happen in human typing.
  2. Mouse stillness above 70%. Humans move the mouse constantly, even when not clicking. LLM-driven sessions often keep the cursor parked.
  3. Uniform keystroke variance near zero. Human typing has natural rhythm variation. LLM agent keystrokes have suspiciously consistent intervals.
  4. Zero backspace rate. Humans make corrections. An agent filling a form it computed upfront doesn't backspace.
  5. Pixel-perfect click precision. Humans click near the center of an element but not exactly on it. Agents click at the computed center coordinate.
  6. Missing field exploration. Humans often click into a field, leave, return, re-read the label. LLM agents visit each field once in sequence and move on.
  7. No idle micro-pauses. Humans have sub-second pauses between thoughts. Agent sessions show continuous forward progress.

No single signal is definitive. A fast typist has low keystroke variance. A focused user might not backspace. isLLMAgent requires several of these signals to align before it fires.

Side-by-side comparison of behavioral signals between a human typist and an LLM agent across seven dimensions: keystroke interval distribution, mouse activity, keystroke variance, backspace rate, click precision, field exploration pattern, and micro-pause presence.

Feature extraction architecture

Early versions of Nyasa had each detection rule computing its own derived metrics from raw signals. That caused two problems: duplicated math across rules, and rules diverging when they read the same underlying signal at slightly different times during a session.

The feature extraction layer solves this. It runs once per session and computes 8 shared derived metrics before any detection rule evaluates. Every rule reads from the same computed values.

The metrics include things like overall typing variance, click precision distribution, and mouse activity ratio. Computing them once means a detection rule that needs "mouse stillness percentage" and a sibling rule that also needs it will always agree on the number.

isMultimodalBot benefits from this the most. It reads the DetectionResults of its sibling rules rather than re-running signal evaluation. Near-miss composition (where a session nearly triggers multiple rules without fully triggering any) gets caught without any rule having to re-sample data that may have aged out.

The verdict system

Every session gets a verdict object with three fields:

interface NyasaVerdict {
  type: 'Human' | 'AuthorizedAgent' | 'UnauthorizedBot';
  confidence: number;           // 0.0 to 1.0
  badges: DetectionBadge[];     // which rules fired or nearly fired
}

Confidence is a noisy-OR score across all active rules. If one rule fires with 0.8 confidence and a second fires with 0.6, the combined score is 1 - (1 - 0.8) * (1 - 0.6) = 0.92. Multiple weak signals compound.

Badge labels tell you which rules contributed. A session might come back as UnauthorizedBot with badges for isHeadless and isLLMAgent, which tells you this is a headless LLM agent. That gets different handling than a scripted form filler.

The verdict payload ships via navigator.sendBeacon. Non-blocking, fires after the page interaction completes, survives page unload. Your analytics pipeline or backend decision layer receives it without adding latency to the user-facing flow.

How 24 signals across three collection layers feed into a shared feature extraction layer, then into six independent detection rules, and finally into a single typed verdict with confidence score and badge labels.

Architecture

Nyasa SDK Architecture

The SDK runs entirely in the browser. Signals are collected passively as the session progresses. The feature extraction layer runs on a timer and on key events. Detection rules evaluate when a verdict is requested or automatically at session end.

Dual packaging

Nyasa ships as both ESM and IIFE from a single tsup build config.

ESM for bundlers:

import { createNyasa } from '@devanshhq/nyasa';
 
const nyasa = createNyasa({
  endpoint: 'https://your-backend.com/nyasa',
  agentBypass: true,
});
 
nyasa.start();

IIFE for script tags, no bundler required:

<script src="https://cdn.jsdelivr.net/npm/@devanshhq/nyasa/dist/nyasa.iife.js"></script>
<script>
  Nyasa.createNyasa({
    endpoint: '/api/nyasa',
    agentBypass: true,
  }).start();
</script>

Same source, two outputs. The tsup config handles the split. No separate build for CDN distribution.

Installation and quick start

npm install @devanshhq/nyasa

Minimal setup:

import { createNyasa } from '@devanshhq/nyasa';
 
const nyasa = createNyasa({
  endpoint: '/api/session-verdict',
});
 
nyasa.start();
 
// Get the verdict at any point
const verdict = await nyasa.getVerdict();
console.log(verdict.type);       // 'Human' | 'AuthorizedAgent' | 'UnauthorizedBot'
console.log(verdict.confidence); // 0.0 - 1.0
console.log(verdict.badges);     // ['isHeadless', 'isLLMAgent', ...]

On the backend, you receive the verdict via the beacon endpoint and decide what to do: allow, challenge, block, or route differently based on the type.

The authorized agent bypass

If you're building an AI agent that needs to interact with Nyasa-protected pages, set the signature before the SDK initializes:

// In your agent code, before navigating to the page
window.__nyasaAgentSignature = {
  token: 'signed-jwt-from-your-auth-server',
  agentId: 'shopping-assistant-v2',
  issuedAt: Date.now(),
};

Or via meta tag for server-rendered flows:

<meta name="nyasa-agent-signature" content="signed-jwt-here" />

The isAuthorizedAgent rule reads this claim, validates the signature, and short-circuits to AuthorizedAgent. No other rules run. The session is logged as a known agent, not blocked as a bot.

This is the part that most bot detection tools don't have a concept for. If you're running a fintech integration or an AI onboarding assistant, you shouldn't have to fight your own security layer.

What it catches that others miss

Traditional fingerprinting misses LLM agents because they run in real browsers with patched automation markers. Traditional behavioral analytics miss them because modern LLM agents have realistic typing cadence.

Nyasa catches them through the combination: machine-speed micro-bursts that no human produces, combined with zero backspace rate and pixel-perfect clicks. Any one signal has false positives. All three together don't.

The multimodal rule catches the edge cases: sessions that pass fingerprinting and look almost human behaviorally but have soft contradictions across signals that don't fit either profile cleanly.


Live: nyasa.devanshtiwari.com

GitHub: github.com/Devansh-365/nyasa

npm: npm install @devanshhq/nyasa

Interested in working together?