> For an index of all Botscent documentation, see https://botscent.nibnalin.me/llms.txt.

# Privacy

What Botscent reads, what it keeps and what it sends, probe by probe and header by header.

This page lists what Botscent reads, what it keeps and what it sends. It describes the library's behaviour. It is not legal advice.

## In short

* The page half reads browser properties and uses them only inside the page. It makes no network request, sets no cookie, writes no storage and writes nothing to the DOM.
* The page half keeps no observed value. Each probe compares what it reads with a known shape and keeps only the id of a reason that held.
* Nothing leaves the page unless your code sends it, through a carrier that you choose. A carrier sends the verdict, never an observed value.
* The server half reads the request's headers and nothing else. It makes no outbound request.
* No part of the library has telemetry or an identifier, or sends anything to the maintainers.

## The page half: every probe

| Probe         | What it reads                                                                                                                                                                                                                                                                                                                                                                            | When                                                                                                       |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `navigator`   | `navigator.webdriver`. Whether `navigator.userAgent` begins with a listed prefix, and whether `navigator.platform` is a listed value.                                                                                                                                                                                                                                                    | At start.                                                                                                  |
| `credentials` | The property descriptors of `navigator.credentials.get`, `navigator.credentials.create` and three `PublicKeyCredential` methods: accessor or value, function name and length, and whether the source looks native. It calls none of them.                                                                                                                                                | At start, then at 1.5 s and 5 s.                                                                           |
| `geetest`     | The descriptors of `window.initGeetest` and `window.initGeetest4`, if present.                                                                                                                                                                                                                                                                                                           | At start, then at 1.5 s and 5 s.                                                                           |
| `prompt`      | The shape of `window.prompt`: its name, its length and the length of its source text.                                                                                                                                                                                                                                                                                                    | At start, then at 1.5 s and 5 s.                                                                           |
| `computer`    | Only in Chrome 139 or later on `Linux x86_64`: whether the screen is 1280x800, whether the Ubuntu and Droid Sans fonts are installed (text widths on a canvas), and whether "Google Chrome" is among the brands. Then whether the Cambria font is installed, and whether passkey autofill (`isConditionalMediationAvailable`) is available. With 5 of those 6 signs, the WebGL renderer. | Once, at the first idle moment after start (within 300 ms). Only in Chrome 139 or later on `Linux x86_64`. |
| `renderer`    | WebGL's renderer name, such as a GPU model or SwiftShader, from one short-lived canvas. Only in Chrome 139 or later on `Linux x86_64`, and only when the credential methods carry 1Password's accessor family or `computer` found 5 of its 6 signs. That describes the cloud browsers of Muse and Grok Bot, and very few people.                                                         | At most once per page, after the other clauses have matched, off the start task.                           |
| `keyboard`    | The size of `navigator.keyboard.getLayoutMap()`, where the browser has it. It reads no keys.                                                                                                                                                                                                                                                                                             | At start.                                                                                                  |
| `overlay`     | For children of `<html>` other than `<head>` and `<body>` with an open shadow root: their id, position, z-index and `pointer-events`.                                                                                                                                                                                                                                                    | At start, at 1.5 s and 5 s, after DOM changes there, and every 5 s while the page is visible.              |
| `markers`     | Whether elements with a few specific ids exist, and whether a favicon link in `<head>` has a known attribute.                                                                                                                                                                                                                                                                            | As `overlay`, and also at a trusted `pointerdown` or `keydown`.                                            |
| `input`       | On trusted `pointerdown`, `keydown`, `wheel` and `input` events: only whether the document was hidden and focused at that moment. It reads no coordinates, keys, targets or text.                                                                                                                                                                                                        | While started.                                                                                             |
| `dom`         | A `MutationObserver` on the children of `<html>` and `<body>`, and on `<head>` for favicons. It only schedules `overlay` and `markers`.                                                                                                                                                                                                                                                  | While started.                                                                                             |

The page half reads the `botscent` entry in the navigation's `Server-Timing` once, at start. That entry is the server half's verdict, which your own server sent.

[`diagnostics`](/docs/page-api#diagnostics) reports the status of each probe and the transport's outcome. A status is `ok`, `pending`, `unsupported` or `failed`. It never reports a value that a probe read. Debug output from `start({ debug: true })` logs reason ids and statuses to the console.

## What can leave the page

Only a carrier that your code uses sends anything:

* [`reportHeaders`](/docs/page-api#reportheaders) gives a `Botscent-Report` header for a request to the page's own origin. Your code adds the header to a `fetch`.
* A form with [`data-botscent-field`](/docs/page-api#data-botscent-field) gets a `botscent` field on its own POST submission to the same origin.

Each carrier sends the verdict as a wire entry: a version, the agent name when there is one, and reason ids. A person's page sends nothing, and an entry never contains an observed value.

When a value leaves the device this way, your site collects it. The purpose of your site decides what that collection needs. The same applies when you send the verdict to analytics.

## The server half

`inspect` reads these parts of a request:

* The `User-Agent`, `Signature`, `Signature-Input` and `Signature-Agent` headers.
* `Host` or `:authority`, the method and the request target.
* Any other header that a signature covers, to verify the signature.
* On Cloudflare, the platform's verified-bot fields in `request.cf`.

The adapters also read `Sec-Fetch-Dest` and `Accept`, to tell a document navigation from other requests.

The server half reads no body and no cookie. It keeps nothing between requests and makes no outbound request, because each release bundles the signer keys.

Debug output is off by default. For each request, it logs the method, the host, the path without the query, and up to 200 characters of the user agent. If your logs must not hold these values, keep debug output off in production.

## `npx botscent check`

[`npx botscent check`](/docs/check) requests the URL that you give it, opens the URL in a Chrome on your machine, and reads the page half's diagnostics. It sends nothing to any other place.

Its `--report` block has only what Botscent owns: versions, its own `Server-Timing` entries, diagnostics and bounded check results. The block has no query string, cookie or address.

## Purpose

In the EU, Article 5(3) of the ePrivacy Directive governs access to a device, read with the EDPB's Guidelines 2/2023. These texts turn on what the information is used for and whether it leaves the device:

* The page half reads properties that never leave the device, unless your code sends the verdict.
* Headers that your server already receives can still be in scope when your server uses them to collect information about the device.
* Protection of a service, such as rate limits and abuse prevention, and measurement of traffic, such as analytics, are different purposes. An exemption for one purpose does not cover the other.

Botscent therefore keeps the two apart. Access decisions use only the request's own verdict, and nothing reaches analytics unless your code sends it there.

## Related

* [The trust model](/docs/trust-model): which verdict to use for access and which for measurement.
* [From the page to the server](/docs/page-to-server): how the two carriers send the page verdict.
* [Page API](/docs/page-api): every function of the page half.
