veilus-human-input
Human-like mouse paths, wheel scrolling and typing rhythm for browser automation, in Rust.
Every function is pure: it takes parameters and a random number generator and
returns points, deltas and durations. Sending them — CDP
Input.dispatchMouseEvent / Input.dispatchKeyEvent, WebDriver, OS events —
is up to you. No I/O, no async runtime, and a seeded RNG makes every result
reproducible.
[]
= "0.1"
= "0.8"
use ;
use ;
let mut rng = seed_from_u64;
// Mouse: from the last pointer position to a random point inside a button.
let p = default;
let target = pick_point;
for step in path
let hover = hover_pause; // then mousePressed
let hold = press_hold; // then mouseReleased
// Wheel: one scroll of ~1,800 px as macOS acceleration produces it.
let w = default;
for in wheel_events
// Typing: hold time and pause for each character, plus the physical key.
for in "Hello".chars.zip
The models
Mouse. A cubic Bézier curve whose two control points sit on the same side of the straight line, so the pointer bends one way instead of wobbling, with a smoothstep speed profile (accelerate, then decelerate). Movement time follows Fitts' law — far and small targets take longer — and points are spaced by a fixed polling interval, the way a real mouse reports its position. Long moves overshoot the target and come back in a short, almost straight correction. Some moves wander through a few waypoints first. The click point is random inside the middle of the box, never the exact center. While the button is held, the pointer sometimes drifts by a pixel.
Wheel. Notched: equal steps with pauses, as a wheel with detents.
MacAccelerated: one flick ramps up from a tiny first delta to a plateau of
large deltas a few milliseconds apart, and the distance actually scrolled
varies a little around the request.
Typing. The pause between keys is log-normal (a long right tail), longer
after a space or punctuation, with occasional hesitations; each key is held for
a random time. us_key maps a character to KeyboardEvent.code, the Windows
keyCode and the Shift state on a US layout. Characters outside that layout
(accented letters, emoji) return None — they need an input method, which is
not modelled. total_budget optionally caps the time for long text.
Every number is a field of MouseParams, WheelParams or TypingParams.
How close to a person?
We recorded one person on one page — 20 clicks on targets of random size and position, three typed sentences, one long scroll — then had the crate do the same tasks in a Chromium browser, and compared the two with a Kolmogorov–Smirnov test per feature and a cross-validated logistic-regression classifier (0.5 accuracy means it cannot tell them apart).
| Parameters | Mouse classifier | Keyboard classifier |
|---|---|---|
Default, unchanged |
1.00 | 0.63 ± 0.08 |
Default, tick set to the recorded mouse's report interval |
0.38 ± 0.14 | 0.59 ± 0.05 |
The first row is the lesson: set tick to the device you claim to be. The
default is 8 ms (a 125 Hz USB mouse); the recorded mouse reported every 11 ms,
and a single feature — the median gap between pointer events — separated the
two perfectly. With that one field matched, no mouse feature stood out from
the noise of a 20-segment sample.
Limits, stated plainly: one session of one person, on macOS, with a wheel mouse; 20 mouse segments and 142 key pairs. That says "close to this person", not "close to people". The defaults are reasonable starting points, not a measurement of anyone — tune them for the users you model. It makes no promise that automation goes unnoticed; it removes the most obvious machine signatures from input timing and geometry.
License
MIT or Apache-2.0, at your option.
Made by Veilus.