Personal Privacy Score

privacyscore.me

Personal Privacy Score

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.

single file no dependencies vanilla JS privacy license status

Live: https://privacyscore.me/

Preview

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.


Contents


Overview

The user works through three stages, with a live exposure meter and letter grade updating as they go:

  1. Inventory — add the services that hold your data from a catalog (search, browsers, email, messaging, social, OSes, AI assistants, cloud, maps, payments, smart home, wearables, connected cars) or add custom entries.
  2. Per-service questions — mark what data each service holds and what you've done to limit exposure (hardened settings, ad opt-out, aliases, no real name, private payment, 2FA).
  3. Posture & report — six questions on habits that apply everywhere, then a graded report with prioritized actions and privacy-respecting alternatives.

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).


Design principles & invariants

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).


Architecture

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.

Logic map (top to bottom of the <script>)

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.


Running locally

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.


Deployment

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.


How scoring works

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)

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.


Tuning the model

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 service catalog

The catalog is a flat array (CATALOG). Adding or improving a service is the most common change a fork makes.

Entry schema

{
  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.

Guidelines for catalog data


ToS;DR integration

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.


Data & privacy

State is held in memory only. Persistence is an explicit file the user downloads and re-imports.

Export schema (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.


Project structure

.
├── 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 to index.html for the repo/host.


Regenerating brand assets

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>.


Forking & reuse

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:

Directions a fork might take are listed under Ideas for forks.


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:


Browser support & accessibility


License


Acknowledgements

Created by Zachary A. Perlman.


Disclaimer

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