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.
| Data | Where it comes from | Notes |
|---|---|---|
| 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.cookieand the collection request is sent withcredentials: '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-Agentheader 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:
- A reverse proxy in front of Sumimi sets a header containing the country code —
typically
CF-IPCountryfrom a CDN, or your own header. - You name that header in
SUMIMI_COUNTRY_HEADER, or per website incountryHeader.
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. SetSUMIMI_APP_URL=/appto 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/heartbeatand/sumimi.jsare 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/:idor thePUTequivalent 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
| Behaviour | Sumimi |
|---|---|
| Cookies | never set |
localStorage identifiers | never used |
| Fingerprinting | never attempted |
| Raw IP addresses | never stored or logged |
| Visitor identifier | rotating, non-reversible HMAC over a coarse IP prefix |
| Session identifier | rotating every 30 minutes, scoped to one website |
| URL query strings | stripped by default, opt-in per site |
| Do Not Track | honoured — nothing is sent |
| Global Privacy Control | honoured — nothing is sent |
| Geography | only from a configured edge header, else nothing |
| Outbound calls from the server | none, ever |
| Request bodies in logs | never |
| Retention | configurable per website, enforced by sumimi prune |
| Erasure | sumimi 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.