Scalebrowser

ghost-cursor and Bezier paths: why curves are not enough

ghost-cursor read in its source: Bezier paths, Fitts's law and overshoot, and the event stream, pressure and timing that a curved path does not change.

Davide Valli · Founder, Scalebrowser

Published Nov 12, 2026 · 5 min read

Short answer

ghost-cursor draws Puppeteer mouse paths from Bezier curves with 2 random control points, sets the number of points by Fitts's law and overshoots targets more than 500 pixels away, so the shape of a path looks hand-made. The events that carry it do not: each point goes out as its own CDP command with no pause, and the press uses the same command that reported pressure 0 for Puppeteer on our bench. When we once injected tremor to look human, paths reversed direction 17 times as often as a person's on the same bench.
2, random control points, beside a computer mouse with a cable

Puppeteer moves the mouse in a straight line, or not at all, and a page that records the pointer sees it. ghost-cursor is the library most Puppeteer users add at that point: one import, and every click arrives along a curve. We read its source on 27 September 2026 and set what it generates next to what our own behaviour bench measured against real people.

What is ghost-cursor?

ghost-cursor is a Node.js library that moves Puppeteer's mouse along generated curves instead of straight lines, and clicks at a random point inside the target element rather than its centre. Its README promises "realistic, human-like mouse movement data between coordinates". Each path is a Bezier curve between the current position and the target, bent by two control points that the library picks at random on one side of the straight line; its README says this avoids "wonky looking cubic curves".

PropertyValue
GitHub stars1,598
Latest npm release1.4.2, 12 January 2026
npm downloads, 20 to 26 September 2026105,677
PathBezier curve, 2 random control points on one side
Number of pointsfrom Fitts's law, distance against target width
Overshootbeyond 500 pixels of distance, landing within 120 pixels, then a correction
Start of the first pathcoordinate 0,0 unless a start is passed
How each point reaches Chromeone CDP Input.dispatchMouseEvent command
Source: ghost-cursor GitHub repository, source code and npm registry, read 27 September 2026

The design is thoughtful for a library of its size. Fitts's law predicts that a far, small target takes longer than a near, large one, and ghost-cursor uses it to decide how many points a path gets. The overshoot copies what a hand does on long moves: fly past, then correct.

Does ghost-cursor make Puppeteer mouse movement human?

ghost-cursor makes the shape of the path human and leaves the event stream as Puppeteer sends it. In its source, the movement loop sends one CDP command per point and awaits the reply before sending the next, with no pause in between, so the spacing of the events is set by the speed of the connection, not by the curve. An option adds timestamps computed from the curve to each event, and it is off by default.

The click has the same limit. ghost-cursor presses and releases with the same CDP command Puppeteer uses, and like Puppeteer it sends no pressure value. On our signature bench on 24 August 2026, Puppeteer's presses through that command arrived with pressure 0 on pointerdown, 2 runs. The Pointer Events specification requires 0.5 for hardware without pressure support while a button is down, so 0 is not an unusual value but an impossible one, and a curve before it does not change that.

Can websites detect Bezier curve mouse movement?

Yes. Acien, Morales, Fierrez and Vera-Rodriguez built two generators of synthetic trajectories, one based on heuristic functions and one on neural networks, and their detector BeCAPTCHA-Mouse caught "bot trajectories of high realism with 93% of accuracy in average using only one mouse trajectory". A Bezier curve with random control points is a heuristic function, the family that paper generated on purpose to train against.

We learned the same thing from the other side. Our input layer once added a hand tremor to every path to make it look less generated, and on our behaviour bench the result reversed direction far more often than a person does. The bench compares every change with the SapiMouse recordings of 120 people moving a mouse in a browser.

MeasureGenerated pathPerson on the same bench120 people, 10th to 90th percentile
Direction reversals per 100 samples, tremor model4.50.270.56 to 2.25
Source: Scalebrowser behaviour bench against the SapiMouse cohort of 120 people, 16 August 2026
With injected tremor our generated paths reversed direction 4.5 times per 100 samples, a person on the same bench 0.27 times: about 17 times as often.

The tremor model reversed direction 17 times as often as the person, and we took it out. We measure the current model against the same cohort on the same bench, and where it still sits outside the human band is open work. The table is a warning about any generator, ours included: a path that looks natural on a screenshot can still sit outside the band on measures nobody sees.

Which signals sit outside the curve?

Three pointer signals sit outside the curve, and each has one right answer: the pressure while a button is down, the samples merged into one pointer event, and the complete chain from pointer to mouse to click. A generator that perfects the path and gets one of these wrong still gives itself away in a single event. Our survey of the free stealth tools covers where generated mouse movement fits among the other layers a page reads, and the human input page of our documentation lists how each of these signals is produced in our browser. What SeleniumBase's UC Mode and CDP Mode hide is covered in SeleniumBase UC Mode and CDP Mode.

Scalebrowser

Scalebrowser runs real browser profiles on your own PC: your AI agent drives them over MCP and signs in without ever seeing a password.

Download free

When is ghost-cursor enough?

ghost-cursor is enough when the site does not score pointer events, or scores only whether a path is straight. It is not enough when the site reads the events themselves.

Situationghost-cursorWhy
A site that checks for straight lines or clicks at the exact centreenoughcurved paths, random points inside the element
A site that reads pointerdown pressurenot enoughthe press uses the command that gave Puppeteer pressure 0
A site that times pointer eventsnot enoughspacing follows the CDP connection, not the curve
A site that trains a model on trajectoriesnot enough on its owngenerated paths were detected with 93 percent accuracy on average in published research

If you keep ghost-cursor, pass a start position so the first path does not begin at the corner of the page. That fixes the first path and nothing else: pressure, event timing and the event chain live in the input path below the library.

Real browser profiles for your AI agent, on your PC.

Each profile keeps its own identity, logins and proxy. Your agent drives it over MCP and signs in without seeing a password.

Download free for Windows

Free plan with 3 profiles · No credit card · Runs on your PC

Written by

Davide Valli

Founder, Scalebrowser

Builds Scalebrowser, browser automation for the agentic era that runs on your own machine. Measures every change a web page could observe against a real browser before it ships, and writes up the ones that turned out wrong.

All articles by Davide Valli

More in Stealth tools

All 11 articles in Stealth tools