docs(privacy): state the two local values PostHog actually stores (#1242)
* docs(privacy): state the two local values PostHog actually stores
Verified in production after Support went live: PostHog's conversations
module writes ph_conv_<token> = {"widgetSessionId": uuid} to
localStorage at PAGE LOAD, before the user contacts support at all, and
it bypasses persistence: 'memory' exactly like surveys' seenSurvey_
flags do.
The privacy page claimed enkäter were the only thing stored locally.
That became wrong the moment Support was enabled, so it now names both
values and what they are for.
The substance is unchanged: both are functional UI state with no
personuppgifter (do-not-ask-again, and find-my-own-ticket in this
browser), no cookies are set, and no analytics data is stored on the
device, so the no-consent-banner position still holds. Only the claim
needed to match reality.
Lesson recorded in DECISIONS.md: enabling any new PostHog product can
silently add device storage, so check localStorage in prod after each
one rather than trusting the persistence setting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(compliance): inventory the client-side storage keys
Closes the ISO 27001 A.5.9 finding from the compliance swarm on #1242:
the two device-side keys were described in prose but were not enumerated
anywhere that a future change would be checked against.
Lists both application keys and both PostHog keys, what each contains,
why neither PostHog key is personal data (which is what makes the
strictly-necessary assessment hold), and why purge-legacy-storage
deliberately leaves them alone.
Adds the review trigger that would have caught this earlier: enabling a
new PostHog product can silently add storage, because the products write
to localStorage directly rather than through the SDK persistence setting.
The Support product was found in prod, not in review.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fbd4b992f5
commit
88099f5390
@@ -70,3 +70,47 @@ logs. The authenticated RLS authorization is repeated through a stateless
|
||||
service-role client with explicit `user_id` and `company_id` predicates. Export
|
||||
queries filter by that company directly or use parent IDs fetched under the
|
||||
same filter. Recipients must store and transfer the ZIP as Confidential data.
|
||||
|
||||
## Client-side storage inventory
|
||||
|
||||
Everything this application persists on a user's device. It is inventoried here
|
||||
because the analytics posture is "no analytics data and no cookies on the
|
||||
device" (`persistence: 'memory'`), and the exceptions are only defensible if
|
||||
they are known, enumerated and reviewed rather than discovered in production.
|
||||
|
||||
Application-owned keys:
|
||||
|
||||
- `Accounted:chat-sidebar-collapsed` — assistant sidebar UI state.
|
||||
- `gnubok.inbox.onboarding.dismissed` — one-time onboarding hint dismissal.
|
||||
|
||||
PostHog-owned keys. Both are written by PostHog's own modules straight to
|
||||
`localStorage`, bypassing the `persistence: 'memory'` setting, so neither is
|
||||
prevented by the SDK configuration:
|
||||
|
||||
- `seenSurvey_<survey_id>` — `"true"`. Suppresses a survey the user already
|
||||
answered or dismissed. Without it every survey re-prompts on each page load
|
||||
under memory persistence.
|
||||
- `ph_conv_<project_token>` — `{"widgetSessionId": "<uuid>"}`. Lets a user
|
||||
return to their own support ticket in the same browser. Created at page
|
||||
load, before the user contacts support.
|
||||
|
||||
Classification: neither PostHog key is personal data. Both are functional UI
|
||||
state: a random identifier and a boolean marker, with no name, address, email
|
||||
or accounting content, and neither is readable across origins. They are the
|
||||
basis for treating this storage as strictly necessary rather than requiring
|
||||
consent, so a change in their content changes that assessment.
|
||||
|
||||
Controls:
|
||||
|
||||
- `lib/analytics/purge-legacy-storage.ts` removes storage from the retired
|
||||
Recapt processor (`recapt` / `glimt` substrings) on every boot. It
|
||||
deliberately does NOT touch `seenSurvey_*` or `ph_conv_*`: clearing those
|
||||
would re-prompt surveys and orphan the user's own ticket.
|
||||
- No cookies are set by the application or by PostHog under this configuration.
|
||||
|
||||
Review trigger: **enabling any new PostHog product may silently add device
|
||||
storage**, because the products write directly to `localStorage` rather than
|
||||
through the SDK's persistence setting. After enabling one, inspect
|
||||
`localStorage` on production and update this section, `.compliance/ropa.yaml`
|
||||
and `app/(public)/privacy/page.tsx` together. The Support product was caught
|
||||
this way; the assumption that `persistence: 'memory'` was sufficient was wrong.
|
||||
|
||||
@@ -588,7 +588,15 @@ processing_activities:
|
||||
- session_replay_masks_all_inputs
|
||||
- org_number_never_transmitted # låst av test i lib/analytics/__tests__
|
||||
- no_pii_in_event_properties # PII endast som person properties via identify()
|
||||
- no_device_storage # persistence: 'memory', inga kakor, ingen consent-banner krävs
|
||||
# persistence: 'memory' -> ingen analysdata och inga kakor på enheten.
|
||||
# TVÅ undantag, båda funktionell UI-state utan personuppgifter, båda
|
||||
# skrivna direkt till localStorage av PostHogs egna moduler förbi
|
||||
# persistence-inställningen: seenSurvey_<id> ("visa inte igen") och
|
||||
# ph_conv_<token> = {"widgetSessionId": uuid} (hitta tillbaka till sitt
|
||||
# eget ärende i samma webbläsare). ph_conv skapas vid sidladdning, alltså
|
||||
# innan användaren faktiskt kontaktar supporten. Verifierat i prod.
|
||||
- no_analytics_data_or_cookies_on_device
|
||||
- only_functional_ui_state_persisted # seenSurvey_*, ph_conv_* (uuid, ingen PII)
|
||||
- same_origin_reverse_proxy # /rl-rewrite; ingen tredjepartsvärd i CSP
|
||||
- sandbox_companies_never_identified
|
||||
- disabled_entirely_when_self_hosted
|
||||
|
||||
@@ -618,3 +618,4 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-07-27] isConfigured() gates /connect only, not /disconnect, /schedule or /sync: a deployment losing its OAuth credentials must not trap users with a connection they cannot remove or a schedule they cannot switch off.
|
||||
[2026-07-27] Dropbox web links point at /home/Apps unless DROPBOX_APP_FOLDER_NAME is set: app-folder scoped calls cannot discover where the app folder sits in the user's account, and a link into the wrong folder reads as a lost backup, so the app-name deep link is opt-in rather than guessed.
|
||||
[2026-07-27] performSync keeps `provider` optional, defaulting to Google Drive: it preserves the pre-Dropbox call shape (and its test suite) as the documented legacy path while all three production call sites pass a provider explicitly.
|
||||
[2026-07-27] Privacy page corrected after verifying prod: PostHog's conversations module writes ph_conv_<token> = {"widgetSessionId": uuid} to localStorage at PAGE LOAD, not when support is contacted, bypassing persistence: 'memory' exactly like surveys' seenSurvey_ flags. The page previously claimed enkäter were the only local storage, which was wrong the moment Support was enabled. Both values are functional UI state without personuppgifter (do-not-ask-again, and find-my-own-ticket), so the no-consent-banner position holds, but "inga kakor eller annan lagring" did not and is now stated accurately. Worth remembering that enabling any new PostHog product can silently add device storage: check localStorage in prod after each one rather than trusting the persistence setting.
|
||||
|
||||
@@ -162,9 +162,13 @@ export default function PrivacyPolicyPage() {
|
||||
gränssnittet du klickar, aldrig vad som står i din
|
||||
bokföring. Organisationsnummer överförs aldrig.
|
||||
Identifiering sker endast för inloggade användare (ej
|
||||
sandbox/demo). Inga kakor eller annan lagring på din
|
||||
enhet används för analysen; enkäter sparar enbart en
|
||||
lokal markering om att du redan sett dem.
|
||||
sandbox/demo). Inga kakor används, och själva analysdatan
|
||||
lagras inte på din enhet. Två små tekniska värden sparas
|
||||
dock lokalt: en markering om vilka enkäter du redan sett,
|
||||
så att du inte får samma fråga igen, och ett
|
||||
slumpmässigt ärende-ID som gör att du hittar tillbaka
|
||||
till ditt supportärende i samma webbläsare. Inget av dem
|
||||
innehåller personuppgifter.
|
||||
</td>
|
||||
<td className="py-2 pr-4">EU (Frankfurt)</td>
|
||||
<td className="py-2">
|
||||
|
||||
Reference in New Issue
Block a user