A comprehensive Chrome extension that audits websites in real-time for trackers, fingerprinting, CSP weaknesses, cookie risks, network behavior, and privacy policy violations — all in one powerful popup.
📹 Demo GIF coming soon — see capture guide
Most people have no idea how many companies watch them on an average web page — analytics SDKs, ad pixels, session recorders that replay every mouse move, and fingerprinting scripts that identify you without cookies. The tools that do expose this are either oversimplified ("X trackers blocked") or buried in DevTools.
I wanted a single popup that answers, in plain language, "how is this site treating my data, and how bad is it?" — a real privacy score, the actual tracker names and what they do, the fingerprinting techniques in use, and the server's security posture (CSP, referrer policy, mixed content). Then one click to block it all.
It started as a way to learn the Manifest V3 extension model deeply, and grew into a full auditing tool.
These are the problems that were interesting to solve, beyond the feature list:
- MV3 service-worker state loss — Chrome kills the worker after ~30s idle, wiping in-memory scan data. I mirror state to
chrome.storage.sessionwith a debounced snapshot and rehydrate on wake-up, so the popup never shows a blank page. Read pathsawaitrestore to avoid a race on cold start. - CSP-bypassing fingerprint hooks — fingerprinting APIs (Canvas, WebGL, Audio…) must be hooked in the page's own world, but strict CSP blocks injected
<script>. I use aworld: "MAIN"content script that patches the prototypes while preservingthisbinding and original behavior, relaying signals to an isolated-world script. - Header capture race —
tabs.onUpdated(loading)would reset tab data afteronHeadersReceivedhad already written the CSP, losing it. Headers now live in dedicated caches separate from the reset path. - Testable core, untestable shell — all scoring, tracker matching, CSP grading, and referrer analysis live in pure modules (
lib/scoring.js,lib/headers.js) with 45 unit tests runnable under Node, while DOM/chrome.*code stays in the shell. One source of truth: the service worker loads the same files viaimportScripts, the popup via<script>. - Production-safe block counter — the obvious
onRuleMatchedDebugAPI is dev-mode only. The lifetime counter detects its absence and falls back to estimating matches insideonBeforeRequestso it keeps working in published builds.
Vanilla JS (no framework) · Manifest V3 · declarativeNetRequest · webRequest · Node built-in test runner · GitHub Actions CI (Node 20 + 22) · Gemini API for AI explanations
| Feature | Description |
|---|---|
| Tracker Detection | 150+ known tracker domains across 12 categories (Analytics, Ads, Session Recording, Data Brokers, Fingerprinting, Consent, etc.) |
| Fingerprinting Detection | Canvas, WebGL, Audio, Font, Battery, Navigator API hooks via Main-world injection |
| Privacy Score | Animated 0–100 gauge with color-coded risk levels and score trend arrow |
| First-party Warning | Flags known data-collector sites (Facebook, TikTok, YouTube) with fixed penalties |
| Corporate Family Awareness | Avoids false positives — won't flag fbcdn.net on facebook.com |
| HTTPS / Mixed Content | Detects HTTP resources loaded on HTTPS pages |
- Full list of detected tracker domains grouped by name + category
- Risk level badge per tracker (low / medium / high / critical)
- Request count per tracker
- Smart deduplication — same tracker from multiple domains counted once
- Detected APIs with call count per technique
- Visual signal strength bar
- "Clean / Low / Medium / High" risk assessment
- Grades A → D (grade F reserved for CSPs with both
unsafe-inline+unsafe-eval) - "No CSP" shown as D — Not Recommended (not falsely flagged as Critical)
- Per-directive issue list with severity: critical / high / medium
- Highlights unsafe keywords in red, safe keywords in green
- "Add CSP" guidance card when header is missing
- Audits 12 browser permission categories (geolocation, camera, mic, clipboard, etc.)
- Shows granted vs. requested vs. blocked count
- Powered by content script DOM analysis
- Cookie count, localStorage, sessionStorage, session recorders, external/total requests
- Referrer Policy Checker — grade A/B/C/D with per-policy description and fix tips:
no-referrer/strict-origin-when-cross-origin→ Aorigin→ Bno-referrer-when-downgrade/ missing → Cunsafe-url→ F
- Export buttons: JSON report + full HTML report
- Full cookie inspector with search + filter (All / Risky / Session / Persistent)
- Risk scoring per cookie (SameSite, Secure, HttpOnly, expiry, suspicious names)
- Delete individual cookies or "Delete All"
- Risk summary badge (🔴 X critical)
- Captures up to 250 requests per tab with relative timestamps
- Stacked distribution bar (JS / CSS / XHR / IMG / Fetch / Font)
- Filter chips: All / 🔍 Trackers / 🌐 External / JS / XHR / IMG
- Per-request waterfall timeline bar proportional to load time
- Tracker rows highlighted in red
- Hover tooltip: full URL + timing + tracker flag
- Persistent site history with privacy scores
- Whitelist manager — individual domains and wildcard subdomains
- Clear history button
- Bell icon with unread badge count
- Categorized alerts: tracker detected / high-risk fingerprinting / score drop
- Dismissible notifications with timestamps
- 7-day sparkline in popup header (canvas mini-chart)
- Weekly bar chart modal (Trackers / Fingerprint / Score over 7 days)
- Per-day breakdown with trend arrow vs. previous week
- "Explain this scan with AI" button
- Sends scan results to Gemini API and surfaces human-readable summary
- One-click block all tracking requests for current tab
- Badge changes to "🔴 BL" when active
- Configurable rescan intervals: 15s / 30s / 1m / 5m
- Countdown progress bar in header
- Stops automatically when popup closes
- JSON — machine-readable full scan dump
- HTML — styled standalone report with score, tracker table, CSP analysis
-
Clone this repository:
git clone https://github.com/tridpt/privacy-auditor.git
-
Open Chrome →
chrome://extensions/ -
Enable Developer mode (toggle top-right)
-
Click Load unpacked → select the
privacy-auditor/folder -
Navigate to any website, reload the tab, then click the extension icon
⚠️ Important: Always reload the target tab after installing/updating the extension to let the network listener capture all requests from the start.
privacy-auditor/
├── manifest.json # MV3 manifest — permissions & content scripts
├── background.js # Service worker — network & CSP capture, blocking, persistence
├── injected.js # Main-world script — fingerprint API hooks (bypasses page CSP)
├── content.js # Isolated-world script — DOM scan, relay fingerprint signals
├── popup.html/js/css # Popup UI — 8 tabs, all feature engines
├── options.html/js/css# Extension options page
├── lib/
│ ├── scoring.js # Pure tracker DB + scoring logic (shared, unit-tested)
│ └── headers.js # Pure CSP + referrer-policy analysis (shared, unit-tested)
├── tests/
│ ├── scoring.test.js # 25 Node unit tests for the scoring engine
│ └── headers.test.js # 20 Node unit tests for header analysis
└── build-zip.mjs # Web Store packaging script
📐 For a deep dive into the data flow, execution contexts, and design decisions, see docs/ARCHITECTURE.md.
background.jsloadslib/scoring.jsviaimportScripts()so the tracker database and scoring functions live in a single source of truth that is also unit-tested under Node — no duplication between runtime and tests.
Page loads
├── injected.js (MAIN world) → hooks Canvas/WebGL/Audio/Font/Battery APIs
├── content.js (isolated) → relays fingerprint signals, scans DOM
└── background.js → onBeforeRequest → logs to requestLog[] (≤250)
onHeadersReceived → captures CSP + Referrer-Policy
tabs.onUpdated → initTabData, badge update
│
▼
User opens popup
│
▼
popup.js sends messages to background (GET_DATA, GET_CSP, GET_REFERRER_POLICY,
GET_REQUEST_LOG, GET_COOKIES, GET_PERMS, GET_WHITELIST …)
│
▼
Score calculated + all tabs rendered lazily (Network waterfall on tab switch)
| Score | Rating |
|---|---|
| 80–100 | 🟢 Good Privacy |
| 60–79 | 🟡 Fair Privacy |
| 40–59 | 🟠 Poor Privacy |
| 20–39 | 🔴 Bad Privacy |
| 0–19 | ⛔ Very Invasive |
Score Penalties:
| Condition | Penalty |
|---|---|
| Each unique tracker | −5 pts (max −35) |
| Session recorder detected | −20 pts |
| High-risk trackers | −2 pts each (max −10) |
| Canvas/WebGL/Audio fingerprint | −5 pts each (max −15) |
| External requests > 100 | −22 pts |
| Known data-collector (FB, TikTok…) | Fixed penalty |
| HTTPS missing | −10 pts |
| Mixed content | −5 pts |
| Category | Examples |
|---|---|
| Analytics | Google Analytics, Amplitude, Mixpanel, Segment, Heap |
| Advertising | Facebook Pixel, TikTok Pixel, DoubleClick, Criteo, AdRoll |
| Session Recording | Hotjar, FullStory, Microsoft Clarity, Lucky Orange |
| Tag Management | Google Tag Manager |
| A/B Testing | Optimizely, VWO |
| Chat / CRM | Intercom, Drift, Zendesk |
| Social | Twitter Widget, LinkedIn Insight, Pinterest Tag |
| Technique | APIs Monitored |
|---|---|
| 🎨 Canvas | toDataURL(), toBlob(), getImageData() |
| 🔺 WebGL | getContext('webgl'), getParameter(), getExtension() |
| 🔊 Audio | AudioContext, OscillatorNode, AnalyserNode |
| 🔤 Font | document.fonts.check() |
| 🔋 Battery | navigator.getBattery() |
| 🧭 Navigator | Systematic multi-property read pattern |
| Permission | Purpose |
|---|---|
webRequest |
Monitor outgoing requests (tracker + network waterfall) |
cookies |
Accurate cookie reading including HttpOnly |
tabs |
Detect current tab URL |
scripting |
Inject main-world fingerprint hooks |
storage |
Persist history, whitelist, notification state |
declarativeNetRequest |
Block tracking requests via rule engine |
notifications |
Browser-level alerts for high-risk detections |
contextMenus |
Right-click "Audit this page" shortcut |
- Manifest V3 compatible service worker architecture
- Single source of truth — all pure scoring/tracker logic lives in
lib/scoring.js, loaded into the service worker viaimportScripts()and unit-tested under Node. Nochrome.*calls in that file - State survives worker restarts — MV3 kills the service worker after ~30s idle, wiping in-memory
tabData. A debounced snapshot is mirrored tochrome.storage.session(in-memory, cleared on browser close) and restored on wake-up, so scan data is not lost between popup opens - Production-safe block counter —
onRuleMatchedDebugonly fires for unpacked dev builds. When unavailable (Web Store builds), the lifetime-blocked counter falls back to a manual estimate insideonBeforeRequest - CSP bypass —
injected.jsusesworld: "MAIN"in the manifest, never injected viainnerHTML - Timing-safe header capture — CSP and Referrer-Policy stored in dedicated
cspCache/refPolCacheMaps to avoid race condition wheretabs.onUpdated(loading)would resettabDataafteronHeadersReceivedalready wrote the headers - Memory-safe request log — Network waterfall entries capped at 250 per tab
- Lazy tab rendering — Network waterfall fetched only on first tab switch via
tabSwitchcustom event - Double
requestAnimationFrame— Used before checking scroll dimensions to ensure browser has completed layout paint
Pure scoring logic in lib/scoring.js is covered by unit tests using Node's
built-in test runner (no dependencies to install):
npm testTests live in tests/scoring.test.js and cover calculateScore,
matchTracker, isSameFamily, getFirstPartyPenalty, scoreToColor, and the
integrity of the tracker database.
MIT License — see LICENSE