ReviewOS

stacks/analyticshq

[privacy] Expose privacy config toggles (retention, geo granularity, DNT, salt)

#11
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

All privacy behavior is hard-coded: daily salt (app/Analytics/tracking.ts:28), 30-min session window (routes/analytics.ts:154), country-only geo, no DNT. config/analytics.ts is unrelated (it configures the framework’s own third-party analytics driver, ironically fathom). Operators cannot tune any of it.

Why it matters (privacy-first)

Self-hosters and privacy-conscious operators expect to set retention, geo granularity, DNT respect, and salt strategy.

Proposed fix

Add a real config/ghostanalytics.ts (or similar) exposing: retention window, geo granularity (country-only default), DNT/GPC respect, salt mode, session window. Wire the ingest/purge paths to it.

Priority: P2 (umbrella for the other privacy issues).


From the 2026-07-20 privacy-first competitive audit (vs. Plausible / Fathom / Simple Analytics / Umami).

9 days ago

One item on the proposed list is already delivered: retention.

#4 ([privacy] Add data-retention limits + a purge job) is closed, and the purge is wired up — app/Scheduler.ts:19-23 schedules scripts/analytics/prune.ts against ANALYTICSHQ_RETENTION_DAYS, citing #4 in the comment.

So the remaining asks for the config surface are: geo granularity, DNT/GPC respect, salt mode, and the session window. Retention just needs surfacing in the same config object rather than implementing.

9 days ago

Done in 155f9cf. config/privacy.ts now declares every privacy-affecting behaviour and each call site reads it.

settingdefaultwas hardcoded in
retentionDays0 (keep forever)scripts/analytics/lib.ts
saltRetentionDays2app/Analytics/salt.ts
sessionWindowMinutes30routes/analytics.ts:213
geo.granularity'country'implied by which helper got called
respectDnttruesplit between tracker and ingest
collect.pageTitle / .screenSizefalseremoved outright in #10

A new file rather than config/analytics.ts, which as the issue notes configures the framework's own third-party driver and is pointed at Fathom.

Two decisions worth recording:

There is no city or region geo option. The union is 'country' | 'none'. Widening what we resolve should be a product decision with a copy change — /compare/plausible explicitly contrasts us with their city-level resolution — not something reachable by editing a config value.

Retention parsing is not reimplemented. The config calls the same retentionDays() helper prune.ts uses, so the two cannot drift on what "unset / 0 / negative / non-numeric" means.

The file also states that these defaults are the posture the comparison pages claim, and that loosening one means updating resources/data/competitors.ts in the same change — since the failure mode for this cluster has been copy and behaviour drifting apart (which is what #8 fixed on the DNT row).

11 tests in two halves: the defaults match what we publish, and each call site reads the config rather than a literal. Sabotage-checked — restoring the hardcoded session window fails them.

This closes the privacy cluster: #8, #9, #10, #11.

Sign in to comment on this issue.