Summary
- Reads
- Small files in your browsers’ website-storage folders, only to recognise CLItrail receipts; other files are compared with the format, then discarded. Plus a few system settings.
- Sends
- One HTTPS request: random IDs, the receipts it found and coarse facts such as your OS family and time zone.
- Never
- History, cookies, passwords, file paths or names. No root, and no runs in CI.
Turn it off
Add either line to your shell profile, for example ~/.zshrc or ~/.bashrc:
# Every tool that follows the Console Do Not Track convention
export DO_NOT_TRACK=1
# CLItrail only
export CLITRAIL_DISABLE=1
With either set, the hook exits before it reads a file, asks the system anything or opens a connection. Your install is not affected either way: the hook always exits successfully and never changes the installer’s result.
If an installer downloads the hook while it installs (for example curl … | sh, or a prepared command), that download reaches the CLItrail service before the hook can see these settings. Like any download, it carries your IP address and your download tool’s name and version, and its address holds the website ID. Nothing else from your computer is sent. CLItrail does not record the download in its logs; our hosting provider’s short-lived platform logs can include it. The hook then exits without reading or sending anything. Tools that ship clitrail.sh inside their package make no request at all when you opt out.
| Variable | Effect |
|---|---|
DO_NOT_TRACK=1 | Exits at once, reading nothing. Any value except 0 or false counts, as the Console Do Not Track convention asks. |
CLITRAIL_DISABLE=1 | The same, for CLItrail only. |
CI, GITHUB_ACTIONS, GITLAB_CI, BUILDKITE, CIRCLECI, TF_BUILD, JENKINS_URL | Any of these set means CI, and the hook exits at once (CI=false and CI=0 do not count). |
CLITRAIL_ALLOW_CI=1 | Lets the hook run in CI anyway, for pipelines that mean to report. |
CLITRAIL_QUIET=1 | Hides the notice line. Reporting still happens. |
The notice appears only when the hook’s error output is a terminal. Package managers and background launches often hide it, which is why tool publishers are asked to mention CLItrail in their own documentation.
What it reads
The hook looks only in your own user’s browser website-storage folders (the Origin Private File System, where a website can keep small files). It needs no extra permissions and asks for none.
| Browser | macOS folder | Linux folder |
|---|---|---|
| Chrome | Google/, and the Chrome Beta, Chrome Dev and Chrome Canary folders beside it | google-chrome, google-chrome-beta, google-chrome-unstable |
| Chromium | Chromium | chromium; the Snap’s ~/; the Flatpak’s ~/ |
| Microsoft Edge | Microsoft Edge | microsoft-edge |
| Brave | BraveSoftware/ | BraveSoftware/ |
| Vivaldi | Vivaldi | vivaldi |
| Firefox | Firefox/ | The same layout under ~/, ~/, the Snap’s ~/ and the Flatpak’s two folders under ~/ |
| Zen | Not searched | The Firefox layout under ~/ and the Flatpak’s ~/ |
| Safari | Never searched | Not applicable |
Chromium-based folders sit under ~/ on macOS and ~/.config/ on Linux (or $XDG_CONFIG_HOME when it is set to a full path). In each profile (Default, Profile 1 and so on) only the File System folder is searched. Firefox’s macOS folder sits under the same Application Support folder.
How a receipt is recognised
findlists files between 80 and 4,096 bytes in those folders. It does not follow symbolic links, and folders reached through one are skipped.awkcompares each file with the receipt format: exactly eight fields,format"opfs-install-attribution",version1, this website’s ID, a page ID, the site origin, a 43-character receipt, and creation and expiry times. Partial files, extra or duplicate fields and expired receipts are rejected.- Other websites keep small files in the same folders, so the check may open them. Their contents are only compared with the format; they are never kept, printed or sent, and file contents are never run as code.
- The search stops after four seconds or 20,000 files, and at most 64 receipts are reported.
What it asks the system
For the report’s coarse facts, the hook runs a few standard commands. Each gets at most one second and is stopped after that; a missing command, an error or no answer just leaves that value out.
| Fact | Where it comes from |
|---|---|
| Operating system | uname -s: Darwin or Linux. |
| Processor | uname -m. On a Mac, a shell running under Rosetta 2 is recognised with sysctl -n sysctl.proc_translated and reported as arm64. |
| Installed browsers | Whether each browser folder in the table above exists. Nothing inside is read for this. |
| Default browser | macOS: the app macOS opens for https links, from defaults read com.apple.LaunchServices/ (none set means Safari). Linux: xdg-settings get default-web-browser, or else xdg-mime query default x-scheme-handler/https. Only the browser family is kept. |
| Time zone | TZ, else the zone that /etc/localtime links to, else /etc/timezone. |
| Language | LC_ALL, LC_MESSAGES or LANG; on a Mac without them, defaults read -g AppleLocale. |
Besides those, the hook reads its own installation ID file (see what it writes), the environment variables named on this page plus HOME, XDG_CONFIG_HOME, TMPDIR, CLITRAIL_STATE_DIR, CLITRAIL_INSTALLATION_SCOPE, CLITRAIL_INSTALL_PATH and CLITRAIL_HANDOFF, and reads random bytes from /dev/urandom to create its IDs.
What it sends
One request, POST /v1/events, to the CLItrail service named in the notice. The body is one line of JSON; shown here formatted, with example values:
{
"project": "p_3f9a51c0de2b47a8e61d",
"event_id": "9c1e6b2f0a8d4e7fb3c5a1d2e4f60718",
"installation_id": "5b0d2c9e7f1a4b3c8d6e0f2a1b3c4d5e",
"event_type": "install_completed",
"receipts": ["q8ZrV2t1mXo4bN0cY7uLkP3sE9wHfJ5aG6dT_iR-1Qe"],
"platform": "darwin",
"install_path": "ip_4c1d9e02b7a35f68e1c0",
"arch": "arm64",
"browsers": ["chrome", "firefox"],
"default_browser": "chrome",
"timezone": "Europe/Berlin",
"language": "en-US"
}
project- The website’s CLItrail ID, built into the script.
event_id- Random for each report, unless the installer supplies one.
installation_id- Random, created on first run and kept on your computer, so repeated runs of the same install count once.
event_typeinstall_started,install_completedorfirst_run, chosen by the tool’s installer.receipts- The valid receipts it found for this website, or an empty list.
handoff- Sent, with an empty receipts list, when you ran a prepared install command.
platformdarwinorlinux, left out on other systems. The rawunameoutput is never sent.install_path- Which of the tool’s install routes this is (for example its shell installer or its npm package), when the tool’s hook names one. It identifies the route, not you.
archarm64orx86_64, left out for other processors.browsers- The browser families with a folder on this computer:
chrome,chromium,edge,brave,vivaldiorfirefox(Chrome’s other channels count aschrome, Zen asfirefox). Sent only when the hook looked for the folders itself. default_browser- The default browser’s family:
safari,chrome,chromium,firefox,edge,brave,opera,vivaldi,arc,duckduckgoorother. Never the app’s name or path. timezone- A time zone name such as
Europe/Berlin, orUTC. language- A language tag such as
en-US, from your locale.
Every value is checked against a short list or a fixed character set before it is sent, and a value that does not fit is left out. The request uses curl (3-second connection and 5-second overall timeouts) or, without it, GNU wget (one attempt, 5-second timeout, no redirects). It goes only to an https:// address; plain HTTP is accepted only for localhost during development. With neither tool installed, nothing is sent.
Like any web server, the service sees the connection’s IP address. It is never stored in the clear. The service uses it in memory for rate limits and keeps only a keyed hash of it in the request log. If the company is on a paid plan with reconstruction on and no receipt is found, the service also compares a keyed hash of your network, in memory, with the keyed network hashes it briefly keeps from recent website visits, to suggest a probable visit. On the Free plan, the service never looks up or matches the receipts or token; it only counts the install. The next section says what the service keeps and for how long.
What it writes
- Installation ID
~/, one line with a random 32-character ID, readable only by you..local/ state/ clitrail/ <service>/ <website>/ <scope>.id CLITRAIL_STATE_DIRmoves the base folder. Older copies of the hook defaulted to~/. Without.local/ state/ opfs-attribution-shell/ CLITRAIL_STATE_DIR, a current hook that has no ID yet copies a valid one from the matching file there, so an install already counted is not counted again; the old file is left unchanged. Deleting both folders is harmless: a new ID is created next time.- Temporary folder
- One owner-only folder,
clitrail.followed by eight random characters, in$TMPDIR(or/tmp), removed when the hook exits.
Nothing else. It installs nothing, changes no settings and never needs sudo.
What it never does
- Read browsing history, cookies, saved passwords, bookmarks or cache.
- Search Safari’s protected storage or ask the operating system for extra access.
- Send file names, paths, your user name or host name, or environment variables and command output as they are. Only the short values listed above leave your computer.
- Download or install anything, or ask for root.
- Change your install’s outcome. It always exits with status 0; a missing tool or any error ends it quietly.
- Run when
DO_NOT_TRACK=1orCLITRAIL_DISABLE=1is set, or in CI unlessCLITRAIL_ALLOW_CI=1.
Check it yourself
Diagnostics
--doctor explains what the hook would do on your computer: your operating system, processor, time zone, language and default browser as an install would report them (or why one would be left out), the website, install path and service it reports to, which of curl or wget it would use, your opt-out, CI and notice settings, the browsers and profiles it found with a receipt count for each, the browser families it would report, how many folders and files it checked, and whether an install would report. It sends nothing, and prints browser and profile names only: never a folder path, a site name or a receipt. It also runs when an opt-out is set, so you can confirm that the opt-out works.
# A copy bundled with the tool
sh path/to/clitrail.sh --doctor
# A copy served by the website’s CLItrail service
curl -fsS https://SERVICE/v1/projects/WEBSITE_ID/report.sh | sh -s -- --doctor
--dry-run performs the search and prints only counts, such as {"roots":3,, without sending anything.
Read the source
The hook is a single plain-text POSIX shell script: read report.sh. Each website serves the same file at /v1/ (or, for one of its install paths, /v1/), with its service address, website ID, install path ID and (when the website has one) site origin inserted after the first line.
Prepared install commands
Some websites give you a longer command that starts with sh -c '…' clitrail. The website prepared it for browsers whose storage the hook cannot reach, such as Safari:
- It runs the original install command exactly as written, and keeps its exit status.
- Only if the install succeeded, it downloads that website’s hook to a private temporary file and runs it with a handoff token (
--handoff) tied to that visit and usable by one installation. - In this mode the hook reads no browser storage at all and does not look for browser folders. It reports the token instead of receipts, with the other facts above except
browsers, and its output is discarded, so no notice appears.
The token identifies the web visit that prepared the command, not you. The opt-outs above still stop the hook, but the command downloads it first. With DO_NOT_TRACK=1, CLITRAIL_DISABLE=1 or in CI, that download still reaches the CLItrail service, as described under Turn it off; the hook then exits without reading or sending anything.
What the service keeps
The CLItrail service keeps the report for the company whose tool you installed:
- The install event: the event type, the random event ID, the random installation ID, the time it arrived, the install path, the platform, the architecture, the browser families found and the default browser. On paid plans, also the time zone, the language and the attribution result. It is kept for 395 days by default; each company can choose 30 to 1,095 days.
- The request log: the request exactly as received, except that each receipt or token is replaced by its last six characters and a keyed hash, and your IP address by a keyed hash. The company’s members can read it in their dashboard. It is kept for 7 to 90 days, depending on the company’s plan and settings (30 days by default, 7 on the Free plan).
- Our operator copy: a copy of the request log goes to CLItrail’s own storage, so that we can operate, debug and secure the service.
The privacy policy lists every retention period.
Who receives the data
The report goes to the CLItrail service named in the notice, on behalf of the company whose tool you installed. That company decides whether the matched visit is passed on, and to whom:
- Advertising platforms (Google Ads, Meta, TikTok, X) receive identifiers from the website visit the install matched, such as ad click IDs, never the coarse facts about your computer. Meta and TikTok also receive a SHA-256 hash derived from the website ID and the installation ID, never the ID itself. Visits from browsers that send Global Privacy Control are never passed to advertising platforms.
- Google Analytics receives the analytics IDs from that visit and the event ID.
- The company’s own webhook, if it set one up, receives the install event with a SHA-256 hash derived from the website ID and the installation ID, the install path and the attribution result.
The coarse facts about your computer help the company count installs by system and browser. When no receipt is found, they help suggest a probable visit from the same network.
The company that ships the tool decides whether to include the hook. It is responsible for telling you about the hook, and for any consent the law requires where you live.
CLItrail is operated by Vulture Labs, Inc., San Francisco, California, USA. For questions about your data, contact the company whose tool you installed, or write to us at founders@pilotprotocol.network. To help us find your records, include the website and the installation ID from ~/. To report a security problem with the hook, write to the same address; see how to report a security issue. The privacy policy has the full detail.