alejandrocora/WhatsAppMediaEnumeration

Self-scope, zero-send tool (and white paper) that traces WhatsApp media lineage through the mediaKey its dedup cache reuses across chats.

5

stars

2

commits

TypeScript

primary language

Aug 21, 2026

updated

README

WhatsApp Media Enumeration (wenum)

License: MIT TypeScript Node.js Python Playwright White paper

A self-scope proof of concept: WhatsApp's media deduplication, combined with its end-to-end media encryption, leaks media lineage to any ordinary, user-level client.

Summary

To save bandwidth, a WhatsApp client keeps a content-addressed media dedup cache and reuses the same mediaKey (the end-to-end key the server never sees) and directPath whenever it re-sends or forwards media it already holds. Because that key rides the whole forward chain, two copies sharing a mediaKey descend from one upload. So a user-level client can read, from its own local storage:

  • whether a given media was ever sent through the account (fileSha256 match);
  • whether two media pieces share an origin, and which chats a blob has touched (reused mediaKey);
  • when a blob was first uploaded (mediaKeyTimestamp, the blob's mint time).

The same secret that hides content from the server exposes its provenance to the client. Neither the encryption nor the deduplication is faulty on its own; the leak is emergent from their combination.

Full details in the white paper: English · Español.

Installation

Requirements: Node.js 20+ (Python 3 optional, only for the tools/ image generators).

git clone https://github.com/alejandrocora/WhatsAppMediaEnumeration.git
cd WhatsAppMediaEnumeration
./install.sh

Try it

./wenum login                 # opens a QR — scan it from WhatsApp to link the session
./wenum detect photo.jpg      # was this media ever sent through the account?
./wenum forward --repeats     # media reused across several chats (same mediaKey)
./wenum forward --timestamps  # every forward: message time vs blob mint time
./wenum forward --repeats --svg --output map.svg   # render an image (add --masked to anonymise)

All commands

Run ./wenum help for the full list, or ./wenum <command> --help for one command's own options.

CommandWhat it does
loginLink a WhatsApp account by QR (run once).
detect FILE…Was this exact image/document ever sent through my account? (matches fileSha256).
forwardForward provenance for reused-mediaKey media (see the sub-modes below).
dump-historyExport the synced history to a file for offline runs (--history FILE).
inspectDump the full stored record(s) for a media (by file / --filehash / --date).
check-directpathIs a media's server blob still reachable? (retention / expiry).
downloadDownload + decrypt a stored blob to .data/downloads/.
scan-hashSearch every local store for a fileSha256.
window-depthHow far back the synced history reaches (with a per-month histogram).
dump-mediaList every synced media descriptor (filehash / type / size / dimensions).

forward sub-modes:

forward …What it shows
--repeatsBlobs with an identical mediaKey surfacing across ≥N chats (--min-chats N).
--timestampsEvery forward's message time vs blob mint time; --many keeps only "forwarded many times".
--stickersSticker media grouped by blob (the easiest tracer).
--map / --svg / --artRender a blob's chronological chain (JSON / SVG image / console).
--file PATHTrace one specific media instead of sweeping everything.

Selectors that narrow any scan or pin one blob: --filehash, --mediakey, --from NUMBER, --name SUBSTR, --recent N, --all, --masked, --json.

Scope and responsible use

Self-scope only: drive your own linked session against your own synced history. This is a defensive/forensic proof of concept and a research artifact; do not use it to deanonymize or track other people.

License

MIT — free to use and modify; please keep attribution to the original author (Alejandro Zapico Lara), including for the white paper.

Contributors

alejandrocora

2 commits

alejandrocora/WhatsAppMediaEnumeration

Self-scope, zero-send tool (and white paper) that traces WhatsApp media lineage through the mediaKey its dedup cache reuses across chats.

5

stars

2

commits

TypeScript

primary language

Aug 21, 2026

updated

README

WhatsApp Media Enumeration (wenum)

License: MIT TypeScript Node.js Python Playwright White paper

A self-scope proof of concept: WhatsApp's media deduplication, combined with its end-to-end media encryption, leaks media lineage to any ordinary, user-level client.

Summary

To save bandwidth, a WhatsApp client keeps a content-addressed media dedup cache and reuses the same mediaKey (the end-to-end key the server never sees) and directPath whenever it re-sends or forwards media it already holds. Because that key rides the whole forward chain, two copies sharing a mediaKey descend from one upload. So a user-level client can read, from its own local storage:

  • whether a given media was ever sent through the account (fileSha256 match);
  • whether two media pieces share an origin, and which chats a blob has touched (reused mediaKey);
  • when a blob was first uploaded (mediaKeyTimestamp, the blob's mint time).

The same secret that hides content from the server exposes its provenance to the client. Neither the encryption nor the deduplication is faulty on its own; the leak is emergent from their combination.

Full details in the white paper: English · Español.

Installation

Requirements: Node.js 20+ (Python 3 optional, only for the tools/ image generators).

git clone https://github.com/alejandrocora/WhatsAppMediaEnumeration.git
cd WhatsAppMediaEnumeration
./install.sh

Try it

./wenum login                 # opens a QR — scan it from WhatsApp to link the session
./wenum detect photo.jpg      # was this media ever sent through the account?
./wenum forward --repeats     # media reused across several chats (same mediaKey)
./wenum forward --timestamps  # every forward: message time vs blob mint time
./wenum forward --repeats --svg --output map.svg   # render an image (add --masked to anonymise)

All commands

Run ./wenum help for the full list, or ./wenum <command> --help for one command's own options.

CommandWhat it does
loginLink a WhatsApp account by QR (run once).
detect FILE…Was this exact image/document ever sent through my account? (matches fileSha256).
forwardForward provenance for reused-mediaKey media (see the sub-modes below).
dump-historyExport the synced history to a file for offline runs (--history FILE).
inspectDump the full stored record(s) for a media (by file / --filehash / --date).
check-directpathIs a media's server blob still reachable? (retention / expiry).
downloadDownload + decrypt a stored blob to .data/downloads/.
scan-hashSearch every local store for a fileSha256.
window-depthHow far back the synced history reaches (with a per-month histogram).
dump-mediaList every synced media descriptor (filehash / type / size / dimensions).

forward sub-modes:

forward …What it shows
--repeatsBlobs with an identical mediaKey surfacing across ≥N chats (--min-chats N).
--timestampsEvery forward's message time vs blob mint time; --many keeps only "forwarded many times".
--stickersSticker media grouped by blob (the easiest tracer).
--map / --svg / --artRender a blob's chronological chain (JSON / SVG image / console).
--file PATHTrace one specific media instead of sweeping everything.

Selectors that narrow any scan or pin one blob: --filehash, --mediakey, --from NUMBER, --name SUBSTR, --recent N, --all, --masked, --json.

Scope and responsible use

Self-scope only: drive your own linked session against your own synced history. This is a defensive/forensic proof of concept and a research artifact; do not use it to deanonymize or track other people.

License

MIT — free to use and modify; please keep attribution to the original author (Alejandro Zapico Lara), including for the white paper.

Contributors

alejandrocora

2 commits

Languages

TypeScript

88.5%

Python

10.3%

Shell

1.2%