Privacy and data protection

Analytics software handles data that identifies people. The rules on this page are not stylistic preferences — they are acceptance criteria in Sumimi's own design, and a feature that would violate them is out of scope. Read them before you deploy, and read them again before you add custom events.

The short version

No cookies. No localStorage. No fingerprinting. No IP address stored — not in the database, not in the logs, not hashed into something stable. Query strings stripped by default. Do Not Track and Global Privacy Control honoured. Geography recorded only if you explicitly configure an edge header. The server never makes an outbound request. Retention is a setting you control.

What is collected

Everything Sumimi records is listed here. Everything not on this list is not recorded. All of it is optional, and all of it is coarse.

DataWhere it comes fromNotes
Page URL The tracker The path and the query string. The server splits the two apart and parses the marketing parameters out of the query.
Referrer The tracker The full referring URL. The server splits it into its host, its path and its query.
Page title The tracker The <title> of the page.
Host name The tracker The visitor's host, e.g. blog.example.com.
Browser family and version The User-Agent header Parsed server-side. No browser-side feature detection.
OS family and version The User-Agent header Parsed server-side.
Device class The User-Agent header Desktop, mobile or tablet — a coarse class, not a model list.
Screen size The tracker screen.width × screen.height. Not the viewport.
Language The tracker navigator.language, e.g. en-GB.
Country An edge header Only if a header such as CF-IPCountry is present and you configure Sumimi to read it. Never derived from a geo-IP database.
Session id Generated server-side Random and non-reversible, rotating every 30 minutes.
Custom event properties You Free-form, stored verbatim. Your responsibility — see the warning below.

What is never collected

Never stored, never logged
Raw IP addresses — not stored, not logged, not hashed into a stable identifier
Cookies of any kind
localStorage or sessionStorage identifiers
Fingerprinting: canvas, audio, fonts, plugins, hardware
Precise geolocation
Precise viewport measurements
Installed fonts or plugin lists
Account credentials, form contents, keystrokes, page text

If a feature ever requires storing something from this list, the feature is out of scope. It will not be implemented quietly.

The visitor identifier

Analytics needs some way to say "this is the same person as yesterday" without keeping a durable record of who that person is. Sumimi uses an HMAC-SHA256 over the server's own secret and four inputs:

HMAC(server_secret, coarse_ip_prefix ‖ user_agent ‖ website_id ‖ time_bucket)

Three properties of this construction matter:

  • It cannot be reversed. The HMAC is one-way, so the identifier cannot be turned back into an IP address, even by someone who holds the secret.
  • It rotates. The time bucket is 30 minutes for sessions and one day for the visitor count, so "unique visitors" means unique visitors today rather than unique visitors forever. There is no long-lived identifier to leak.
  • It is scoped to one website. The website id is part of the input, so the same visitor has different identifiers on different sites and the two cannot be correlated.

The coarse prefix is the first three octets of an IPv4 address (a /24) or the first four hextets of an IPv6 address (a /64). The full address is used to derive that prefix and then discarded; it is never written to disk and never written to a log line.

Cookies, storage and fingerprinting

  • No cookies. The tracker never calls document.cookie and the collection request is sent with credentials: 'omit', so no cookie is attached to it either.
  • No persistent storage. Nothing is written to localStorage, sessionStorage, IndexedDB or the cache API, so nothing survives between visits on the same browser.
  • No fingerprinting. The tracker does not touch the canvas, the audio API, the font list, the plugin list or any hardware API. The browser and OS are classified from the User-Agent header on the server.
  • No session replay. There is no DOM capture, no input recording and no screenshot mechanism of any kind.

Do Not Track and Global Privacy Control

The tracker checks navigator.doNotTrack and navigator.globalPrivacyControl before it does anything else. If DNT is "1" or "yes", or GPC is true, the tracker returns immediately: no request is made, no payload is assembled, nothing about the visit leaves the browser.

Note the direction of the protection: the visitor opts out, and the default is to honour that. The server side has no way to override it.

URLs and query strings

The tracker sends the page URL and the referrer in full, and the server splits them apart: it stores the path, the query string and the referrer's host, path and query. The query string is kept because it is where the marketing parameters live.

# what the visitor visits
https://blog.example.com/posts/hello?utm_source=newsletter&email=ada@example.com

# what is stored
url      = /posts/hello
query    = utm_source=newsletter&email=ada@example.com
utm_source = newsletter

The server parses the marketing parameters (utm_source, utm_medium, utm_campaign, utm_content, utm_term) and the ad click ids (gclid, fbclid, msclkid, ttclid, lifatid, twclid) out of it and stores them as their own columns.

This does mean the query string is personal data: it routinely carries session tokens, email addresses and search terms. If you do not want it, run your own Sumimi behind a reverse proxy that strips the query string before it reaches POST /api/send.

Geography

Sumimi has no geo-IP database and never looks one up. A country code is recorded only when two conditions are both true:

  1. A reverse proxy in front of Sumimi sets a header containing the country code — typically CF-IPCountry from a CDN, or your own header.
  2. You name that header in SUMIMI_COUNTRY_HEADER, or per website in countryHeader.

