Local cookie-store counter-intelligence. Parses browser cookie databases, reconstructs what the ad-tech ecosystem can infer about you, and flags concrete threats - all fully offline, on your machine.
Named after the Building from Stanisław Lem's Memoirs Found in a Bathtub: an environment of total surveillance where every artifact may be a message and every message may be a cipher. The tool is named after the adversary it maps.
python gmach.py # scan Chrome / Edge / Brave / Firefox profiles
# -> gmach_report.json + gmach_purge.json
open dashboard.html # drag & drop the report (or serve both via http.server)
No report yet? The dashboard ships with a synthetic-data preview mode.
- Exposure profile - interest sectors inferred from cookie domains (explicitly labeled as inference, not fact)
- Activity rhythm - 7×24 heatmap from
last_accesstimestamps: the same behavioral pattern every tracker on your list can reconstruct server-side - Sync bursts / tracking seed events - clusters of tracker cookies created within a 10-second window: one page visit seeding a wave of tracking, pointing at seeder sites
- Cross-browser correlation - trackers present in ≥2 browsers, i.e. domains for which browser separation provides no isolation
- Trend & diff - append-only run history (
gmach_history.jsonl), sparklines across runs, new/removed domains with new trackers highlighted - Findings engine - severity-ranked, each finding tagged with provenance:
measured/heuristic/inference - Purge list - actionable
gmach_purge.json(stale + ad-tech candidates); the tool never writes to live browser databases
| id | severity | trigger |
|---|---|---|
| auth-no-secure | high | session/auth-named cookie without Secure |
| persistent-id-cookies | high | entropy >3.5 bit/char, len ≥16, lifetime >30d (Firefox only) |
| tracking-seed-events | medium/high | ≥3 tracker domains created within 10s |
| samesite-none-insecure | medium | SameSite=None without Secure |
| lifetime-gt-400d | medium | beyond the Chrome lifetime cap |
| adtech-footprint | medium/high | ad-tech domain density |
| fingerprinting-adjacent | medium | device-intelligence / anti-bot domains |
| new-tracker-domains | medium | tracker domains absent in the previous run |
| cross-browser-tracking | medium | same tracker in ≥2 browsers |
| stale-cookies | low | unused >180d, still valid |
| identity-graph | info | cross-service login ecosystems |
| session-hijack-surface | high/medium | auth/session cookies stealable via XSS/network |
| jwt-identity-exposure | medium | JWTs carrying decoded identity claims |
| pii-in-cookies | high | email/phone/uuid/ip found inside cookie values |
By default the tool reads only metadata. The confidential layer goes deeper into what cookies actually carry - while keeping raw secrets out of any shareable artifact:
- JWT decode - header + payload of your own tokens (no signature verification): issuer, subject, email, scope, expiry. Shows what services store about you and whom they trust. PII masked.
- Token classifier - session / auth / jwt / oauth_bearer / csrf / opaque_id.
- In-value PII scan - emails, phones, UUIDs, embedded IPs. Masked in the report.
- Theft-impact score (0-100) - per cookie: what an attacker gains if it leaks, weighted by missing HttpOnly (XSS-stealable), missing Secure (network-interceptable), weak SameSite, lifetime.
- Session-hijack surface - auth/session cookies ranked by theft impact.
- Re-identification index - relative signal (inference) of how uniquely identifiable you are across the tracker set + PII/JWT presence.
Privacy architecture - deliberate, and the point:
- The main
gmach_report.jsonnever contains raw values. Confidential data is derived and masked (prefix + length, redacted claims). - Firefox values are plaintext, so the confidential layer runs by default (masked output only).
--decrypt(opt-in) decrypts Chromium values using the current user's own OS credentials - self-scoped, the same operation the browser performs; App-Bound-Encrypted stores return no key and are skipped.--reveal(opt-in) is the only way raw values leave memory: they go to a separategmach_confidential.json, chmod 600, warning-stamped, gitignored. Treat it like a password file: analyze, then delete. A file of live session tokens is a liability, not a feature - the tool is built so you have to ask for it explicitly.
- Chromium decryption is opt-in, not default (
--decrypt). Default analysis runs on metadata. When enabled, it uses the current user's own OS key material and is self-scoped by the OS crypto. - Raw values never enter the main report or history. They leave memory only with
--reveal, into a separate gitignored 0600 file. The shareable report is always value-free. - Snapshot-copy before read - locked SQLite files from a running browser are handled safely; source stores are opened read-only, never modified.
- Provenance on every claim. Tracker classification and naive eTLD+1 are explicitly labeled heuristics; the method in use is stamped in report meta and the dashboard footer.
- Fully offline dashboard. Zero CDN, zero external fonts, zero network calls beyond an optional local report fetch. Works air-gapped.
pip install publicsuffix2- upgrades eTLD+1 grouping from the built-in naive heuristic (fallback is automatic and labeled)python gmach.py --tracker-list services.json- swap the built-in tracker snapshot for a Disconnect-format list
last_accessis overwritten by browsers: the rhythm heatmap is a snapshot of last touches, not full visit history.- Diff compares domain sets between runs. Changing scan scope (different
--path, added browser) reflects scope change, not behavior - keep scope constant for a meaningful trend. Natural usage pattern: a weekly scheduled task. - Third-party status is proxied by domain classification, not by request-context measurement.
- No entropy-based ID detection for Chromium (values encrypted by design choice above); metadata analysis remains complete.
- Built-in tracker list is a curated snapshot, not exhaustive - hence the Disconnect hook.
- Findings and dashboard UI are currently in Polish; i18n is on the backlog.
MIT