- Docs
- Recipe: Snitch Privacy
Recipes
Recipe: Snitch Privacy
One line. One command. Any URL. One signed forensic report.
The demo
pluck snitch https://example.com --signing-key ./keys/snitch.pem -o report.json
That's it. Pluck fetches the URL, runs every privacy-relevant scanner across the response, and writes a structured report. Pass --signing-key and the whole report is wrapped in an Ed25519-signed receipt you can share, archive, or hand to counsel.
What snitch checks
pluck snitch is a forensic composition: connect + navigate + extract + sense + receipts. It runs each scanner against the same fetched payload and aggregates the findings:
- Known trackers – Google Analytics, Meta Pixel, TikTok Pixel, Hotjar, Segment, Amplitude, Mixpanel, Heap, New Relic, DataDog RUM, and 40+ more. Each match records the script pattern + occurrence count.
- Fingerprinting signatures – canvas fingerprinting (
canvas.toDataURL,measureText), WebGL fingerprinting (getSupportedExtensions,getParameter), audio fingerprinting (AudioContext,OscillatorNode), battery API, enumerated fonts, screen geometry, WebRTC IP leak. Each signature carries a weight (0..1) so confident matches dominate the risk score. - Third-party hosts – every external origin the page talks to, grouped by scheme. Flags
http://requests on anhttps://page as downgrade warnings. - Ultrasonic cross-device beacons – when the page embeds autoplay audio, snitch senses >18 kHz carriers (Lisnr / Silverpush / SonicNotify).
- PII pattern leaks – emails, phone numbers, SSNs, credit-card shapes emitted as visible text or query parameters.
- Security headers – presence/absence of CSP, HSTS, Referrer-Policy, Permissions-Policy, X-Frame-Options.
Every scanner contributes a weighted finding; snitch rolls them into a single riskScore (0..100) and a verdict (clean | moderate | high | critical).
The report shape
interface SnitchReport {
url: string;
fetchedAt: string; // ISO 8601
contentHash: string; // sha256 of the fetched body
trackers: SnitchTracker[];
fingerprinting: SnitchFingerprinting[];
thirdParties: SnitchThirdParty[];
ultrasonicCarriers?: { // present only when sense ran
carrierHz: number;
confidence: number;
}[];
piiLeaks: {
kind: "email" | "phone" | "ssn" | "credit-card" | "other";
occurrences: number;
sample?: string; // redacted
}[];
securityHeaders: Record<string, string | undefined>;
riskScore: number; // 0..100
verdict: "clean" | "moderate" | "high" | "critical";
}
When signed, the whole report lives in receipt.receipt of a SignedReceipt:
interface SignedSnitchReceipt extends SignedReceipt {
action: "snitch";
receipt: SnitchReport;
}
Verify with the public key alone – no Pluck required:
import { verifyReceipt } from "@sizls/pluck";
const ok = verifyReceipt(signedReport, publicKeyPem);
// true | false – standalone, no network
Programmatic use
From TypeScript:
import { snitch } from "@sizls/pluck";
const signed = await snitch("https://example.com", {
signingKey: process.env.PLUCK_SIGNING_KEY,
});
console.log(signed.receipt.verdict); // "moderate"
console.log(signed.receipt.riskScore); // 38
console.log(signed.receipt.trackers); // [{ name: "google-analytics", ... }]
// Share the whole receipt – it verifies offline with the matching public key.
await fs.writeFile("./report.json", JSON.stringify(signed, null, 2));
Generating your keys
Generate a durable Ed25519 keypair once with the CLI:
pluck keys generate --name pluck --dir ./keys
# Writes ./keys/pluck.pem (private) and ./keys/pluck.pub.pem (public)
Ship pluck.pub.pem with your audits so downstream readers can verify. Load pluck.pem into PLUCK_SIGNING_KEY via your secret manager.
Without a signing key, snitch() returns the raw SnitchReport:
const report = await snitch("https://example.com");
// typeof report === "SnitchReport" (unsigned)
Why this matters
Every privacy audit tool on the market today has a trust problem:
- Browser devtools show you what's happening on your machine at this moment. No provenance, no archival value.
- Third-party auditors produce PDFs. PDFs are not machine-verifiable and can be edited.
- Open-source scanners (e.g. webbkoll, Blacklight, HTTPS-Everywhere reports) produce JSON but not signed JSON. You can tamper with the output.
pluck snitch is the first tool that produces cryptographically signed privacy audits from the command line. The signature means:
- A journalist can publish a snitch report and readers can independently verify nobody edited it.
- A regulator can audit a year's worth of snitch reports and be confident each one is untampered.
- A developer can commit snitch reports to their repo as regression fixtures – any diff is real, not drift.
This is what makes Pluck not another scraper – the forensic chain of custody lives in the library, not in a hosted service.
Pairing with monitors
Every pluck snitch run produces a contentHash. Wire it into the monitor system (REST /v1/monitors or the pluck watch CLI) and you get continuous privacy-drift detection – every time the page's tracker soup changes, a new signed report lands in your audit log. See Recipe: DriftWatch Fleet for the full pattern applied to SSH fleet config drift.
The ten-line Show HN version
import { snitch } from "@sizls/pluck";
import { writeFileSync } from "node:fs";
for (const url of process.argv.slice(2)) {
const report = await snitch(url, { signingKey: process.env.PLUCK_SIGNING_KEY });
writeFileSync(`./reports/${new URL(url).hostname}.pluckfinding`, JSON.stringify(report, null, 2));
console.log(`${url} – ${report.receipt.verdict} (risk ${report.receipt.riskScore})`);
}
Run it against any list of URLs and you get a portable, verifiable forensic bundle.
What's next
- Concepts: Sense – how the ultrasonic beacon detector works.
- Concepts: Act – the receipt signing primitives snitch reuses.
- Recipe: DriftWatch Fleet – the SSH-fleet equivalent with Merkle-chained audit log.
- MCP-First Pipeline –
pluck_snitchas an MCP tool your agent can call.