Without that configuration, no geographic information is recorded at all — not a country, not a region, not a city. There is no fallback path that geolocates an address.

When a country is recorded, it is the two-letter code from the header and nothing more. Region and city remain empty.

The server's own behaviour

  • No outbound calls. The service never phones home. No telemetry, no crash reporting, no update checks, no usage reporting, no third-party scripts. If the box has no outbound network access, nothing breaks.
  • The tracker is always served by your own server. The script you put on your website (sumimi.js) is compiled into the server binary and served from your own host, so your visitors' browsers never talk to a third party to report a page view. The dashboard frontend is a separate thing: it is loaded by your own administrator's browser, and by default comes from a CDN. Set SUMIMI_APP_URL=/app to serve it from your own server instead.
  • No raw IPs in logs. Access logs record the method, the path, the status and the duration. Query strings are stripped from logged paths, because they routinely carry tokens and personal data.
  • No request bodies in logs. Payloads are validated and discarded, never logged verbatim.
  • Default-deny access. Statistics and website management require a valid token. Only /api/send, /api/collect, /api/heartbeat and /sumimi.js are public.
  • Synthetic examples only. Everything in Sumimi's documentation, tests and screenshots uses invented domains and invented data. Real traffic is never pasted into the project.

Custom event properties are your responsibility

Read this one twice

Custom event properties are free-form data that you supply, and they are stored exactly as sent. The tracker never attaches anything sensitive on its own, but it cannot stop you from attaching something sensitive yourself.

# fine — a coarse, non-identifying value
sumimi.track('signup', { plan: 'free', source: 'pricing' });

# NOT fine — this creates personal data in your database
sumimi.track('signup', { email: 'ada@example.com', user_id: 'u-10423' });

The moment you put an email address, a phone number, a name or a stable user id into an event property, you have made your Sumimi installation a system that processes personal data — and every guarantee on this page stops applying to it. You would then need your own legal basis, your own retention policy and your own way to honour access and erasure requests for that data.

If you need to count something about a person, count the category they belong to rather than the person. "Enterprise plan" is a statistic; "ada@example.com on an enterprise plan" is a record.

Retention and deletion

Retention is a setting, not an afterthought. Each website has its own retentionDays value, and 0 means "keep forever".

  • Set the default for new websites with SUMIMI_RETENTION_DAYS.
  • Change it for one website with PUT /api/websites/:id or the PUT equivalent in the dashboard.
  • Read it back with GET /api/websites/:id.
  • Enforce it with sumimi prune, scheduled from cron.

To remove a website and everything belonging to it:

sumimi erase --db /var/lib/sumimi/sumimi.duckdb 3f2a...

# or through the API
curl -X DELETE https://analytics.example.com/api/websites/3f2a... \
  -H "authorization: Bearer $TOKEN"

Erasing a website deletes its sessions and its events as well as the website record.

Who can see the data

Only you. The data lives in a DuckDB file on the machine you deployed it on. There is no account with the Sumimi project, no hosted dashboard, no central database and no third-party processor. The people who can read your analytics data are the people who can read that file — which means the file's permissions are the whole access-control story.

Two practical consequences:

  • The database file and the JWT secret should be mode 0600, owned by the service user. The self-hosting guide sets that up.
  • If you put your site behind a CDN, the CDN sees visitor addresses regardless of what Sumimi does. That is outside Sumimi's control, and it is worth remembering when you weigh the privacy benefit of running your own analytics.

Legal notes

Sumimi is Apache-2.0 licensed software. The licence covers the software, not the data you collect with it, and it makes no statement about the legal basis on which you may collect data from your visitors. That depends on where you and your visitors are. A small amount of anonymous, coarse traffic statistics is treated differently in many jurisdictions from personal data, but the line moves, and using the defaults described here keeps you on the comfortable side of it. If you need specific advice, talk to a lawyer in your jurisdiction.

If you are in the European Economic Area or the United Kingdom, the defaults here are designed to support a position that the processing is anonymous and therefore outside the scope of the GDPR — but you remain responsible for that conclusion in your own context, and for your own privacy notice to your visitors.

Reporting a problem

If you find something in this software that breaks a promise made on this page, please report it. Security and privacy issues should be sent to the maintainer privately rather than opened as a public issue — a public issue containing a working exploit or a real leaked value helps nobody. The issue tracker is fine for everything else.

Summary

BehaviourSumimi
Cookiesnever set
localStorage identifiersnever used
Fingerprintingnever attempted
Raw IP addressesnever stored or logged
Visitor identifierrotating, non-reversible HMAC over a coarse IP prefix
Session identifierrotating every 30 minutes, scoped to one website
URL query stringsstripped by default, opt-in per site
Do Not Trackhonoured — nothing is sent
Global Privacy Controlhonoured — nothing is sent
Geographyonly from a configured edge header, else nothing
Outbound calls from the servernone, ever
Request bodies in logsnever
Retentionconfigurable per website, enforced by sumimi prune
Erasuresumimi erase and DELETE /api/websites/:id

Sumimi is an independent open-source project, an open-source alternative to Umami that implements the Umami v2 HTTP API. Apache-2.0 licensed.