---
title: "ghost-cursor and Bezier paths: why curves are not enough"
description: "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."
canonical: "https://scalebrowser.net/blog/ghost-cursor"
last_modified: "2026-11-12"
published: "2026-11-12"
author: "davide"
category: "stealth-tools"
---

> ## Documentation Index
> Fetch the complete documentation index at: https://scalebrowser.net/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

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".

| Property | Value |
| --- | --- |
| GitHub stars | 1,598 |
| Latest npm release | 1.4.2, 12 January 2026 |
| npm downloads, 20 to 26 September 2026 | 105,677 |
| Path | Bezier curve, 2 random control points on one side |
| Number of points | from Fitts's law, distance against target width |
| Overshoot | beyond 500 pixels of distance, landing within 120 pixels, then a correction |
| Start of the first path | coordinate 0,0 unless a start is passed |
| How each point reaches Chrome | one CDP `Input.dispatchMouseEvent` command |

*Source: [ghost-cursor GitHub repository, source code and npm registry, read 27 September 2026](https://github.com/Xetera/ghost-cursor)*

The design is thoughtful for a library of its size. [Fitts's law](https://en.wikipedia.org/wiki/Fitts%27s_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](https://www.w3.org/TR/pointerevents3/) 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](https://arxiv.org/abs/2005.00890) 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.

| Measure | Generated path | Person on the same bench | 120 people, 10th to 90th percentile |
| --- | --- | --- | --- |
| Direction reversals per 100 samples, tremor model | 4.5 | 0.27 | 0.56 to 2.25 |

*Source: Scalebrowser behaviour bench against the SapiMouse cohort of 120 people, 16 August 2026*

![Three bars for pointer direction reversals per 100 samples: a generated path with the tremor model in blue at 4.5, a person on the same bench at 0.27, and the 10th to 90th percentile of 120 people at 0.56 to 2.25.](/blog/ghost-cursor/reversals.png "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](/blog/stealth-browser) fits among the other layers a page reads, and the [human input page of our documentation](/docs/behaviour) 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 runs real browser profiles on your own PC: your AI agent drives them over MCP and signs in without ever seeing a password.

## 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.

| Situation | ghost-cursor | Why |
| --- | --- | --- |
| A site that checks for straight lines or clicks at the exact centre | enough | curved paths, random points inside the element |
| A site that reads `pointerdown` pressure | not enough | the press uses the command that gave Puppeteer pressure 0 |
| A site that times pointer events | not enough | spacing follows the CDP connection, not the curve |
| A site that trains a model on trajectories | not enough on its own | generated 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.
