In een eerder artikel deden we een bewering. Hoe een script de cursor ook tekent, of het nu recht naar het doel teleporteert, een bezier-curve volgt of een fysicabibliotheek als WindMouse of NaturalMouse draait, een bewegingsmodel vangt het toch. De vorm van een beweging is maar de helft van wat een hand achterlaat. We lieten zien dat hetzelfde geldt voor Playwright en de "humanlike" API van browserless.io.
Dat roept de voor de hand liggende vraag op van de andere kant van de tafel. Wat als je stopt met de vorm met de hand tekenen en de rest aanleert? We bouwden de tool die dat doet. Hij heet human_nav: een red-team-onderzoekstool die menselijke cursor-, scroll- en toetsaanslagbeweging synthetiseert om gedragsmatige botdetectie te testen. Dit is wat hij onthulde over de sites die het zwaarst leunen op gedragsbiometrie.
De korte versie:
- Kant-en-klare humanizers (bezier-curves, WindMouse, NaturalMouse) worden 97% tot 100% van de tijd gevangen door een desktop-bewegingsmodel. Geometrie was nooit het antwoord.
- human_nav gooit de met de hand afgestelde geometrie weg. Drie kleine reinforcement-learning-policies genereren cursorpaden, scrollvensters en toetsaanslagtiming, getraind op echte menselijke beweging: cadans, terugbewegingen, de pauze tussen woorden.
- Tegen de bevroren detectoren waarop het getraind is, haalt het de menselijke band. Tegen een live detector die is verschoven, gaat de kloof weer open. De scheidbaarheid van scrollen ligt rond een AUC van 0,77, de cursor-policy kan nog boven een live drempel uitkomen, en de toetsaanslagcadans kan buiten het menselijke bereik vallen.
- Het resultaat valt uit in het voordeel van verdedigers. Vorm is een opgelost probleem voor de aanvaller. Wat een policy nog van een persoon scheidt, is cadansstabiliteit, coherentie tussen kanalen en een detector die blijft bewegen.
Waarom dit de moeilijke doelen zijn
De botverdedigingen die een competente operator overleven, zijn niet die controleren of de browser headless is. Die strijd is jaren geleden gewonnen en verloren. De verdedigingen die nog bijten, kijken naar hoe de sessie zich gedraagt: de micro-timing van een scroll, de aarzeling in een cursor, het ritme van typen in een zoekbalk. De grote marketplaces, professionele netwerken en snel roterende sociale feeds, de Amazons, LinkedIns en Reddits van het web, leunen op deze laag, want het is de laag die een stealthbrowser en een schone vingerafdruk niet gratis passeren.
Dat maakt ze het juiste doelwit om een gedragsmatige red-team-tool op te richten. Niet om ze te breken, maar om uit te vinden hoeveel van die gedragslaag echte beveiliging is en hoeveel een drempel is die inklapt zodra de beweging van iets beters komt dan een bezier-curve. Dus bouwden we dat betere.
Hoe human_nav werkt
Het idee is smal. Script de beweging niet. Trek haar als sample uit een policy die leerde hoe menselijke beweging eruitziet. Drie aparte modellen, elk een kleine reinforcement-learning-policy die lokaal draait, elk eigenaar van één kanaal.
Elke beweging, scroll en toetsaanslag wordt door een lokale policy-server geleid voordat die de pagina raakt. De automatisering vraagt "ga naar B" of "typ dit", en de policy geeft de exacte beweging terug, punt voor punt, om af te spelen.
| Kanaal | Observatie → actie | Levert | Parameters |
|---|---|---|---|
| Cursor | 19 → 3 | A→B-pad van stappen (dx, dy, dt ms) | ~40k |
| Scroll | 13 → 2 | wielvenster van ticks (dy, dt) | ~9k |
| Typen | 33 → 2 | timing (hold, flight) per toets | ~35k |
Elk is een compacte actor-critic-MLP getraind met PPO: pure PyTorch, CPU, een tanh-geknepen Gaussische policy, lopende observatienormalisatie, een entropiebonus, een vroege stop op doel-KL. Niets daarvan is exotisch. De optimizer was nooit het punt. De beloning wel.
Dit is wat het onderscheidt van elke kant-en-klare humanizer. Een bezier-bibliotheek optimaliseert het uiterlijk van een pad. Deze policies worden beoordeeld door bevroren kopieën van cside's eigen detectoren, en beloond in logit-ruimte voor het produceren van beweging die die detectoren als menselijk lezen. Ze tekenen geen mooiere curve. Ze lossen op waar de curve altijd alleen maar een plaatsvervanger voor was.
In de cursor-policy
De cursor-agent ziet een observatie van 19 dimensies: de vector naar het doel, de laatste (dx, dy, dt), de stapindex, de cumulatieve padlengte, de netto verplaatsing, lopende statistieken van snelheid en timing, een telling van richtingswisselingen, een rechtheidsratio en een vooruitblik op het volgende waypoint. Die met accumulatoren beladen toestand is bewust. De detectoren lezen de geaggregeerde kinematica over het hele pad, dus de policy krijgt lopende voldoende statistieken aangereikt voor precies de aggregaten waarop ze wordt beoordeeld. Ze handelt door elke stap een drietal (dx, dy, dt) uit te sturen, begrensd tot ±40 px en 4 tot 40 ms.
De beloning is een poort. Een aankomstbonus van 20 domineert alles, dus de policy leert eerst echt bij B aan te komen. Pas bij aankomst int ze een detectorbeloning, gezet op de menselijke marge van de bindende criticus, de slechtste van twee bevroren scorers: cursor_v2 (een MLP-kop, drempel rond 0,992) en cursor_v1 (een LightGBM-kop, drempel rond 0,828). Breng beide tegelijk onder de drempel en ze verdient er een realismebonus bovenop. De training loopt een curriculum af: eerst pure navigatie, dan oplopende detectordruk tegen beide critici samen. Tegen twee detectoren tegelijk vechten is wat de paden schoon en vloeiend gekromd houdt, in plaats van in te storten tot een artefact dat één scorer misleidt en er voor de ander kapot uitziet.
Zes echte A→B-paden rechtstreeks uit de policy, deterministisch, tonen de cursor die door het midden versnelt en soepel het doel binnenkomt, de vertraging bij nadering die een hand maakt in plaats van een glijden op constante snelheid. De mediane rechtheid ertussen is ongeveer 0,99, over 12 tot 26 punten, bij ruwweg 38 ms per stap. De timing per stap blijft bijna vlak in de midden-dertig milliseconden. De policy ontdekte dat stabiele micro-timing, niet jitter, is wat de bevroren detectoren als menselijk lezen.
Deze oplossing is een scherpe, deterministische naald. De gemiddelde actie van de policy is wat in de menselijke band valt. Trek haar stochastisch als sample, of voeg er je eigen ruis bovenop toe, en het realisme valt uiteen. Willekeur is precies waar naïeve automatisering naar grijpt, en hier is het de verrader. De winst is ook smal. Het is een adversariële exploit van één bevroren detector, geen gecertificeerde menselijke beweging.
Scrollen en typen
De scroll-policy levert (dy, log1p(dt)) per wieltick. De timing wordt in logaritmische ruimte gegenereerd en teruggeschaald, dus één policy dekt alles, van bursts onder 10 ms tot bezinkingspauzes van een seconde. Ze trekt haar scroll-taken als sample, de lengte, de netto afstand en de terugbewegingen, uit een bank van echte menselijke scrolls. Elk gegenereerd venster gaat soepel in en uit in plaats van in een rechte lijn op te lopen, en richtingsomkeringen vallen op langere gaten, iemand die even pauzeert voordat die corrigeert.
De typ-policy draait op een observatie van 33 dimensies waarvan de staart een one-hot is van de categorieën van de huidige en de volgende twee toetsen (letter, cijfer, spatie, bewerking), en levert een paar (hold, flight) per toets. De hold-tijden blijven rond 150 ms, maar de flight-gaten dragen het signaal.
| Toetsaanslagtiming | Waarde |
|---|---|
| Hold-tijd | ~150 ms |
| Flight binnen een woord | 250 tot 350 ms |
| Flight op woordgrens | ~850 ms |
De policy leerde te pauzeren tussen woorden, een ritme dat een vaste vertraging tussen toetsen nooit produceert. Dat is het hele idee. Vervang met de hand geschreven geometrie door een aangeleerde policy per kanaal, beoordeel haar tegen een echte detector, en laat haar de delen van menselijke beweging vinden die iemand die mouse.move() schrijft nooit zou bedenken om te coderen: de vertraging bij nadering, de pauze voor een correctie, de tel tussen woorden.
Waar deze paden landen
Eén manier om te zien wat de policy ons opleverde: neem een stapel gegenereerde paden, van de naïeve humanizers en van human_nav, breng elk terug tot tien kinematische kenmerken, en projecteer het geheel naar drie dimensies met PCA. De families recht-met-jitter en bezier zitten in hun eigen strakke klontjes, want er zijn maar zoveel manieren om een glad ogende curve te tekenen. De policy spreidt zich over een breder, rommeliger gebied, dichter bij hoe echte handbeweging zich verspreidt, wat de eigenschap is die een vaste curve niet kan produceren.
Lees dit eerlijk. De projectie beslaat 440 paden van vier synthetische generatoren, met 77% van de variantie in drie assen. Ze vergelijkt synthetische generatoren onderling, niet tegen echte menselijke opnames. De bevinding is dus "de policy bezet een ander, breder gebied dan kant-en-klare humanizers", niet "de policy is niet te onderscheiden van een mens".
Hoe dichtbij het echt komt
Hier telt eerlijkheid meer dan de kop. Tegen de bevroren detectoren waarop de policies getraind zijn, winnen ze. Dat is wat "tegen ze getraind" betekent, en op zichzelf bewijst het bijna niets. De echte test is een detector die de policy nog nooit heeft gezien, en idealiter een die sindsdien is verschoven. Daar is het beeld gemengd, en de mengeling is de bevinding.
| Kanaal | Tegen de bevroren detector | Tegen een live detector die is verschoven |
|---|---|---|
| Cursor | In de menselijke band | Kan boven een live drempel uitkomen. Niet gecertificeerd menselijk. |
| Scroll | In de menselijke band | Scheidbaar rond een AUC van 0,77. Realistisch, niet onzichtbaar. |
| Typen | In de menselijke band | Pauzepercentage en snelheid kunnen buiten het menselijke bereik vallen. |
Lees die tabel zoals een verdediger dat zou moeten doen. Een aangeleerde policy is een grote stap vooruit ten opzichte van een bezier-curve. Ze dicht het grootste deel van de geometriekloof die kant-en-klare humanizers nooit raken. Maar "plausibel menselijk tegen de detector waarop ik trainde" is niet "menselijk". Zodra de detector aan de andere kant er een is die ze nooit heeft bestudeerd, of een die sinds de training is bewogen, komt het restsignaal terug. Bewegingsrealisme vervalt onder verschuiving, en verschuiving is het enige wat een verdediger volledig beheerst.
Wat dit betekent als je gedragsdetectie draait
Er volgen een paar dingen uit, en dat is de reden waarom we überhaupt red-team-tools bouwen.
Ten eerste, rol geen gedragscontrole uit om die te bevriezen. Elke policy die tegen een statische detector is getraind, zal die uiteindelijk evenaren. De effectiefste verdediging in die tabel is de rechterkolom: opnieuw vastleggen en hertrainen volgens een schema. Dat is geen onderhoud, het is het eigenlijke mechanisme. Een detector die sneller beweegt dan een aanvaller kan hertrainen, is er een waarop hij nooit convergeert.
Ten tweede, beoordeel coherentie, niet kanalen los van elkaar. Een policy die cursorbeweging beheerst en een policy die typen beheerst, blijven twee aparte samplers. De correlaties die een echt persoon produceert tussen bewegen, scrollen en typen zijn veel moeilijker na te maken dan welk afzonderlijk kanaal ook, want niemand trainde een policy op de gezamenlijke verdeling. Die naad is waar een verzameling goede enkelkanaals-namaak uiteenvalt.
Ten derde, houd gedrag als één laag, niet dé laag. Gedragsbeweging is krachtig, en het is ook de laag die het meest is blootgesteld aan een vastberaden humanizer. Combineer haar met vingerafdruk-, netwerk- en TLS-signalen, zoals cside's stack is_bot beslist als een gecombineerde oproep in plaats van op één model te vertrouwen, en een operator moet elke laag tegelijk verslaan, niet alleen die ene waarin hij een aangeleerde policy heeft gestopt. Dat is het argument voor een cascade, en daarom houdt botdetectie stand waar één controle het zou begeven.
Verantwoorde openbaarmaking. Dit verslag behandelt de techniek en haar grenzen op het niveau van uitkomsten. Het laat de interne werking van de detector, de drempels, de kenmerkdefinities per kanaal en elke afstemprocedure buiten beschouwing, dezelfde lijn die onze andere openbare artikelen aanhouden. De tool wordt niet verspreid. Alles hierin dat een extern platform raakt, is vóór publicatie met dat platform gedeeld.
Waarom je de aanvaller bouwt
Het is makkelijk om human_nav te lezen als een ontwijkingstool die toevallig in een detectiewinkel woont. We bouwden hem om de tegenovergestelde reden. De enige manier om te weten of een gedragsverdediging echte beveiliging is of een drempel, is de best mogelijke aanvaller bouwen en precies meten waar hij ophoudt te werken. Het antwoord hier is hoe dan ook nuttig. Aangeleerde beweging verslaat geometrie, en het verslaat nog steeds geen detector die blijft bewegen en meer dan één kanaal tegelijk leest. Dat is een ongemakkelijk resultaat als je "humanlike" automatisering verkoopt, en een geruststellend als het jouw taak is de bots buiten te houden.
cside laat je elk script en elke sessie zien die je site raakt, inclusief de automatisering die heeft geleerd te bewegen als een hand. Zie wat er echt draait in de browsers van je gebruikers.





