Before you install a Boosthis kit
A Boosthis kit runs inside your own application, in your own process, on your own domain. That is a lot of trust to ask for from a README, so these are the questions an evaluation actually turns on, answered in one sitting.
Every answer below is generated from the same source the kits and the install guides read, and checks on our build fail when one of them stops matching. Nothing here is a description of the product written separately from it.
- What does it send, and what is always on?
- What can it read inside my process?
- What appears on my users' pages?
- What does it put on my own domain?
- What do I have to allow through my Content-Security-Policy?
- What key does it need, and what happens if it leaks?
- How do I know the bytes I received are yours?
- What is the first network call, and when does it happen?
What does it send, and what is always on?
Everything below goes to one place: https://www.boosthis.com/api. An install with no project key configured sends nothing at all.
Sent by every registered install, in every runtime
- A random install identifier. A random UUIDv4 generated on the machine your app runs on, the first time it registers. It is made from nothing about your users, their devices, an IP address or a hostname. Boosthis links it to the project key it registered with, and so to the account that holds that key: that link is how your dashboard, alerts and AI connections show you your own installs.
- Buckets describing a recurring performance problem. When the same performance pattern recurs, the kit sends the kind of problem it detected, a three-level severity bucket and a coarse occurrence-count bucket. The screen or route name is never part of that signature. The Node and browser kits send it beside the signature: each of their submissions says where the latest sighting was — the code-defined route or screen label, put through the same identifier guard as every label, or, where none can be named, a reason from a fixed list (no screen or route was open, the kit does not track them there, or the label was refused). The other kits send no such place with a signature; the React Native kit's call measurements below name screens only to count the calls each one made. From release 1.0.0-alpha.157 the browser kit also sends, beside the signature, what the problem's own measurements pointed at, as fixed words and counts with the time they were read: for failing or slow calls, which kind of destination they went to (your site's own address, a service from a fixed list, or another address), how they failed (before any answer came, turned away by the other side, an error on the other side, timed out, or still unanswered long after) and the labels of the pages that started them; for a burst of calls, the same destination kinds and its page; for a live connection, the kind of service and the gaps between reconnects or messages; for a slow page view, how long scripts from your own address and from other addresses held it up, how long it waited on calls, and the rest. A call's address, path, query string and body stay on the device. From Node kit 1.0.0-alpha.218, Python kit 1.0.0a181, Java and Go kits 1.0.0a154, PHP kit 1.0.93, .NET kit 1.0.98, Ruby kit 1.0.0a95, Rust kit 1.0.0a90 and Elixir kit 1.0.0a87, the server kits also send what the problem's own measurements pointed at beside two problems, as fixed words and counts with the time they were read: for calls to one destination retried without backing off, which kind of destination it was (one of six kinds of outside service, a database or an AI provider), the service's name where it is one on a fixed list of well-known services, how many attempts were counted and over how long; for a burst of calls, how many destinations it reached, over how long, and how many of them were of each kind. Neither carries a host, address, path or status number. From React Native kit 1.0.0-alpha.342, the phone kit sends the same kind of measurements beside failing or slow calls: which kind of destination they went to (your app's own back end, a service from the same fixed list, or another address), how they failed (in the same words, or called off by the app before an answer came) and the labels of the screens that made them, with how many calls each made; the screen labels go through the same identifier guard as every label. A call's address, path, query string and body stay on the phone.
- Buckets describing when one of those problems improves. When a tracked problem gets better, the kit sends the rule and its kind, the before-and-after rating bucket — good, needs-work or poor — how many times it was seen, and the severity and count buckets it was proven under. It is the fact that something improved, never the change that improved it: no source code, diff, file path, screen name, duration or value.
- Error types with a redacted code location. When your app hits an uncaught error, the kit sends the error type, a redacted code location (function name, file basename and line number — never a path, URL, argument or value), a hash derived only from those code-defined tokens, and how many times it recurred. Every crash report, always on and not only in detailed crash mode, also says where in your app it happened: the code-defined label of the screen or route that was open, put through the same identifier guard as every label, or, where none can be named, a reason from a fixed list (no screen or route was open, the kit does not track them there, or the label was refused). Where the crash happened in the middle of an action the kit was tracing, it also carries that action's trace reference: the 32-character trace ID and, where there is one, the 16-character ID of the step in flight. A kit may also state when each crash happened; Boosthis keeps that time only when it is plausible, and otherwise dates the crash by when it arrived. On Android 11 and later and iOS 15 and later, the kit also reads the operating system's own record of a native crash or an "app not responding" kill at the next launch and sends only which it was, the signal or exception-type name, a fixed placeholder for the code location and when it happened — never the system's description, process or thread names, or the native stack. An error your app caught and survived is not sent as a crash by the phone and browser kits. The browser kit decides on the visitor's device, before any address is cut back, whether an error came only from a browser extension, and recognises the browser's own ResizeObserver notices; neither is sent as a crash. Of those it sends only two counts — how many of each it set aside in the crash window — and never an extension's identifier, name or file. An error whose details the browser hid, because its script came from another address, is still sent as a crash, under a fixed stand-in type. A crash that cannot be sent at once waits on the device: the React Native, Flutter, Android and Apple kits keep an unsent crash in the app's own storage, offer it again at each later launch and drop it unsent once it is 30 days old; the browser kit keeps one in the site's local storage, retried and dropped after 30 days, beside a short crash history — for each time the site was opened in the last 24 hours, at most 32 of them, when it began and was last in use, the times of its crashes and how many errors from browser extensions were set aside, times and counts only, each dropped after 24 hours. From React Native kit 1.0.0-alpha.342, every crash report from the phone also carries a word from a fixed list for the kind of mistake (read a value from nothing; used a name that is not defined or not ready yet; called something that is not a function; a value was the wrong type; a request failed and nothing caught it; data could not be read; or not recognised), decided on the phone from the error's wording, which stays there; and, for the first three, a word saying whether the JavaScript engine printed a name from the app's code. The name itself is sent only in detailed crash mode, since a phone cannot prove the engine wrote the wording rather than the app, and then only when the engine copied it as written in the code and it passes the same shape rule and both guards as a website's code name; otherwise the word says the name was withheld or not printed. Boosthis counts each phone crash's reports by screen, keeping the twelve largest, and by the phone system and version band, and on Android the maker, that the reporting install already reported; nothing more about the phone is sent for it.
- Where a crash came from beyond its top frame. A crash report may also say that no stack came with the error at all; which part of the framework caught it, as a word from a fixed list (a part the kit does not know is sent as "other"); for a Flutter debug or profile build, the file basename and line of the widget the framework named as the cause; the first frame in your app's own code, in the same function name, file basename and line form, when the top frame is in the framework, the language SDK or another package; and that it came from a debug build. Which packages are your app's is decided on the device, and that list is never sent. Never the widget's description, a path, an argument or a value.
- A short-lived crash-batch fingerprint. A current kit also sends a non-readable fingerprint for each crash batch so a retry after a lost response cannot add the same occurrences twice. Boosthis also uses it to count a refused batch once, however often it arrives again, and to recognise it if it is accepted later. It is operational duplicate-suppression metadata, not customer crash history: it is excluded from customer exports, retained for no more than 30 days, 1,024 receipts and 10,000 refused batches per install, and erased with the install.
- Anonymous notes about patterns we have no rule for yet. When the kit meets a slow or buggy pattern it has no rule for, it may send the runtime, a coarse category, a severity bucket, a timing bucket, a hash and a recurrence count. Buckets and a hash — never a screen name, a value or your source.
- The kit version and the runtime it runs in. For example, the Boosthis package version together with the language runtime, so a reading can be interpreted against the version that took it.
- Where a server runs. When a server kit registers, the hosting platform it recognised from the markers that platform publishes about itself, as a word from a fixed list; the region code the platform declares, such as us-east-1, as the platform wrote it; and whether this is production, a preview or development — or unknown, where the platform does not say. It is what keeps a preview's numbers from being shown as your live app's. Never a hostname, an address, or any other value read from the machine.
- One-way codes of the names your servers answer under. From Node kit 1.0.0-alpha.230, Python kit 1.0.0a191, Java and Go kits 1.0.0a163, PHP kit 1.0.103, .NET kit 1.0.109, Ruby kit 1.0.0a103, Rust kit 1.0.0a98, Elixir kit 1.0.0a96 and edge kit 1.0.0-alpha.43, a server kit sends with the check-in it already makes a one-way code for each of the 8 names it answered requests under most — the request's own Host, or a name a proxy forwarded, read only at the depth set in BOOSTHIS_FORWARDED_HOST_HOPS — never the name itself. The PHP kit keeps no name at all, only each code and how often it answered under it, in its own state on your server; the edge kit, which makes no check-in, sends the codes with the uploads it already makes. The code is made with a key worked out for your account and never stored, so a code alone does not reveal the name. Boosthis hands the codes only to phone installs of the same project and owner, so that the React Native kit can write its trace header to your own servers; it keeps them until 7 days after the server's last report and deletes them with the install and when the account is closed.
- The version of your own application. On a phone, the kit reads the version your app was built with — CFBundleShortVersionString on iOS, versionName on Android — and sends it alongside the Boosthis version, never merged into it, so a reading can be attributed to the release of YOUR app it came from. It is the version string you ship and nothing else: not the build's contents, not its signing identity, not a build number, not anything about the device.
- The releases your project declares. When a release of your app goes out, your app, a deploy script, a build step or you on your dashboard can tell Boosthis so, from any runtime: the version string as given (at most 60 characters, screened like every other name), when it shipped, how it was declared — through the install's own credential, on the dashboard, read out of a phone build, or handed to the kit by your app — and when it was first and last declared. Declaring the same version again moves only that last time, and an install holds at most 200. Nothing about what the release contains. A developer can withdraw one, and all of them are deleted when the project is erased or the account is closed.
- The names of scheduled jobs, and how each run went. If your app runs work on a schedule, the kit sends the job's name as written in your source, how long each run took, whether it succeeded, and — if your code states one — how often it should run. Never an argument, a payload, a result, a log line or anything the job touched.
Depends on your setup, your kit or its version
- If your app uses the website kit. Choosing the website kit sends these facts by default with every crash report; detailed crash mode is not required. Check that your site uses the website kit, whether through the hosted script tag or the browser package, and check its version on the project page. Website crash cause facts are sent by default with every crash report: a word from a fixed list for the kind of mistake — read a value from nothing; used a name that is not defined or not ready yet; called something that is not a function; a request failed and nothing caught it; data could not be read; a code file from another release failed to load; storage was full or refused; the browser blocked an action; the browser hid the details; or not recognised. The kit decides on the visitor's device from the error's wording; the wording itself stays there. For a name not defined, something not a function, or reading from nothing, it also sends the one variable, function or property name the browser printed from the site's code, if it is shaped like a code name (letters, digits, _ and $, in at most five dot-joined segments and at most 80 characters) and passes both the identifier guard and the route-label guard; otherwise it sends a word saying the name was withheld or not printed. This is the same kind of fact as the function names in crash locations: a code name, not a value or any other part of the message. A word from a fixed list also says whose code was running: written into the page itself, a file on the site's own address, the store platform's own scripts, a named outside service, another address, or no address. For the store platform or a named service, it names which service from the fixed list of platforms, backends, ad pixels, tag managers, analytics, chat widgets, store apps and public code libraries. That decision is made on the device from the full file address, which stays there. For an error with no stack, the browser's file name and line are cut back like every other redacted code location. The mistake word and code name are not part of the crash signature. Boosthis also keeps a count per screen or route label for each crash, keeping the twelve largest, and counts website crash reports by browser family as described under Where an IP address or browser description goes. Website crash browser counts. On arrival, the server reads the browser description the upload already carries and counts reports per crash by browser family from a fixed list: Chrome, Safari, Firefox, Edge, Samsung Internet, Opera, the in-app browsers of Instagram, Facebook, TikTok and Snapchat, another browser, or not said. The description itself is not stored. The counts stay with the crash record. The legal ground is legitimate interest in diagnosing the customer's crashes.
- If full performance details are on (a new project starts with them on). On from a new project's first check-in. Starting the kit with your project key turns them on, and the Full telemetry switch on your Boosthis dashboard turns them off from the app's next launch. The edge kit has a single mode and ignores that switch; it sends them whenever it runs with a key. Open the project on your Boosthis dashboard: its Full telemetry switch shows whether they are on, except in the edge kit, which sends them whenever it runs with a key. (The meter snapshot below has a second switch of its own, so check that separately.)
- If the page map is on, and full details or meter sharing is on. In the browser kit from version 1.0.0-alpha.144, and in the React Native kit from version 1.0.0-alpha.297, it is on unless your code passes sendPageMap: false in the kit's options; an older React Native kit sends it only if your code passes sendPageMap: true. Nothing we send back can turn it on or off. With it on, the map travels with the meter snapshot, so it also needs one of that snapshot's two switches: full performance details, which are on from a new project's first check-in until the Full telemetry switch on your Boosthis dashboard turns them off, or meter sharing switched on for that project. Erasing the install and BOOSTHIS_DISABLED=1 each stop it. Search your own source for sendPageMap, and check the kit's version. In a browser app on kit 1.0.0-alpha.144 or later, or a React Native app on kit 1.0.0-alpha.297 or later, the map is sent unless it says sendPageMap: false; on an older React Native kit, no map is sent unless it says sendPageMap: true, whatever else is on. Where the map is on, check the same two switches as for the meter snapshot: the Full telemetry switch and meter sharing, both on the project in your Boosthis dashboard — either of those on means the map is being sent.
- If full performance details OR meter sharing is on (a new project starts with full details on). Either switch uploads it: full performance details, which are on from a new project's first check-in until the Full telemetry switch on your Boosthis dashboard turns them off (the edge kit has a single mode and sends them whenever it runs with a key), or meter sharing switched on for that project so a connected AI can read your live numbers. Erasing the install and BOOSTHIS_DISABLED=1 each stop it. Two things to check, not one: open the project in your Boosthis dashboard and look at its Full telemetry switch and at whether meter sharing is on. Either one on means the snapshot is being sent; only with both off is it not. The edge kit sends it whenever it runs with a key.
- If your project runs a server kit: the operating system it runs on. Decided by which kit you installed, not by a setting. The server kits send it — Node, Bun, Python, Java, Go, PHP, .NET, Ruby, Rust and Elixir — from the kit release that first reports it, each time the install registers, whatever else is switched on. The phone kits send their own system word inside the meter snapshot (above), and the browser and edge kits leave it out. Erasing the install, and BOOSTHIS_DISABLED=1, each stop it with everything else. Check which kit your project uses: a server kit reports it, and a phone, browser or edge kit does not. Then open the project's performance page in your Boosthis dashboard: beside where the install runs, an "Operating system" line names the system, or says the kit did not report one.
- If the route list is on (it rides the meter snapshot's switches). It travels only when the meter snapshot does — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off), or meter sharing switched on for that project — and it has a switch of its own on top of those: BOOSTHIS_ROUTE_LIST=0 on Node and Python, or setRouteListEnabled(false) in the browser and React Native kits. Erasing the install and BOOSTHIS_DISABLED=1 each stop it with everything else. Two things decide it. First, is the meter snapshot being sent at all — check the Full telemetry switch and meter sharing for that project in your Boosthis dashboard. Then, if it is, open the project's map page: it names where each route came from, and says plainly when the list is switched off or when the kit could not ask your framework.
- If the control census is on (it rides the meter snapshot's switches). It travels only when the meter snapshot does — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off), or meter sharing switched on for that project — and it has a switch of its own on top of those: setControlCensusEnabled(false) in the browser and React Native kits, or BOOSTHIS_CONTROL_CENSUS=0. Erasing the install and BOOSTHIS_DISABLED=1 each stop it with everything else. Only the browser and React Native kits answer this question at all; no server-side kit has a rendered interface to ask. Open the project's map page: it states the control count for the page the latest report was taken on, and says plainly when the census is switched off, when the kit could not read the page, and when a runtime has no interface to ask.
- If you switched a project into detailed crash mode. Detailed crash mode stays off unless turned on. For the website kit, turn it on per project with the Detailed crash reports switch on the project page of your Boosthis dashboard (carried to the kit when it registers), with the hosted script tag attribute data-crash-details="on", or in the kit's code settings. Other kits use their code setting. From release 1.0.0-alpha.342 the React Native kit is the exception: it also reads the same switch, carried to it when it registers, beside its own code setting, and either one turns it on. For the website kit, check the Detailed crash reports switch on the project page, the hosted script tag's data-crash-details attribute, and the kit's code settings: any one can turn it on. For the React Native kit from 1.0.0-alpha.342, check the same switch and the kit's code setting: either one can turn it on. For other kits, check their code setting.
- If your app is a React Native app. Decided by which kit you installed, not by a setting. Only the React Native kit sends any of it. The daily reach tag rides the always-on signals. The other three are collectors the kit starts itself when your code calls enableTelemetry(), with no setting to pass, and what they measure travels only when the meter snapshot does — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off), or meter sharing switched on for that project. Each of those three can be refused by name: autoWrapNetwork: false, trackUnhandledErrors: false, or trackImageWeight: false. A refused reading wires nothing and its meter says it is switched off in this build. Erasing the install and BOOSTHIS_DISABLED=1 each stop all of it with everything else. It applies if you installed the React Native kit in a mobile app; no other runtime sends it. The three the kit starts itself are on unless your own enableTelemetry() call refuses them, so read the options you pass it — search your source for enableTelemetry. The kit's own panel and the project's meters then say, for each of the three, whether it is measuring or switched off in this build.
- If the React Native screen circuit is on, and full details or meter sharing is on. On by default from React Native kit 1.0.0-alpha.297, and off with traceScreens: false in enableTelemetry(); an older kit records nothing unless your code passes traceScreens. It records only while one of the meter snapshot's two switches is on — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off), or meter sharing on the project — and never while your app has recorded that its user withheld permission (setEndUserConsent("withheld")); until then it reads no request's address and makes no screen span, call span or trace header. Erasing the install and BOOSTHIS_DISABLED=1 each stop it. From 1.0.0-alpha.339 its trace header also goes to the application's own servers, recognised by the one-way codes their server kit sends, unless your code passes traceScreens: { ownServers: false }. The separate Network meter is unchanged and remains blind — it still reads no URL, host, path or status. Check the React Native kit's version and search the app's enableTelemetry() call for traceScreens. On 1.0.0-alpha.297 or later without traceScreens: false, the circuit is recorded whenever one of the meter snapshot's two switches is on; on an older kit only an explicit traceScreens records it. A propagateTo list names hosts that may receive the trace header; from 1.0.0-alpha.339 the application's own servers receive it too, recognised by the one-way codes their server kit sends, unless enableTelemetry() passes traceScreens: { ownServers: false }; with no named host and that switch off, no host receives it.
- If your web pages call your own server (browser kit 1.0.0-alpha.144 and later). On by default from browser kit 1.0.0-alpha.144, and off with traceSameOrigin: false in the kit's options. Each fetch or XHR request a page makes to its own origin then carries three trace headers to your server and is recorded as one traced step. A request to any other host carries no trace header unless your code names that host in tracePropagateTo. The step travels on the same two switches as the meter snapshot — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off), or meter sharing on the project — and erasing the install and BOOSTHIS_DISABLED=1 each stop it. Check the browser kit's version and search your source for traceSameOrigin: false. On 1.0.0-alpha.144 or later without that line, a page's requests to its own origin are traced whenever one of the meter snapshot's two switches is on. tracePropagateTo lists the only other hosts that receive the trace headers; with no host named, no other host receives them.
- If your server runs a Boosthis server kit (Node 1.0.0-alpha.200, Python 1.0.0a168, PHP 1.0.83, Java and Go 1.0.0a146, Ruby 1.0.0a86, .NET 1.0.89, Rust 1.0.0a81, Elixir 1.0.0a78, edge 1.0.0-alpha.32 and later). Decided by which kit your server runs, and its release: the Node kit from 1.0.0-alpha.200, the Python kit from 1.0.0a168, the PHP kit from 1.0.83, the Java and Go kits from 1.0.0a146, the Ruby kit from 1.0.0a86, the .NET kit from 1.0.89, the Rust kit from 1.0.0a81, the Elixir kit from 1.0.0a78 and the edge kit from 1.0.0-alpha.32 send these, and no browser or phone kit does. They travel only together with the request's own traced step, on the same two switches as the meter snapshot — full performance details (on from a new project's first check-in until the Full telemetry switch turns them off, and always on in the edge kit), or meter sharing on the project — and erasing the install and BOOSTHIS_DISABLED=1 each stop them. Check which kit your server runs and its version. On one of the releases above or later, a traced request also sends one linked step for each kind of database or outside-service call it made, whenever one of the meter snapshot's two switches is on.
- If your server runs the Node or Python kit and calls your own services (on by default). On by default in the Node and Python kits, and changed by one setting on your server, BOOSTHIS_TRACE_PROPAGATION: none turns it off, internal keeps internal addresses only, and a list of hosts replaces the default. With nothing set, the kit adds three trace headers to its calls to addresses that cannot be on the public internet and, from Node 1.0.0-alpha.204 and Python 1.0.0a171, to hosts on the same site as the request being served. No other company's own domain receives them unless you name it, nothing new is sent to Boosthis, and BOOSTHIS_DISABLED=1 stops it too. Check which kit your server runs and its version, and whether BOOSTHIS_TRACE_PROPAGATION is set where it runs. The kit's own status page states the rule in force in one line.
- If your app is a Flutter app. Decided by which kit you installed, not by a setting: only the Flutter kit does this. It watches the requests your app sends through Dart's own HTTP client, which covers package:http and Dio on its default adapter, and the optional boosthis_dio add-on watches every call a Dio instance makes once your code adds its interceptor. While the kit is not switched off, each such request carries three trace headers to whatever host it goes to, and only BOOSTHIS_DISABLED, set when the app is built, stops them. The traced step for each call is sent only while full details are on for the project, and forget stops anything being sent. In a Flutter app running in a browser, the kit and the add-on stand down: nothing is measured and no header is added. It applies if your app runs the Flutter kit; no other kit writes trace headers to every host. Search your build configuration for BOOSTHIS_DISABLED: unless it is set when the app is built, every request through Dart's HTTP client carries the three headers, whatever host it goes to. Search your source for BoosthisDioInterceptor to see whether the add-on is in use, and open the project on your Boosthis dashboard to see whether full details are on.
- If your phone app's code wraps a tap in trackTap (Flutter kit 0.1.95 or iPhone kit 0.2.76 and later). Decided by your own code, not by a setting: from Flutter kit 0.1.95 and iPhone kit 0.2.76, a tap is sent only where your app wraps that tap's handler in trackTap (on iPhone, trackTap or trackTapAsync). Neither kit watches touches by itself, and the Android kit sends no tap at all. A wrapped tap's step travels with the kit's other traced steps, and only when they may travel; removing the wrapper stops it, and erasing the install and BOOSTHIS_DISABLED=1 each stop it like everything else. Search your source for trackTap and trackTapAsync, and check the kit's version. On Flutter 0.1.95 or iPhone 0.2.76 or later, each wrapped tap that caused a call the kit records sends one tap step; with no wrapper in your code, no tap step is sent.
- If your app runs in visitors' web browsers (browser kit 1.0.0-alpha.146 and later). Decided by which kit you installed, not by a setting. Only the browser kit sends it — the hosted script tag and the browser module alike — from 1.0.0-alpha.146, and it rides the always-on signals: the uploads and the registration the kit already makes carry it, with no request added. Erasing the install (forget()) deletes the day's value from the browser, and a visitor who switches off the kit's storage on their device erases it with everything else the kit keeps there. It applies if your site runs the browser kit at 1.0.0-alpha.146 or later; no other runtime sends it. A site loading the hosted script tag runs the release Boosthis serves at that moment, and a site bundling the browser module runs the version its lockfile names; your project's page on Boosthis shows the kit versions your visitors' browsers reported.
- The name your app registers under, and the name you type for the project. An app sends a name only if your own code or build gives it one: the field is optional, and an install that sends none is listed by its identifier instead. The project name is not sent by anything — you type it on your Boosthis dashboard, and no kit ever sees it. Both are on your dashboard: the app's name on the install's own row, the project's name at the top of the project. Search your own source or build configuration for the name you pass to the kit; if you pass none, none is sent. Renaming a project renames it for every key in its rotation line, so an app still shipping an older key reports under the new name from its next handshake.
- If a developer uploads a build map so crash frames read as source. Nothing is uploaded unless a developer does it — by hand from the project page, or by a step added to your own build. No kit fetches a build map's contents, and Boosthis never asks your build system for one; a project that has uploaded none holds none. One kit asks a question about a map rather than for its contents: the browser kit's development-posture check makes one HEAD request to your own site for the first script's .map address and keeps only whether the answer was OK, as a 1 or a 0. Nothing is downloaded, and the address is neither stored nor sent. Upload one and your crash frames resolve to your own file and line; upload none and they read as the compiled position, which is the only difference. The project page lists every build map the project holds: which release, which generated file, when it arrived and the date it ages out. An empty list means none is held right now — either none was ever uploaded, or the ones that were have aged out. Each map is kept for 90 days from the day it arrived and is then deleted; re-uploading the same release replaces the map already held rather than adding a second, and restarts that clock.
Never sent, in any mode
- IP addresses. Never as part of telemetry, and never used to identify anyone or joined to telemetry. Like nearly every web service our server keeps operational request logs. In place of the address, each line carries a tag that changes every day and cannot be turned back into it, and the logs are deleted after 30 days, older lines that still held the full address included. Rate limits count requests per project key or install, never per address.
- Anything that identifies one of your users. User IDs, email addresses, names, phone numbers, postal addresses, device IDs or any other identifier. A denylist blocks them on the device and again on our server, and a batch that trips it is rejected whole.
- The contents of requests and responses. Request bodies, response bodies, query strings and headers are never collected and never sent. Reading something inside your process and collecting it are two different things, and this is the whole of the first. The one exception is the AI-call meter: to tell repeated prompts apart it reads up to the first 4,096 characters of an outbound request body to an AI provider, inside your own process, and reduces it immediately to a single number — no body text is stored, uploaded or recoverable. Nine other bounded reads work the same way — inside your own process, reduced on the spot to a number, a flag or a label from a fixed list: the same meter reads the provider's reply as it streams back, holding at most 8,192 characters of a partial line at a time, to take the token counts the provider states in it; when a provider refuses a call, the meter takes its own copy of that error reply to say which kind of refusal it was — the Python kit stops accumulating at 8,192 characters, and in Node and the browser the runtime hands the reply over as one string, of which anything longer than 8,192 characters is dropped without being examined; the leak watch reads up to the first 8,192 bytes of an ERROR response your app is about to return, to see whether a stack trace or a secret-shaped string is about to reach one of your users; in the browser, and only if you switch it on, a copy of a JSON answer to one of your app's own calls is read to see whether it is error-shaped despite its 200: the copy is taken only where the server declared a length of 65,536 bytes or less, and because a declared length can understate what arrives, the copy the browser hands back is checked again and dropped unexamined past 65,536 characters; on an MCP endpoint your app serves, up to the first 64 KB of the JSON-RPC request is mirrored as your app reads it, to take the method name out of the envelope — never the arguments, and never a byte of what the app itself receives is changed; in the edge kit, the repeated-work and database readings fold up to 4,096 characters at each end of an outgoing call's query, and of its body when the body is text, into one number, so that two different calls to the same address are never counted as the same work; the number is compared inside the request and dropped with it; the cookie check reads the Set-Cookie headers your app sends — at most 20 per response, and at most 2,048 characters of the attribute tail on each, which is 2,048 bytes in the kits whose strings are bytes — for the Secure, HttpOnly and SameSite flags and the size, never the value; the Node kit also reads each cookie's name, up to 1,024 characters and stopping where the name ends, and matches it against a fixed word list only to decide whether a missing HttpOnly matters — a session or token cookie needs it, a CSRF or preference cookie is meant to be read by your page's own script — and the name is never kept or sent; the Cookie header your users send is not read at all; a fixed list of headers is looked up by name and turned straight into a number or a fixed label: the content type, length and encoding, the Accept header your user's browser sent, whether your reply is marked as a download (Content-Disposition), the cache instructions and validators on your app's own reply (Cache-Control, ETag, Last-Modified), the elapsed-milliseconds offset a previous instrumented hop wrote, the queue-start timestamp a proxy adds, the presence (never the value) of a reverse proxy's forwarding headers (X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP), whether the Host and Origin headers on a request for one of the kit's local-only answers name this machine (a yes or no that decides whether the kit answers; neither value is kept), whether a request carried an Origin header and whether the cross-origin headers on your app's own reply (Access-Control-Allow-Origin and Access-Control-Allow-Credentials) answer any origin, repeat that Origin back, or pair any origin with credentials (yes-or-no answers that become counts; no origin or header value is kept), a hosting platform's cache verdict, an AI provider's rate-limit headroom, retry delay and service-side processing time, and whether an authorization header or an authentication challenge is present — never its value, and never a header nobody named: the list is the claim, and a kit that starts reading a header outside it fails our own build before it ships; and the query values the kit reads on its way OUT are the ones in URLs the kit itself builds for its own uploads, read key by key so the guard can refuse anything identifier-shaped before it leaves your process; your app's own query strings are cut off, unread, before a route label is made. One bounded read is not a reduction at all, because what it holds is your own page on its way to your own visitor: where the dashboard bubble is switched on, an HTML page is buffered so the one script tag can be written into it — up to 2 MB, 4 MB on .NET, past which the page is passed through untouched. Those bytes are handed back with one script tag added and nothing taken from them. One read is neither narrowed to a label from a fixed list nor limited in length, because the value IS the question being asked of the kit's own endpoint: the kit's own local read endpoints — the panel and the JSON reads it serves under its own path, behind their loopback or key gate — read the query of requests made TO THEM: how many rows to return, and which route name to filter to; the row count becomes a number, the route name is compared, exactly as the caller wrote it and with no limit on its length, against the route names the kit is already holding, so a name matching none of them simply returns nothing; neither value is recorded, and these endpoints read no other query string in your app. Nothing checks it against a list first, no cap is put on how long it may be, and nothing is derived from it or kept once the reply has gone. One bounded read does not stay inside your process, and the difference is set out here rather than left to be inferred: on a request arriving at your app, the kits on your servers read two headers for their VALUE rather than for a label — the trace id and the caller's span id that another Boosthis-instrumented service of yours wrote on it. A value is taken only if it is exactly the shape the kits mint (32 hexadecimal characters for the trace, 16 for the span), and anything else is discarded, the trace id replaced with a fresh random one. An adopted trace id then IS that request's trace id: it is put on the hops your app makes while handling the request, and it is uploaded with that request's timings; an adopted span id is uploaded as the parent of that request's own step. That is the only way one action crossing several of your services can be shown as one line rather than several unrelated ones. Those two ids are the only things read here that are carried onward or uploaded with your app's readings; each is an identifier the kits mint and nothing is derived from it, and no other header's value is read. Two reads send what they hold to Boosthis, because what is held is a credential Boosthis itself issued and only Boosthis can say whether it still stands: where the dashboard bubble is switched on, the kit looks in a page's address for one key, boosthis — a server kit in the address of each page request your app receives, the browser kit in the page's own address — and only for one of three answers: a one-time owner proof from your project's page on Boosthis, which is sent to Boosthis to be swapped for an owner pass that lasts about 30 days (a server kit keeps it in a cookie of its own, the browser kit in that browser's own storage); off, which forgets that pass; or on, which only a server kit honours, and only from the machine itself; when the kit acts on one, the browser is left at the same address without that key, no other key in the address is used or kept, and the switch-off control in a server kit's bubble only ever writes that key, as off, into the address of the page it is on; and a server kit looks in the cookie header of each request your app receives for two cookies of its own and uses no other: boosthis_owner_pass, which holds the owner pass Boosthis issued through an owner link, and boosthis_owner, which holds only 1, marking the browser as switched on; the pass is sent to Boosthis to ask whether it still stands, and the kit keeps the answer for up to ten minutes (thirty seconds when Boosthis did not answer); a request without that cookie makes no call at all; the bubble's script in the page reads the second cookie to decide whether to draw; where a kit can only set its cookies by rewriting the response's Set-Cookie lines, it takes the ones your app already put there and hands them back unchanged beside its own; and on a page that carries the bubble it adds Cookie to your app's Vary header, keeping what was there. One read is neither bounded nor reduced to a number, and you ask for it yourself: one endpoint the Python kit's panel serves takes a POST — the rule matcher — and it reads that request's body in full, because the code you paste in IS the question being asked: it is matched against the rule book inside your own process, behind the same loopback or key gate as the rest of the panel, the answer is a list of rule names, and neither the code nor any part of it is stored, logged or uploaded. Apart from the trace id and span id another instrumented service already put on the request, the owner link's one key, boosthis, in a page's address and the kit's own two owner cookies, none of what is read here is stored, uploaded or recoverable from what is uploaded, and the guard on the upload path refuses outright any payload carrying a request or response body, a header, a cookie or a query value — and those ids are uploaded as fields of their own, never as the header they arrived in, and the owner proof and pass go to Boosthis on calls of their own, never inside an upload.
- Raw stack traces, raw error messages and log lines. A crash report carries a redacted code location and an error type, the screen or route it happened on (or a reason from a fixed list where none can be named) and, where it happened during a traced action, that action's trace references. Website reports also carry the closed mistake and whose-code words, the screened code name or its absence reason, and service word described in the website-kit group, with browser-family and screen or route counts. Phone reports also carry the mistake word described above, and from React Native kit 1.0.0-alpha.342 a word saying whether a code name was printed, with screen, system-band and maker counts. Only in detailed crash mode, it adds a first line that passed the guard, cut to at most 300 characters, and up to 20 frames reduced the same way, and the React Native kit adds the screened code name. Never a raw stack and never a value. A meter that watches logged errors records how many and how often, never the line, the message, the logger or the level.
- Anything about the processes your app spawns. Command lines, arguments, environments and output are never sent. The subprocess meter records only how many spawns happened and how often.
- The names of the things the meters count. Threads, workers, modules, handles, timers, files, pools and queues are counted, never named. A meter reports how many and how long, never which. The one name a meter carries is a well-known outside service's, from a fixed list, on the server kits' outside-services meter — never its host.
- Your source code. Boosthis never reads your codebase. No kit opens, scans, indexes or uploads a source file, and no source text is stored on our servers — including from a build map, whose embedded copy of your source is discarded the moment the map arrives and is never written down. What we can say about a line of your code comes from what your own build already put in a stack frame.
- Your hostnames and your environment. Hostnames, and the values of your environment variables, are never sent, with two exceptions. The first is the region code a hosting platform publishes about itself, such as us-east-1, sent with where a server runs (above). The second is an outside service your app calls in a burst: where a kit watches your app's outgoing calls for one host being called again and again without backing off — five or more calls to it within ten seconds — it reports that as a problem named after the host alone, such as api.example.com, never a path, query string, port or anything else from the address. The Node kit watches for this on its own, and some other kits only where your code hands them each outgoing call. A kit does read environment variables inside your own process — its own settings, and the markers a hosting platform publishes about itself — and, apart from that region code, what leaves is a word from a fixed list or a number worked out there, never the value it read. The browser kit's development-posture check, for one, reads NODE_ENV to report whether a development build is running, as a 1 or a 0; and the browser kit reads NODE_ENV and the page's own hostname to recognise a developer's own page — a development build, or a loopback address such as localhost — and when it is one, sends only the word "development" and reports that page as the project's separate development install. What a server kit sends about the names it answers requests under is a one-way code for each, never the name (see "One-way codes of the names your servers answer under" above). File paths are never sent from your machine either — with one exception, named here rather than further down the page: a build map a developer uploads keeps its own original source paths, for as long as that map is kept. Two things read paths without sending them: a kit reads the paths in an error's stack inside your process only to cut each one back to its file name, so a crash frame's file is a basename; and the Boosthis editor extension's chat reader opens the conversation files a developer's AI assistant keeps on their own disk.
- Anything your users see or say. Screen contents, screenshots, video and audio are never captured.
The field-by-field version, in the words your own privacy policy needs, is on what Boosthis collects inside your app.
What can it read inside my process?
Reading something inside your process and collecting it are two different things. The one exception is the AI-call meter: to tell repeated prompts apart it reads up to the first 4,096 characters of an outbound request body to an AI provider, inside your own process, and reduces it immediately to a single number — no body text is stored, uploaded or recoverable. Nine other bounded reads work the same way — the token counts a provider states in its reply, which kind of refusal a provider's error reply is, a stack trace or a secret about to reach one of your users, an error-shaped JSON answer returned with a 200, the method name in a tool-call request your app serves, the query and body of an outgoing call, folded into one number, the flags on the cookies your app sets, a fixed list of named headers and the query of the kit's own upload URLs — each reduced on the spot to a number, a flag or a label from a fixed list, and each set out with its own limit in the Privacy Policy. One bounded read is not a reduction at all — the HTML page the dashboard bubble is written into — held only long enough for the one script tag to be written in, then handed back with nothing taken from it. One read is neither narrowed to a label from a fixed list nor limited in length — the query on requests to the kit's own read endpoints — used as the caller wrote it to pick which of the kit's own rows come back, then dropped with the reply. One bounded read does not stay inside your process — the trace id and span id another instrumented service already put on the request — each adopted only when it is exactly the shape the kits mint, then carried onward or uploaded with that request's timings so one action across your services reads as one. Two reads send what they hold to Boosthis — the owner link's one key, boosthis, in a page's address; and the kit's own two owner cookies — and what is sent is only a credential Boosthis itself issued, returned to be checked on a call of its own, never inside an upload. One read is neither bounded nor reduced to a number, and you ask for it yourself: the code you post to the kit's own rule matcher. Apart from the trace id and span id another instrumented service already put on the request, the owner link's one key, boosthis, in a page's address and the kit's own two owner cookies, none of what is read here is stored, uploaded or recoverable from what is uploaded, and the guard on the upload path refuses outright any payload carrying a request or response body, a header, a cookie or a query value — and those ids are uploaded as fields of their own, never as the header they arrived in, and the owner proof and pass go to Boosthis on calls of their own, never inside an upload.
What appears on my users' pages?
The floating bubble is for the app's owner only, in every kit. On a live site a server kit or the browser kit draws it only in a browser switched on with the owner link from the project's page on Boosthis; on the developer's own machine, a server kit draws it in a browser that opened one of the app's pages with ?boosthis=on there, and the browser kit draws it on localhost. A phone kit draws it in development builds only. ?boosthis=off, or the switch-off control in the bubble, switches a browser off. No setting shows it to anyone else. BOOSTHIS_BUBBLE set to 0 hides it everywhere and the kit carries on measuring; BOOSTHIS_DISABLED stops the kit altogether.
Kits that draw it: Node.js, Python, Go, Java, PHP, Ruby, .NET, Rust, Elixir, Browser / web, React Native / Expo, Flutter, Swift / iOS, Kotlin / Android. Kits that show your users nothing at all: Edge / V8 isolates.
What else the kit touches on the page
- One element, in a closed shadow root. The bubble is a single
divcarrying adata-boosthis-bubbleattribute, with a closed shadow root inside it. Its styles are onestyleelement inside that root, so none of them reach your page and none of your page's styles reach the bubble. Nothing else in your document is added, moved or restyled. - Web Storage, under one namespace. Every value the kit keeps is stored under the
boosthis:prefix inlocalStorage, falling back to memory where the store is unreachable. The kit reads no key outside its own prefix and enumerates none. - No cookies. The kit never reads or writes
document.cookie, and the uploads carry no credential of a visitor's — only the project key you configured. - A passive policy listener. The kit listens for the browser's own
securitypolicyviolationevent so it can print one console line if your policy is blocking it. The event only fires when something is actually blocked, so a page with no Content-Security-Policy sees no extra work and no probe request either way.
What does it put on my own domain?
A kit mounts addresses inside your application, on whatever domain you deploy to. Most answer local requests only. These answer only your own browser, switched on with the owner link from your project's page on Boosthis, or a direct connection from the machine itself; anyone else gets your app's own answer for an address it does not know: /_boosthis, /_boosthis/, /_boosthis/panel, /_boosthis/pulse.
The full inventory, per runtime, with what each address returns and what it deliberately omits, is on what Boosthis adds to your app.
What do I have to allow through my Content-Security-Policy?
One origin, https://www.boosthis.com, on connect-src and script-src. No wildcard is needed, no other host is contacted, and a page that sends no Content-Security-Policy at all needs nothing.
connect-src— needed by every browser install, however the kit got onto the page — this is the one that carries the uploads. Without it, the browser refuses each upload, the kit keeps running and measuring, and nothing ever arrives.script-src— needed by only pages that load the pre-built script tag. An install bundled through your own build step needs nothing here — the code is already yours. Without it, the script never loads at all, and nothing measures.style-src— met by only the on-page bubble, and only if you want it to look right. The bubble's styles are astyleelement the kit creates at runtime inside its own shadow root, and the kit takes no nonce, so a policy that refuses inline styles refuses that one too. Without it, the bubble draws unstyled — every reading is still taken, still uploaded and still on your dashboard. If you would rather not widenstyle-srcfor someone else's element, the honest fix is to turn the bubble off rather than to allow inline styles across your whole page.script-src-attr— met by nothing you need. The pre-built script tag carries anonerrorattribute whose only job is to print one console line when the kit file never arrives, and an inline handler is exactly what a strict policy refuses (script-srccovers this wherescript-src-attris not set). Without it, our console line never prints. A blocked or mistyped script then looks like a page with nothing installed, so read your own policy violation report instead — the browser's report names the script we could not tell you about.
In a policy, that is — schematically, because both directives are ones you almost certainly already have. Add the origin to the ones you have rather than pasting this as a policy of its own; the … stands for whatever sources you already allow, and a second script-src appended after your first one narrows the first rather than extending it.
connect-src … https://www.boosthis.com; script-src … https://www.boosthis.com;
Two more directives are worth knowing about and are deliberately NOT in that line, because neither is needed for a single reading to arrive: style-src and script-src-attr. Both would have to allow 'unsafe-inline', which is a real widening of your policy for someone else's code. We would rather tell you what keeping the policy costs.
The pre-built script tag, for pages with no build step, is served from https://www.boosthis.com/kit.js — the same origin.
Every address the kit posts to, under https://www.boosthis.com/api
/installs/consent— the install identifier, the runtime name, the kit version, the collection mode this build's code asked for, and the display name if one was set. The project key rides as a header. This is the first call./samples— individual timing readings, as numbers against a route label./snapshots— the periodic roll-up of this install's meters./crashes— crash and error reports, with the message and stack put through the kit's own denylist first./spans— trace spans — a method and a route label per span, never a URL or a body./candidates— candidate rules the kit noticed, as closed-vocabulary terms./resolutions— whether a previously reported problem stopped happening./job-runs— background job and scheduled-task timings, where the kit watches them. Server and device kits only./installs/forget— nothing but the install identifier and its own token — the erase lever, and the only one of these a person triggers deliberately.
A check on our build reads these addresses back out of the kit sources and fails when this list misses one, or names one no kit posts to.
What key does it need, and what happens if it leaks?
One project key, minted from the Project keys page of your Boosthis dashboard. Server-side kits read it from the environment — the shared BOOSTHIS_PROJECT_KEY where nothing more specific is wired in — so it never travels inside anything a user downloads.
the React Native kit, the browser kit, the Flutter kit, the Swift kit and the Kotlin kit are different: their key comes to rest inside a published build, where anyone holding a copy of the app can read it. We mint those with the narrowest scope we have, and say plainly what that leaves:
A key lifted out of a published build can still register installs under this project and send made-up readings into its meters, which is a real cost and the one this scope does not remove. What it cannot do is read those meters back — no measurements, traces, structure, coverage or exposure findings over the Boosthis AI connection — and it reaches no account, billing, team or other project.
A key placed in public can be revoked on its own from the Project keys page of the Boosthis dashboard, which leaves every other key on the account running.
How do I know the bytes I received are yours?
Be clear about what the check in front of you does — and run the one that matches what you downloaded, because the archives come in two shapes and the wrong command finds nothing to read.
Node.js, Python, Go, Java, PHP, Ruby, .NET, Rust, Elixir, Browser / web, Flutter, Swift / iOS, Kotlin / Android, Edge / V8 isolates
The archive ships a BOOSTHIS_CHECKSUMS.txt listing a SHA-256 for each file it carries. From the folder you unpacked into:
sha256sum -c BOOSTHIS_CHECKSUMS.txt
On macOS without GNU coreutils, shasum -a 256 -c BOOSTHIS_CHECKSUMS.txt does the same thing. It needs no network, no account and no Boosthis tooling.
React Native / Expo
That archive carries no checksum list. Its packer hashes every file it ships into a boosthis-kit-manifest.json at the archive root, and the same archive brings the tool that re-hashes them where you unpacked them:
node lib/boosthis-runtime-rn/cli/init.js verify .
The same offline check against a different file. A sha256sum -c BOOSTHIS_CHECKSUMS.txt here would fail for the uninteresting reason that the file it wants is not in this archive.
What either check proves: every file the archive says it carries is on your disk, byte-for-byte as it was packed. It catches a truncated download, a file that failed to write, and an elision made by a transport in between — all of which otherwise produce wiring that looks installed and measures nothing. It says nothing about files the list does not name, so unpack into an empty folder: anything an older copy left behind is invisible to both checks.
What neither check proves: where the bytes came from. The list — or the manifest — travels inside the same archive it describes, so anyone able to alter the archive could alter its own record of itself with it.
What does: the signed release list. Beside each kit archive we publish a release list: the SHA-256 of the archive as served and of each file inside it, with the kit version, signed with the Boosthis kit release key. The install steps we serve fetch that list, its signature and our signers file, check the signature with ssh-keygen -Y verify and the archive against the list, and stop with the archive still packed when either check fails.
New releases are signed with the key kr-2026-10-c, fingerprint SHA256:9U7YAkn2DM2UqngyJxMB6hlzO8Puql0cb6aa9Jb885w. The signers file at https://www.boosthis.com/.well-known/boosthis-kit-release-keys carries its public half and the history of earlier keys and revocations, and security.txt names the same key.
The signing key is kept apart from the deployment that serves the downloads: our production build stops when it can see the key, so a change to that deployment is unable to sign a release. The limit, stated plainly: the signers file is served from our own site too, so a first install trusts our site for the key. A project that keeps boosthis-kit-release-keys and leaves out its download line on later updates goes on checking against the key it already holds, whatever our site serves later.
If you received a kit some other way — pasted, forwarded, or handed over by an AI connection you did not open yourself — the checksum check tells you the copy is intact, not that it is ours. Fetch it again from your own dashboard, where the steps check the signature.
What is the first network call, and when does it happen?
A POST to https://www.boosthis.com/api/installs/consent, carrying the install identifier, the runtime name, the kit version, the collection mode this build's code asked for, and the display name if one was set. The project key rides as a header. This is the first call.
It fires on initialisation, not after any human action: there is no banner, no click and no consent dialog in front of it, because the call is the kit telling us that an install of your project exists. An install with no project key configured makes no call at all.
WHEN EACH KIT FIRST CHECKS IN (so quiet is not mistaken for broken): a browser page registers within a couple of seconds of the page loading, and a mobile-app kit registers shortly after launch — both show up almost immediately. A back-end kit registers on the service's FIRST REAL REQUEST instead: it consents lazily off the reporting path rather than at boot, so a back end that has been started but never called reports as never checked in, and that is the expected state rather than a fault.
If that did not settle it
- What Boosthis collects inside your app — every field, where it is processed, how long it is kept, and paste-able wording for your own privacy policy.
- What Boosthis adds to your app — the visible element and every address your app starts answering, per runtime.
- Privacy Policy and Terms of Service — the legal source. This page describes behaviour; those two govern it.
- support@boosthis.com — a question about your own deployment reaches a person.