Skip to content

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

AttributeDecisionPurposeRetentionAccess boundaryRe-identification risk
CountryApproved (already collected)Show where traffic comes fromSame as other dashboard aggregatesAggregates only, project-scoped, operator consoleLow. Countries are coarse and counted, never listed per visitor.
ContinentApproved (derived)Regional summaryNone; computed from country at display timeSame as countryLow. Derived from country in the console; nothing extra is stored.
Tor network / unknown locationApproved (already collected)Keep totals complete and honestSame as countrySame as countryLow
Browser languageDeferredWould show language mixWould need a new stored field and a retention decisionAggregates onlyLow 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 cityRejected for nowFiner locationWould need a new stored fieldAggregates onlyMedium to high: small places produce tiny counts that can point at individuals. Needs a small-count suppression design first.
Age, gender, interestsRejected"Demographics"n/an/aNot collectable without profiling or third-party data, which the privacy-minimal principle forbids.

Rules that apply to anything added later

  1. 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.
  2. Any attribute with small-count risk needs a documented suppression or bucketing rule before it is stored.
  3. 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.
  • T1 is 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.

InputUsed forLicense
world-atlas (Natural Earth 110m)Map shapesISC package; Natural Earth data is public domain
countries-listCountry names and continentsMIT
i18n-iso-countriesISO numeric codes (to join names to map shapes)MIT
topojson-client, d3-geoReading and projecting the shapesISC

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.

Released under the MIT License. Visitor data stays in your own Cloudflare account.