Skip to content

Recipes

Recipe: Snitch Privacy

One line. One command. Any URL. One signed forensic report.


The demo

Shell
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:

  1. 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.
  2. 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.
  3. Third-party hosts – every external origin the page talks to, grouped by scheme. Flags http:// requests on an https:// page as downgrade warnings.
  4. Ultrasonic cross-device beacons – when the page embeds autoplay audio, snitch senses >18 kHz carriers (Lisnr / Silverpush / SonicNotify).
  5. PII pattern leaks – emails, phone numbers, SSNs, credit-card shapes emitted as visible text or query parameters.
  6. 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

TypeScript
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:

TypeScript
interface SignedSnitchReceipt extends SignedReceipt {
  action: "snitch";
  receipt: SnitchReport;
}

Verify with the public key alone – no Pluck required:

TypeScript
import { verifyReceipt } from "@sizls/pluck";

const ok = verifyReceipt(signedReport, publicKeyPem);
// true | false – standalone, no network

Programmatic use

From TypeScript:

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:

Shell
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:

TypeScript
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

TypeScript
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

Edit this page on GitHub
Previous
DriftWatch Fleet

Ready to build?

Install Pluck and follow the Quick Start guide to wire MCP-first data pipelines into your agents and fleets in minutes.

Get started →