Map your footprint. Score the leak. Get a plan.
A local-first web tool that inventories the services holding your data, scores how much you're giving away and to whom, and produces a prioritized plan of fixes. It runs entirely in the browser — nothing is uploaded — and overlays live, crowd-sourced privacy grades from ToS;DR on top of a built-in baseline that works fully offline.
Live: https://privacyscore.me/

Status: archived & unmaintained. This repository is published as-is for anyone to read, audit, and fork. Issues and pull requests aren't monitored — if you want to change something, fork it and make it your own rather than filing it here. The code is finished and self-contained; that's a feature, not neglect.
The user works through three stages, with a live exposure meter and letter grade updating as they go:
The whole app is one HTML file with no build step and no runtime dependencies beyond two optional CDNs (Google Fonts) and one optional API (ToS;DR).
These are the rules that make this tool what it is. Treat them as hard constraints — if you fork it, breaking one of these means you've built something different (which is entirely fine; just know that's where the line is).
localStorage, no sessionStorage, no IndexedDB. State lives in memory; the only way to save is exporting a JSON file the user controls (and re-importing it). This is deliberate — a privacy tool must not silently retain your inventory.↑); services ToS;DR hasn't reviewed stay a grey "–" rather than borrowing an unrelated grade.Everything lives in index.html. Three layers, all inline:
| Layer | Where | Notes |
|---|---|---|
| Markup | <body> |
Header + exposure meter, the three sections, the report container, and the service picker modal. |
| Styles | <style> |
CSS custom properties (--bg, --ink, --good, --warn, --risk, …) drive the whole theme. A @media print block strips chrome so reports print clean. |
| Logic | <script> |
Vanilla ES6. No modules, no bundler. |
<script>)DATA_TYPES (sensitivity weights), MITIGATIONS (per-account risk reductions), CATALOG (the service database), POSTURE (global questions).TOSDR_DOMAINS (id→domain), PARENT_DOMAINS (parent-company fallback), GRADE_COEF, INHERITED_WEIGHT, and the functions gradeForDomain, attachGrade, syncTosdr, loadReasons, setTosdrStatus, recountTosdr.recipientMultiplier, mitigationFactor, dataWeight, accountExposure, accountRating, concentrationPenalty, postureGaps, overallScore, letter.state = { accounts: [], posture: {} } object, plus the TOSDR runtime object.renderCards, renderPosture, updateMeter, the picker (openPicker/renderPickList/addFromCatalog/addCustom), the report (buildActions/generate), and exportJSON/importJSON.There is no global framework state or reactivity — handlers mutate state and call the relevant render*/update* function. Keep that pattern if you extend it.
The simplest path:
# just open it
open index.html # macOS (xdg-open on Linux)
Opening via file:// works for everything except live ToS;DR sync, which browsers may block as a cross-origin request from a local file. To exercise the live grades, serve it over HTTP:
python3 -m http.server 8080
# then visit http://localhost:8080/
No install, no npm, no build. The file is the app.
It's a static site. Upload three files to the web root of any static host (GitHub Pages, Netlify, Cloudflare Pages, plain nginx/Apache):
index.html # the app (rename from privacy-audit.html if needed)
og-image.png # social link-preview image (1200×630)
apple-touch-icon.png # iOS home-screen icon (180×180)
The favicon is embedded inline (SVG data URI), so it needs no file. The og:image and apple-touch-icon use absolute/root paths, so they only resolve once the PNGs are at the domain root.
Serving from a real domain (e.g. https://privacyscore.me/) is also what makes the ToS;DR live sync work without any CORS workaround.
Every account is scored on one idea: how sensitive is the data you keep there, how risky is the recipient, and how much have you done to limit it.
exposure = data sensitivity × recipient risk × (1 − mitigations)
surveillance 1.6 → oss 0.6), is nudged by policy flags (selling, third-party sharing, ad profiling, AI training, indefinite retention raise it; E2EE, deletion, transparency reports, EU/Swiss jurisdiction lower it), then multiplied by the live ToS;DR grade coefficient when available. Clamped to [0.3, 2.4].Each account becomes a 0–100 rating. The overall score blends them (weighting the leakiest a little heavier), subtracts a concentration penalty (one company holding many data types) and posture gaps (no password manager, reused passwords, …), and maps to a letter grade.
A longer prose write-up of the model ships as Personal-Privacy-Score-Documentation.docx.
Every knob is a single named constant near the top of the script. Adjusting the model means editing these, not chasing logic through the code.
| Constant | Purpose |
|---|---|
DATA_TYPES[k].w |
Sensitivity weight per data type. |
MITIGATIONS[k].r |
Risk reduction per mitigation (sum capped at 0.70). |
business-model bases in recipientMultiplier |
surveillance 1.6 · freemium 1.3 · paid 1.0 · nonprofit 0.8 · oss 0.6. |
flag adjustments in recipientMultiplier |
How much each policy flag moves recipient risk. |
GRADE_COEF |
ToS;DR grade → multiplier (A 0.75 … E 1.35). |
INHERITED_WEIGHT |
How hard an inherited grade pulls (0 = ignore inherited grades, 1 = treat as direct). Default 0.6. |
clamp in recipientMultiplier |
[0.3, 2.4] bound on the final multiplier. |
accountRating factor |
The 1.15 exposure→penalty scale. |
bands in letter() |
Score → A/B/C/D/E/F thresholds. |
POSTURE[*].penalty |
Points docked per global-posture gap. |
The catalog is a flat array (CATALOG). Adding or improving a service is the most common change a fork makes.
{
id: "gmail", // unique slug (also the key into TOSDR_DOMAINS)
name: "Gmail", // display name
cat: "Email", // category label — new values appear automatically
parent: "Google", // parent company (drives concentration + grade fallback)
model: "surveillance", // surveillance | freemium | paid | nonprofit | oss
juris: "US", // jurisdiction; EU/CH/DE/FR/… reduce recipient risk
collects: ["comms","contacts","identity","telemetry"], // keys from DATA_TYPES
flags: { shares3p:1, adProfiling:1, retention:1, delete:1, transp:1 },
alts: [ // privacy-respecting alternatives, best first
{ n: "Proton Mail", w: "E2E encrypted, Swiss jurisdiction, paid not ad-funded" }
]
}
flags keys: sells, shares3p, adProfiling, trainsAI, retention (raise risk); e2ee, delete, transp (lower it). Use truthy 1.
Domain for live grades: add TOSDR_DOMAINS["<id>"] = "domain.com" so the service can match a ToS;DR grade. Pick the domain ToS;DR is most likely to list; the matcher strips www. and reduces subdomains.
alts concrete and honest: name the alternative and the one specific reason it's better.Built against the same endpoints the official ToS;DR browser extension uses.
| Endpoint | Returns | Used for |
|---|---|---|
GET https://api.tosdr.org/appdb/version/v2 |
[{ id, url, rating }] (url = comma-separated domains) |
One bulk fetch; matched to services locally by domain. |
GET https://api.tosdr.org/service/v3?id={id}&lang=en |
{ name, rating, points:[{ case:{ classification, localized_title } }] } |
The specific reviewed points behind a grade, loaded on demand. |
Matching (gradeForDomain): normalize (www. stripped), look up the domain, and if absent progressively drop subdomains. attachGrade tries the service's own domain first, then falls back to PARENT_DOMAINS[parent]; a fallback match is flagged tosdrInherited and rendered with a dashed chip + ↑.
CORS / degradation: the bulk call is attempted without custom headers first (avoiding a preflight) and retried with ToS;DR's public apikey only on 401/403. If the browser blocks it, the tool falls back to the baseline and says so. Serving from a domain resolves it.
Attribution: ToS;DR data is CC BY-SA 3.0. The in-app data-source footnote credits tosdr.org — preserve it. The dataset is fetched at runtime and not redistributed in this repo.
State is held in memory only. Persistence is an explicit file the user downloads and re-imports.
personal-privacy-score-YYYY-MM-DD.json){
"_tool": "personal-privacy-score",
"_version": 1,
"_exported": "<ISO timestamp>",
"score": { "score": 0, "grade": "—" },
"accounts": [ /* the user's services with their data/mitigation selections */ ],
"posture": { "pwmgr": true, "unique": false, "...": null }
}
importJSON only requires accounts; older exports remain importable. Do not add fields that would be embarrassing to find in a file the user shares.
.
├── index.html # the entire app (rename of privacy-audit.html)
├── og-image.png # social preview (1200×630)
├── apple-touch-icon.png # iOS icon (180×180)
├── README.md
├── LICENSE # MIT — see License section
└── docs/
└── Personal-Privacy-Score-Documentation.docx # prose write-up of the model
The current build artifact is named
privacy-audit.html; rename it toindex.htmlfor the repo/host.
The OG image and icons were generated from SVG with cairosvg using IBM Plex Mono (the app's mono face). To regenerate: install cairosvg, make IBM Plex Mono available to fontconfig, build the SVGs (dark #0F1117 background, the red→amber→teal segmented meter, mono type), and render og-image.png at 1200×630 and apple-touch-icon.png at 180×180. The favicon is a 32×32 SVG (three ascending score-bars) embedded inline as a base64 data URI in <head>.
This project is unmaintained — I'm not reviewing issues or pull requests, by choice. It's a finished, self-contained tool, and the most useful thing you can do with it is take it and run.
The MIT license lets you do exactly that: fork it, host it, rename it, strip it for parts, or build something better on top of it. A few pointers if you do:
index.html. Serve it locally (python3 -m http.server) and exercise the flow: add services, sync ToS;DR, generate a report, export/import.Directions a fork might take are listed under Ideas for forks.
Things a fork could take on, roughly ordered — left here as a map, not a to-do list anyone is working through:
lang; the UI strings are inline and ready to externalize).robots.txt + sitemap.xml for the hosted site.fetch, ES6, CSS grid/flex, and CSS custom properties. No legacy/IE support.prefers-reduced-motion is respected; visible keyboard focus and the print stylesheet are in place.LICENSE file at the repo root (your name, the current year) — that's the only file you need to add to make this official.Created by Zachary A. Perlman.
Personal Privacy Score is a privacy heuristic and an educational aid — not legal advice or a compliance assessment. Built-in flags are a coarse seed and ToS;DR grades change; treat every rating as a starting point and verify the current policy for anything that matters.
MIT licensed · local-first · created by Zachary A. Perlman · privacyscore.me