For developers

What the CLItrail installer hook does

You probably arrived here from one line in your terminal. This page lists everything the hook reads, sends and writes, how to turn it off, and how to check it yourself.

$ curl -fsSL acme.example/install.sh | sh
==> Installing acme 2.4.0
✓ acme installedclitrail: linking this install to your recent visit to https://acme.example (DO_NOT_TRACK=1 turns this off) — https://clitrail.com/installer
An example of the one-line notice the hook prints after an install.

The company behind the tool you installed uses CLItrail, a service operated by Vulture Labs, Inc., to learn which of its web pages lead to installs. When you visited its website, the site saved a small random receipt in your browser. After the install, the hook looks for that receipt and sends one report. The report holds the receipts it found (possibly none), random IDs, the event type, and a few coarse facts about the computer: macOS or Linux®, the processor type, which browsers are installed, the default browser, the time zone and the language. Nothing else about you or your computer is sent.

Describes
report.sh, the hook every website serves
Effective
24 September 2026
Last updated
24 September 2026
Status
In effect
On this page

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:

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

Environment variables the hook respects
VariableEffect
DO_NOT_TRACK=1Exits at once, reading nothing. Any value except 0 or false counts, as the Console Do Not Track convention asks.
CLITRAIL_DISABLE=1The same, for CLItrail only.
CI, GITHUB_ACTIONS, GITLAB_CI, BUILDKITE, CIRCLECI, TF_BUILD, JENKINS_URLAny of these set means CI, and the hook exits at once (CI=false and CI=0 do not count).
CLITRAIL_ALLOW_CI=1Lets the hook run in CI anyway, for pipelines that mean to report.
CLITRAIL_QUIET=1Hides 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.

Folders searched
BrowsermacOS folderLinux folder
ChromeGoogle/Chrome, and the Chrome Beta, Chrome Dev and Chrome Canary folders beside itgoogle-chrome, google-chrome-beta, google-chrome-unstable
ChromiumChromiumchromium; the Snap’s ~/snap/chromium/common/chromium; the Flatpak’s ~/.var/app/org.chromium.Chromium/config/chromium
Microsoft EdgeMicrosoft Edgemicrosoft-edge
BraveBraveSoftware/Brave-BrowserBraveSoftware/Brave-Browser
VivaldiVivaldivivaldi
FirefoxFirefox/Profiles/*/storage/default/*/fsThe same layout under ~/.mozilla/firefox, ~/.config/mozilla/firefox, the Snap’s ~/snap/firefox/common/.mozilla/firefox and the Flatpak’s two folders under ~/.var/app/org.mozilla.firefox
ZenNot searchedThe Firefox layout under ~/.zen and the Flatpak’s ~/.var/app/app.zen_browser.zen/.zen
SafariNever searchedNot applicable

Chromium-based folders sit under ~/Library/Application Support/ 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

  • find lists files between 80 and 4,096 bytes in those folders. It does not follow symbolic links, and folders reached through one are skipped.
  • awk compares each file with the receipt format: exactly eight fields, format "opfs-install-attribution", version 1, 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.

System questions
FactWhere it comes from
Operating systemuname -s: Darwin or Linux.
Processoruname -m. On a Mac, a shell running under Rosetta 2 is recognised with sysctl -n sysctl.proc_translated and reported as arm64.
Installed browsersWhether each browser folder in the table above exists. Nothing inside is read for this.
Default browsermacOS: the app macOS opens for https links, from defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers (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 zoneTZ, else the zone that /etc/localtime links to, else /etc/timezone.
LanguageLC_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:

Request bodyjson
{
  "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_type
install_started, install_completed or first_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.
platform
darwin or linux, left out on other systems. The raw uname output 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.
arch
arm64 or x86_64, left out for other processors.
browsers
The browser families with a folder on this computer: chrome, chromium, edge, brave, vivaldi or firefox (Chrome’s other channels count as chrome, Zen as firefox). 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, duckduckgo or other. Never the app’s name or path.
timezone
A time zone name such as Europe/Berlin, or UTC.
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
~/.local/state/clitrail/<service>/<website>/<scope>.id, one line with a random 32-character ID, readable only by you. CLITRAIL_STATE_DIR moves the base folder. Older copies of the hook defaulted to ~/.local/state/opfs-attribution-shell/. Without 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=1 or CLITRAIL_DISABLE=1 is set, or in CI unless CLITRAIL_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.

Run diagnosticssh
# 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,"markers":1,"files_checked":42,"truncated":false}, 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/projects/<website-id>/report.sh (or, for one of its install paths, /v1/projects/<website-id>/paths/<path-id>/report.sh), 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:

  1. It runs the original install command exactly as written, and keeps its exit status.
  2. 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.
  3. 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 ~/.local/state/clitrail/. 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.