DRAFT - NOT LEGAL ADVICE
Legitimeter Privacy Policy (draft for counsel review)
Drafted 2026-10-01 by agent DjSYhkPZ for CFo0ETXT (P5). Every data statement below is grounded in the code at the cited file:line, as read on 2026-10-01; where code and this text disagree, the code governs and this draft is wrong. {{OWNER}} = a fact only the owner can supply; {{COUNSEL}} = a legal judgment for the attorney (see ATTORNEY_BRIEF.md). Regime sources: docs/STRATEGY_GAPS.md section 3.
1. Who we are
Legitimeter is operated by Holoschema LLC, a Texas limited liability company (Texas SOS file #6000019436), {{OWNER: postal address}}, contact support@legitimeter.com (web: https://legitimeter.com/support). EU representative (GDPR Art 27) and UK representative: {{OWNER/COUNSEL: appoint before EU/UK entry}}.
2. The short version
-
Legitimeter analyses pages on your device, using only what your browser already received. It does not browse for you, crawl or fetch extra pages (invariant I2; enforced by
packages/adapter-chromium/test/observe_only.test.mjs). -
Everything below leaves your device only through one module,
packages/adapter-chromium/src/share_egress.mjs, which re-checks the exact allowed shape at the moment of sending and refuses anything else (share_egress.mjs:56-78, 86-107, 119-154, 162-183). Requests carry no cookies (credentials: "omit") and no referrer (referrerPolicy: "no-referrer") (share_egress.mjs:68-72). - We never send page addresses (URLs), page text, your browsing history, private messages, drafts or passwords.
3. What we collect, exactly
3.1 Usage counts (analytics)
| Field | What it is | Source |
|---|---|---|
install_id | 128-bit random number, not derived from any key or identity; resettable to a new, unlinked one | analytics.mjs:8, :31, :56 |
day | calendar date (YYYY-MM-DD) | analytics.mjs:32 |
counts | integer counts of named events from a fixed list only: install, first_value, opt_in, register, plan_view, checkout_start, paid, active, chip_shown, label_feedback, report_problem, crash, error, mark, undo, donation, uninstall_survey | analytics.mjs:11-16, :35-38 |
No other key is accepted (analytics.mjs:29). When switched off, nothing is stored and zero requests are made (analytics.mjs:9-10).
**In-product notice (verbatim, USAGE_NOTICE_COPY, packages/adapter-chromium/src/ui.mjs:170, commit 9b8e272; INTERIM DRAFT approved by A0 ruling 2026-10-01 16:48):**
Legitimeter counts which features are used, with a random install ID. No names, accounts or browsing history. You can switch this off anytime in Settings.
Button: "Allow usage counts" (shown in every region; Settings lets you switch counting off again). This policy must match that constant word for word. Any wording change is made in that one place, and test/usage_notice.test.mjs guards it.
How it behaves (G11):
- Nothing is recorded before the notice is shown.
- Opt-in regions (EU/EEA and UK): seeing the notice is not consent. Nothing is recorded until you press "Allow usage counts". Legal bases: ePrivacy Art 5(3) and UK PECR reg 6.
-
Firefox (opt-in in every region): On Firefox, usage data is collected only if you leave Firefox's technical-and-interaction data permission enabled. Firefox shows this choice when you install the extension (
technicalAndInteraction, an optional category; requirednone). If it is off, no usage counts or problem reports are sent, in any region, and the region rule below does not apply on Firefox. Website content and account information are requested separately when you turn on those features (contributions, and account registration), and nothing is sent if you decline. The code checks the Firefox permission before anything is sent (data_consent.mjs,share_egress.mjs). -
Chrome and Edge (opt-in in every region): seeing the notice is not consent, wherever you are. Nothing is counted, and no install ID is created, until you press "Allow usage counts". You can switch it off again at any time in Settings. This is the default in code on Chrome and Edge (
OPT_IN_EVERYWHEREatanalytics.mjs:24istrue, soanalytics.mjs:89setsenabled: falseandinstall_id: nullafter the notice in every region). - Settings → off stops recording and clears any unsent queue.
-
How we decide your region: your browser's time zone (
IntltimeZone) plus the region part ofnavigator.language(regionRequiresOptIn). If the result is ambiguous, we treat you as being in an opt-in region.
Earlier default-ON behaviour (user ruling v271, counting after the notice outside opt-in regions) was retired on 2026-10-20: usage counting is now opt-in on every browser and in every region. {{COUNSEL}} Confirm that the notice plus the "Allow usage counts" button is valid opt-in consent in every region (ATTORNEY_BRIEF Q3).
The server computes cohort retention (D1/D7/D30) from these counts (packages/server/src/usage.mjs:5, :177-180).
3.2 Contributions (opt-in only)
If you turn on contribution, your device sends a judgment about a public account, never the account name itself:
| Field | What it is | Source |
|---|---|---|
epoch | a period label such as 2026-W39 | core/src/privacy_gate.mjs:21, :29-34 |
entity_key | a keyed hash (HMAC-SHA256) of platform + handle under a rotating epoch key; the raw handle is refused | privacy_gate.mjs:36-39, :84 |
contributor_id | a keyed hash of a device secret + epoch, so contributions are unlinkable across epochs | privacy_gate.mjs:41-44 |
axes | numeric scores and confidences in [0,1] for named signal axes | privacy_gate.mjs:46-61 |
Only these four keys are allowed (privacy_gate.mjs:19, :78-88).
The contribution record path also carries timing: observed_at (when your browser received the item) leaves the device only floored to a 15-minute UTC bucket, and full precision stays on the device; an unparseable time is refused (core/src/contribution.mjs:59, :65, :93; PV1 c2629694, commit e5533c2). authored_at is the public post's own time. content_hash is an unsalted hash of public content: someone holding the same public item could learn that some contributor received it in a given quarter-hour, but no identity is attached (wording aligned with docs/CHROMEWEBSTORE.md 'Disclosed costs'). Marks carry a month period (YYYY-MM).
Served results: aggregated entity results are served only once at least DEFAULT_K = 5 contributors agree (with a banded cold-start minimum of 2 distinct contributors), so a single user's contribution is never served alone (privacy_gate.mjs:16-18).
3.3 Account (optional; needed only for paid tiers)
| Field | What it is | Source |
|---|---|---|
public_key | Ed25519 public key (64 hex); the private key never leaves your device | identity.mjs:8-9, :25-34 |
account_id | SHA-256 of the public key | identity.mjs:8, :37-44 |
nonce, signature | single-use proof of key possession | identity.mjs:10, :31-32; server nonces expire (server/src/auth_challenge.mjs:33-49) |
No email, name, phone or password is collected by the account system. Payment details, when live, are collected by the payment provider, not by us (server/src/payments.mjs:10-12; see MERCHANT_OF_RECORD.md).
Referral codes are derived only from account_id; no personal data is involved (server/src/referral.mjs:10-12).
3.4 Problem reports (only when you send one)
Keys: operation, category (fixed list), message (length-capped; refused if it contains a URL or handle), diagnostics = analytics_enabled, browser_family, ext_version, policy_name, error_counts (core/src/problem_report.mjs:47-54, :57-80). You see the exact body before sending.
3.5 Peer-to-peer list sharing (if enabled)
The relay keeps peer announcements in memory only, with hard caps and a 10-minute expiry (server/src/p2p_server.mjs:12, :26). {{COUNSEL}} whether this makes us an intermediary under the DSA / UK OSA (STRATEGY_GAPS L-DSA).
3.5a Trust-list updates (automatic, no personal data)
About once a day (24 hours plus a random delay of up to 1 hour), the extension downloads a public, signed list of content-credential (C2PA) trust anchors from our own release server. The request is identical for every user: it names only the list (c2pa_trust) or a chunk's SHA-256 hash, sends no cookies, headers, body or referrer, and carries no account id, page address, browsing data or verdict (adapter-chromium/src/trust_sync.mjs REQUEST_INIT, requestUrl). A list is used only after its release signature and every chunk hash verify; otherwise the built-in list is kept. It can be turned off in Options. Status: built but not yet switched on; no release host is configured (DECISIONS.md, 2026-10-02 trust-list sync rulings). Hosting logs for this request are covered by 3.7.
3.6 What stays on your device only
Verdicts, entity history, exact timestamps, raw handles, page content, private signing keys, and your labels/preferences.
3.7 Server logs and IP addresses
The server code does not record client IP addresses (the only address check is a loopback guard on the local operator dashboard, server/src/dashboard.mjs:104). Our service is hosted on Cloudflare (edge network, static pages and request routing) and Hetzner (the API server). Our API server keeps no access log. Cloudflare processes your IP address to route and protect requests and may keep it in short-lived operational logs; we keep no copy of those logs for longer than 30 days. {{OWNER: confirm the 30-day figure and the Hetzner region}}
4. People we label (non-users)
Legitimeter shows probabilistic signals about public accounts. Under GDPR this is processing of those people's personal data even though they are not our users. Our basis is {{COUNSEL: legitimate interest (Art 6(1)(f)) + LIA}}. Because we cannot give each labeled person notice directly, we rely on {{COUNSEL: Art 14(5)(b)}} and publish 'How Legitimeter decides' (LABELING_POLICY_DRAFT.md). Any labeled person may object or request correction through the dispute path (TERMS_OF_SERVICE_DRAFT.md section 'Disputes'; owned by peer fvcXO9t8). Labels never produce legal or similarly significant effects by themselves; each user decides what to do ({{COUNSEL}} GDPR Art 22 position). Country of origin is display-only and never changes a verdict (adapter-chromium/src/channel_quality.mjs:4, decision D11).
5. Why we process data (purposes and bases)
| Data | Purpose | EU/UK basis {{COUNSEL}} | US (CCPA/TDPSA) category |
|---|---|---|---|
| Usage counts | fix bugs, measure retention | consent (EU/UK, pending G11); legitimate interest elsewhere | internet/usage activity; not sold or shared for targeted advertising |
| Contributions | improve shared authenticity intelligence | consent (opt-in) | inferences about public accounts |
| Account | entitlement, quota, payment linkage | contract | identifiers (pseudonymous) |
| Problem reports | support | consent (user-initiated) | usage activity |
| Waitlist (email, product, time joined, optional account link) | launch notification | consent by submitting the form [OWNER/COUNSEL TO CONFIRM]; retention [OWNER DECISION PENDING] | identifiers (email) |
We do not sell personal data, and we do not 'share' it for cross-context behavioral advertising (CCPA/CPRA). We do not process sensitive data. We do not knowingly collect data from children under 13 (16 in the EU) (COPPA).
6. Retention
{{OWNER/COUNSEL}} per store once deployed. Code-level facts: nonces expire (auth_challenge.mjs:33-49); P2P announcements expire after 10 minutes (p2p_server.mjs:26); contributions are keyed per epoch (privacy_gate.mjs:41-44); referral credits are time-bounded windows (referral.mjs:7-8, :23-24).
7. Your rights
- Everyone: turn analytics off in Options, reset the install ID, stop contributing, uninstall.
- EU/UK (GDPR / UK GDPR): access, rectification, erasure, restriction, portability, objection, and complaint to your supervisory authority (UK: ICO).
- California (CCPA/CPRA): know, delete, correct, opt out of sale/sharing (we do neither), limit sensitive-data use (we hold none), and non-discrimination.
- Texas (TDPSA) and other US states: confirm, access, correct, delete, portability, opt out of targeted advertising, sale and profiling; appeal a refusal; complaint to the Texas Attorney General.
- Canada (PIPEDA), Brazil (LGPD), Australia (APPs): equivalent access and correction rights.
We do not collect a name or email for your account. If you join a waitlist, we store the email address you enter, the product name and the time you joined. You prove a request by signing it with your device key (account holders) or by giving your install_id (Options page).
What "Delete all" keeps (source of truth: docs/DATA_INVENTORY.md section 4, commit ac92729; the server keep-set is PAID_KEEP_FIELDS at server/src/accounts.mjs:41, applied at :205-217, and pinned by packages/server/test/data_rights_ops.test.mjs:38,93 (cf60ec8)):
-
Free plan: "Delete all" removes your data from your device and removes your account record and your testimonials from our server (this does not remove waitlist entries; see below). Two kinds of record are not removed, and we want to be clear about why:
- Contributions and marks you shared with the network. These are stored in an append-only log so that the network can be audited. They are never edited or physically deleted. Each row carries a contributor id that is derived from a secret kept on your device and that changes every epoch. Rows carry no account id, name, email or IP address. If you retracted a contribution, the retraction is honoured and the contribution no longer counts. Deleting your device data removes that secret; we designed the system so that the remaining rows then cannot be linked back to you. You may still ask us to erase them, and we will answer that request under applicable law.
- Referral credit records, if you referred someone. These are kept against your account id for the life of the credit.
-
Paid plan: only the minimum needed to keep your subscription working.
-
On our server, your account record:
account_id,public_key(a random device key that we check signed requests against; it is pseudonymous and is left out of export bodies),tier,purchase_token_hash,paid_ref_hashes(these stop one payment from being counted twice), andcreated_week. -
On your device: the device identity (
ain.account.identity), the registered flag (ain.account.registered) and the purchase token (ain_purchase_token_v1).
-
On our server, your account record:
-
Testimonials you wrote: erased on every plan. A registered account, free or paid, may submit up to 3 testimonials. Each one is text of 10 to 1000 characters, plus an optional display name of up to 40 characters, stored exactly as you typed it.
- We store the id, text, display name, status and day. Fields for email, IP, real name or country are refused by name.
- Each testimonial stays hidden until an operator approves it. The public list shows only the approved text, display name and day, never your account.
- You can delete any of your testimonials at any time. "Delete all" erases all of them on free and paid plans; they are not part of the paid keep-set. Your download includes them, with their status.
- Delete all erases your testimonials first, before it removes your account. If that erase fails, nothing is removed, and you can retry.
-
Sources (commit f44c744): limits
TESTIMONIAL_LIMITSatserver/src/testimonial_route.mjs:21; allowed fieldsTESTIMONIAL_KEYSat:16, with other fields refused at:82; the public list shows approved items only (:86); export and erase at:131-132, run byserver/src/accounts.mjs:205-224. Pinned bypackages/server/test/testimonial_signed.test.mjs: approval:43,62, refused fields:125, cap:145, cross-account 401:174, export:188, failed erase is retryable:200,216. See DATA_INVENTORY section 2, line 47.
-
Held by the payment provider, not by us: your email, payment details, and billing and tax records (
accounts.mjs:11). These are outside our delete scope. To exercise your rights over them, contact the provider. - Not held per person, so not claimed as deleted: weekly funnel and campaign counters, the uninstall counter, daily and cohort usage totals, k-anonymous community aggregates, and the in-memory dispute queue, which is keyed by the labeled subject rather than by you (DATA_INVENTORY section 3).
To remove a waitlist entry, email [OD-2 CONTACT EMAIL] from the address you used and we will delete it. Your IP address is kept only in memory, in hashed form, to limit abuse, and is never written to storage.
We respond within 45 days (CCPA/TDPSA) or 1 month (GDPR).
8. Transfers and processors
The API server is hosted by Hetzner Online GmbH in {{OWNER: Hetzner region (Germany, Finland or US)}}, behind Cloudflare, Inc. (edge network and static pages). EU/UK transfers to the US: {{COUNSEL: DPF/SCCs}}. Processors: hosting Hetzner Online GmbH and Cloudflare, Inc.; payments Paddle.com Market Limited, which acts as Merchant of Record and is an independent controller for payment and tax data. No advertising SDKs and no third-party analytics are embedded; analytics go only to our own endpoint (analytics.mjs:46-50).
9. Security
Signed requests (Ed25519), single-use nonces, credential-less requests, keyed hashing of handles, and k-anonymity thresholds on served results (citations above).
10. Changes and contact
Material changes are announced in the extension before they take effect. Contact: {{OWNER}}.
Pre-publication checklist (for owner and agents, not part of the public policy)
- G11: EU/UK opt-in analytics (owner ruling, then agent).
-
'Delete all' exists in
server/src/accounts.mjs, and its retention is set out in DATA_INVENTORY section 4 (ac92729). Device side: pinned bypackages/adapter-chromium/test/data_rights.test.mjs:122('delete all, paid plan: keeps only the entitlement keys and says so'), which checks thekeepIfPaidkeys in REGISTRY (src/data_rights.mjs:49). Server side: pinned bypackages/server/test/data_rights_ops.test.mjs:38,93(cf60ec8). After a paid delete-all, the stored keys equalPAID_KEEP_FIELDSexactly and a signed entitlement call still returns paid:true. A free delete-all erases everything, verified by read-back. A cross-account export or delete returns 401 (Kf7mIPiv, 2026-10-01 19:24). - Host and log retention confirmed after deployment (f742bcab).
- Counsel answers on Art 6/14/22, DPF/SCCs and DSA (ATTORNEY_BRIEF.md).
- Re-verify every file:line above against the release commit.