Security · 2 November 2026
What can an analytics script do to your site?
A script tag is not a guest on your page. Once it loads, it runs with the same rights as your own code. It can read the DOM, including what someone types into a form. It can rewrite links and buttons. It can send what it read to any server your Content-Security-Policy allows, and if you have no CSP, to any server at all.
That is true of every analytics tag, ours included. On 30 October 2026 Fathom wrote up how they protect their script after a third-party script on another company's site was replaced with a malicious one. Their post is worth reading: How we secure our analytics script. Below is the same question asked of our own file, plus the controls you can put around any tag, whoever makes it.
Our whole script, read line by line
On 2 November 2026 /cf.js was 1,515 bytes raw and 930 bytes with gzip -9 -n. Its md5 was 496076dccd8b36bea711e153c8253f3e, which is also the ETag the server sends. Open https://www.cookiefreeanalytics.com/cf.js in a browser tab and you can read all of it in a minute. It does five things.
One: it reads the data-site attribute from its own script tag. No attribute, no tracking; the script stops there.
Two: it builds one small JSON body. That is the site ID (sid), the path, the hostname, and the referrer with its query string and fragment cut off. On the first view of a page load it also keeps up to five utm_ tags from the URL. If you call cfa('signup'), it adds that event name. Every field is described in What a 1 KB analytics script actually sends.
Three: it sends that body with navigator.sendBeacon, and if that fails, with one fetch POST. Both go to /api/collect on the same origin the script was loaded from. It does not read a response, and it does not take instructions back.
Four: it wraps history.pushState and history.replaceState and listens for popstate, so a single-page app counts a view when the route changes.
Five: unless the tag has data-badge="0" or the page already has an element with data-cfa-link, it adds one small "Powered by Cookie Free Analytics" link in the corner. That is the Hobby badge.
What it does not do, also visible in the file: no cookies, no localStorage, no second script loaded, no eval, no reading of form fields, no fingerprinting. Click tracking is a separate file (cf-clicks.js) that you add yourself only if you want it.
Same file for every customer
The install in our docs is one URL, /cf.js, with your site ID in the data-site attribute. Every customer gets the same static file, with the same ETag. There is no per-customer build to swap out quietly for one site. If that file ever changed, it would change for everyone at once, and anyone could see it by comparing the hash.
One honest footnote. Some installs use a hosted path instead, /t/ followed by the site ID, which is also what our assistant install helper hands out. That path serves the same source file, from the same deploy, with two values filled in when the request comes in: your site ID, and whether the Hobby badge shows (paid sites hide it). It is not a different script or a separate build, but the bytes differ per site, so its hash is not the shared one. If you want the one-file-for-everyone property, use /cf.js with data-site, as /docs shows.
How to limit any analytics tag
Start with a Content-Security-Policy. script-src lists where scripts may load from. connect-src lists where scripts may send data. A tag that is allowed to load but not allowed to connect anywhere new cannot quietly ship your form data to a stranger's server. For our script loaded from our host, the analytics part of the policy is: script-src 'self' https://www.cookiefreeanalytics.com; connect-src 'self' https://www.cookiefreeanalytics.com. Add those hosts to the policy you already have; do not replace it.
Better still, serve the script from your own domain. With a path rewrite, /cf.js and /api/collect live on your own host, so the policy can stay at script-src 'self'; connect-src 'self' with no outside host at all. With a subdomain such as c.yourdomain.com, list that one host. Both setups are in Serve it from your own domain.
Then keep the number of tags small. Every tag is code you did not write, running with your rights. Size is a rough proxy for how much code that is: on 2 November 2026, GA4's gtag.js with a placeholder ID was 155,039 bytes gzipped, against 930 for ours. The method is in Analytics script size compared.
Subresource Integrity, and why we don't offer it today
Subresource Integrity (SRI) is an integrity attribute on the script tag holding a hash of the file you expect. If the bytes the browser downloads do not match the hash, the browser refuses to run them. It is a strong check against a swapped file.
It only works when the URL always returns the same bytes. /cf.js is an unversioned URL: when we ship a fix, the file at that URL changes, the old hash stops matching, and a site that pinned it would stop counting until someone updated the tag. We do not offer a pinned, versioned tracker URL today, so we do not offer SRI for it. If SRI matters to you, the self-hosting route above gets you most of the way: the file comes from your own host, and you decide when it changes.
When this matters less
If your site is a brochure with no logins, no forms that take personal data, and no payments, a compromised tag has much less to reach. The controls above are still cheap, but they are not urgent. If people sign in, type card numbers, or connect a wallet on your pages, treat every third-party tag as part of your attack surface: allowlist it, self-host what you can, and remove what you do not use. That goes for analytics too.
Comparing tools on this point? Ask each vendor whether every customer gets the same file, whether the script loads anything else, and where it may send data. Our answers are above; Fathom's are in their post, and the product side is on /vs/fathom.
Questions
- Can an analytics script steal form data?
- Any script on your page can technically read form fields, because it runs with your page's rights. Whether it can send them anywhere depends on your Content-Security-Policy: a connect-src allowlist blocks requests to hosts you have not listed. Our /cf.js does not read form fields; it sends one JSON body with the site ID, path, hostname, referrer, UTM tags on the first view, and an optional event name.
- How do I limit what a tracking script can do?
- Set a Content-Security-Policy that lists the script's host in script-src and its collection endpoint in connect-src, and nothing else. Serve the script from your own domain if the vendor supports it, so both can be 'self'. Keep the number of third-party tags small, and remove the ones you do not use.
What the script sends · Analytics script size compared · Serve the script from your own domain · Install docs · vs Fathom