Audience attributes: privacy review
Status: Recorded 2026-09-19 for spec 012 (FR-025, FR-026). This is the review the constitution requires before any new data field is accepted. It records decisions only. This release collects nothing new.
Every candidate below is judged against the same questions: what is the purpose, what would be retained and for how long, who can see it, and how could it identify a person?
Decisions
| Attribute | Decision | Purpose | Retention | Access boundary | Re-identification risk |
|---|---|---|---|---|---|
| Country | Approved (already collected) | Show where traffic comes from | Same as other dashboard aggregates | Aggregates only, project-scoped, operator console | Low. Countries are coarse and counted, never listed per visitor. |
| Continent | Approved (derived) | Regional summary | None; computed from country at display time | Same as country | Low. Derived from country in the console; nothing extra is stored. |
| Tor network / unknown location | Approved (already collected) | Keep totals complete and honest | Same as country | Same as country | Low |
| Browser language | Deferred | Would show language mix | Would need a new stored field and a retention decision | Aggregates only | Low to medium: rare language and country combinations can single out a visitor. Needs a small-count rule first. Deserves its own specification. |
| Region, state, or city | Rejected for now | Finer location | Would need a new stored field | Aggregates only | Medium to high: small places produce tiny counts that can point at individuals. Needs a small-count suppression design first. |
| Age, gender, interests | Rejected | "Demographics" | n/a | n/a | Not collectable without profiling or third-party data, which the privacy-minimal principle forbids. |
Rules that apply to anything added later
- A new attribute must arrive as a bounded aggregate. No visitor identifier, raw event, or raw URL query value is ever returned to the console.
- Any attribute with small-count risk needs a documented suppression or bucketing rule before it is stored.
- The attribute gets its own specification, retention statement, and privacy review entry here.
What the console does with country data
- Country codes stay as stored. The console maps a code to a full English name and a continent at display time, using reference data shipped inside the console. No request is made to any outside service, and nothing new is sent to or stored in the backend.
T1is shown as "Tor network".XX,Unknown, and empty values are shown as "Unknown location". An unrecognized value is shown as text with "(unrecognized)".- The country and page rankings can now return more rows (up to 300 countries and 100 rows for pages, referrers, and user agents) so complete lists can be shown. Responses remain bounded aggregates.
Appendix: sources and licenses of the bundled reference data
The reference data is generated by scripts/generate-world-geo.mjs and committed as apps/admin-web/src/geo/countries.ts and apps/admin-web/src/geo/world-paths.ts. The generator's inputs are development dependencies and are not shipped.
| Input | Used for | License |
|---|---|---|
world-atlas (Natural Earth 110m) | Map shapes | ISC package; Natural Earth data is public domain |
countries-list | Country names and continents | MIT |
i18n-iso-countries | ISO numeric codes (to join names to map shapes) | MIT |
topojson-client, d3-geo | Reading and projecting the shapes | ISC |
Natural Earth is public domain and asks for no attribution, but it is credited here as a courtesy. Country borders on the map follow Natural Earth's default view and are not a political statement.