Darkwatch
A dark web exposure monitor for people and companies you are authorised to protect.
Website: https://rithikkrishnat.me/darkwatch/ · VS Code extension · Releases · PyPI
You list identifiers in a watchlist: names, emails, phone numbers, domains, usernames. Darkwatch checks them against ransomware leak sites, onion pages fetched over Tor, infostealer logs, and breach and paste data. It scores each hit by what surrounds it, remembers what it has already reported, and writes a report that says what to do about every hit. New hits trigger a Windows notification, and optionally a phone push, a webhook or an email. A scheduled task runs it daily.
There is a local dashboard for everything that is easier to see than to read: keyword search
across every finding, filters, charts, triage, live scans, and one-off investigations of a value
that is not on the watchlist. darkwatch web, or the "Darkwatch" shortcut on the Desktop.
It only reads. It never logs in, posts, buys, or downloads files, and it keeps no page bodies. Per finding it stores the URL, the title, the first and last time it was seen, a short snippet around the match, and a hash of the page so a changed page can be recognised — never the page.
What it searches
Ten sources. Nine of them need no key at all.
| Source | Needs | What it gives |
|---|---|---|
leaksites |
nothing | Every post that ransomware gangs have made on their leak sites, as crawled by ransomware.live (the full history since 2013, one bulk file cached for 12 hours) and RansomLook (last 30 days). Claim pages are never fetched. |
recentattacks |
nothing | Recently reported cyber-attacks and breaches — gang-claimed or not — from ransomware.live's incident feed, so an incident a victim discloses or the press covers is caught before any leak-site post exists. |
stealers |
nothing | Machines infected by credential-stealing malware that had a watched email, username or domain saved in the browser, via Hudson Rock's Cavalier OSINT API. This is the one source that says your own machine, rather than a service you used. The free tier masks the stolen values (P********3), which is all Darkwatch wants: enough to recognise the machine, never the credential. |
leakcheck |
nothing | Which breaches hold each email or username, and which data classes they exposed (ssn, dob, password, address). The only source that answers for a handle, so a person whose strongest identifier is a username is not invisible. |
xposedornot |
nothing | Breaches and pastes that included each email, plus breaches of the service at each watched domain, with the data classes exposed. The free tier allows 25 lookups per hour. |
hibp |
nothing / DARKWATCH_HIBP_KEY |
Whether a watched domain's own service was breached (free). With a paid key, per-email breaches and pastes too. |
telegram |
nothing | Public Telegram threat-actor and infostealer channels (from the community-maintained deepdarkCTI index, ~940 channels), searched through Telegram's no-login web preview. This is where much leak and stealer-log trading now happens. |
sites |
nothing | Which of 24 public sites carry each watched username — GitHub, X, YouTube, Mastodon, npm, Docker Hub, Hugging Face, Substack, Tumblr and more. A found profile is then read for the person's other identifiers, so a handle leads to the real name or email printed on that page. The checks run in parallel, since each site is a different host. |
ahmia |
Tor for pages | Ahmia's onion search index. With Tor verified, the search itself goes over Tor to Ahmia's onion service, and never over the clearnet (ahmia_route). Every listing is checked locally for the exact term, then matching pages are fetched over Tor as text, plus the top 5 per query. |
seeds |
Tor for onion URLs | Pages you choose, re-read every run, with same-host links followed one level deep. |
Every site in sites was verified against live responses with a handle known to exist and one
known to be free; a site that answers 200 for a free handle without a marker that proves absence
is left out, because it would report every handle as a profile. Add your own with person_sites.
Deep scan
darkwatch run --deep --open
The ordinary run is tuned to finish in a few minutes. --deep trades time for reach and can run
an hour or more:
- every source, whatever the watchlist enables;
- all ~940 Telegram channels instead of the first 40;
- onion depth raised — the blind top-N sweep goes 5 → 15 pages per query, on a 2,000-page budget instead of 150;
- links followed one level out of any onion page that already mentions a watched identifier, so Darkwatch reads the actual dark-web site, not just the search listing.
Two things --deep still will not do, by design:
- It never logs in, joins, pays, or solves a CAPTCHA, so forums and markets that wall their content behind an account (BreachForums successors, XSS, carding markets) are not reached — there is no read-only way in, and creating an account is out of scope.
- It never blind-crawls. Links are followed only out of pages that already match a watched identifier, and onion discovery still goes through Ahmia's abuse filter. Darkwatch does not wander an unfiltered index, which is what keeps illegal material off the machine.
The dashboard
darkwatch web # opens http://127.0.0.1:8787/?t=<token>
- Search every finding by keyword, across the term, title, URL, snippet, signals, target and source. Quote a phrase. Filters for severity, source, status, target and identifier type, with live counts.
- Charts: severity over the last 30 days, the mix by source, and what each run found.
- Triage in place — acknowledge, resolve, mark a false positive — with the recommended actions for each finding.
- Run a scan from the page and watch it happen: progress streams live over server-sent events.
- Investigate any value without adding it to the watchlist. Nothing is stored.
- Export the open findings as CSV or JSON.
It is locked down rather than merely convenient, because it serves personal data over HTTP:
it binds to loopback only, every API route requires a token minted at start-up (the link the
command prints carries it once), a non-loopback Host header is refused so DNS-rebinding fails,
no CORS header is ever sent, and changes are POST-only. Untrusted values — a leak-site title, an
onion page's text — are inserted as text nodes, never as HTML, and evidence URLs are shown but
never made clickable.
Install
Windows — paste into PowerShell (no administrator rights needed):
irm https://raw.githubusercontent.com/Daemon-VI/darkwatch/main/install.ps1 | iex
macOS / Linux:
curl -LsSf https://raw.githubusercontent.com/Daemon-VI/darkwatch/main/install.sh | sh
The installer sets up uv if you don't have it (uv fetches a
suitable Python itself), installs the latest release as the darkwatch command, asks who to
watch, offers Desktop shortcuts, and installs the VS Code extension if VS Code is present. Run it
again at any time to upgrade; your watchlist and findings are kept.
Already have uv? uv tool install <wheel URL from the latest release> does the same install.
From PyPI:
uv tool install darkwatch # or: pipx install darkwatch
Docker — the CLI with Tor already inside, for running scans anywhere (the dashboard stays local by design):
docker run --rm -v "$PWD/dw:/work" ghcr.io/daemon-vi/darkwatch setup --name "Acme Inc" --company
docker run --rm -v "$PWD/dw:/work" ghcr.io/daemon-vi/darkwatch run
Setup. darkwatch setup creates your Darkwatch folder (~/Darkwatch, or DARKWATCH_HOME)
with a watchlist for one person or company, asking for their name, emails, domains, usernames
and phone numbers. It can also run with no prompts:
darkwatch setup --name "Asha Rao" --email asha@example.com --username asharao --no-prompt.
Every command then finds that watchlist from any folder. A watchlist.yaml in the current folder,
or --watchlist <file>, takes precedence. Add more targets by editing the file.
darkwatch doctor checks the install and says what is missing.
From a source checkout (development):
git clone https://github.com/Daemon-VI/darkwatch.git
cd darkwatch
uv sync --all-extras
uv run darkwatch setup
uv run pytest -q
Tor. Darkwatch starts tor.exe for each run when nothing is already listening on the
proxy port, and stops it afterwards. It finds tor.exe in these places, in order:
- The
tor_exesetting. - The
DARKWATCH_TOR_EXEenvironment variable. PATH.tools\tor-*\tor\tor.exebeside the watchlist (or up to two folders above it), or in the Darkwatch folder — where an unpacked Tor Expert Bundle goes.- A Tor Browser install (Desktop,
%LOCALAPPDATA%or Program Files), e.g. afterwinget install TorProject.TorBrowser.
Without Tor, every source except the onion page fetches still runs. If Tor Browser is already
running, point tor_proxy at socks5h://127.0.0.1:9150 and Darkwatch will use it without
starting its own. Verify a downloaded Tor bundle's signature against the Tor Browser Developers
key EF6E 286D DA85 EA2A 4BA7 DE68 4E2C 6E87 9329 8290.
darkwatch check-tor # starts Tor if needed, then asks check.torproject.org
darkwatch sources # what each source searches and what limits it
Secrets live in .env beside the watchlist and never in the YAML. See .env.example:
DARKWATCH_HIBP_KEY, DARKWATCH_NTFY_TOPIC, DARKWATCH_WEBHOOK_URL, DARKWATCH_SMTP_*.
Use
darkwatch setup # first run: who to watch
darkwatch doctor # check the install
darkwatch web # the dashboard: search, charts, triage, live scans
darkwatch run --open # everything; opens the HTML report at the end
darkwatch run --sources leaksites # one source
darkwatch run --no-tor # no onion page fetches
darkwatch search "for sale" # keyword search over stored findings
darkwatch hits --json # stored hits as JSON (for tooling)
darkwatch search acme --json # search results + facets as JSON
darkwatch search --severity CRITICAL,HIGH --source leaksite
darkwatch search --facets # counts per severity, source, status, target, type
darkwatch investigate jane@mail.com # one value, live sources, nothing stored
darkwatch hits # open hits (new and acknowledged)
darkwatch hits --min-severity HIGH --snippets
darkwatch show 12 # one hit with its recommended actions
darkwatch ack 12 --note "rotated password"
darkwatch resolve 12
darkwatch false-positive 7 --note "different person"
darkwatch reopen 7
darkwatch report --open # rebuild the report from stored hits
darkwatch runs # history: duration, documents, hits, Tor
darkwatch scan-text dump.txt --save # check a file you already hold
darkwatch notify-test # synthetic alert through every configured channel
darkwatch shortcut # put the Desktop shortcuts in place
From a source checkout, prefix each command with uv run.
Desktop shortcuts
darkwatch shortcut puts three shortcuts on your Desktop, so you never need the command line:
- Darkwatch starts the dashboard and opens it in your browser. This is the one to use.
- Darkwatch - Scan now runs a scan in a console window and opens the report when it finishes.
- Darkwatch - Report opens the latest report with no console window.
Remove them with darkwatch shortcut --remove. They point at the project's own interpreter, so
nothing has to be on PATH.
Reports go to reports/ as Markdown, HTML and JSON. latest.* always holds the newest set,
and older sets beyond keep_reports (60) are deleted. The HTML report follows the system
light or dark theme and can be filtered by severity.
A hit is one identifier on one piece of evidence: a target, a term, a URL and a source. It is reported as new once. Later runs only update its last-seen time. If a later run finds stronger evidence at the same URL, the stored evidence is replaced and the hit keeps its triage status. If the severity also rises, the hit is escalated: it alerts again, and a resolved hit reopens. Reports show only targets that are still in the watchlist.
Daily runs
darkwatch schedule install --at 09:00 # Task Scheduler: "Darkwatch daily scan"
darkwatch schedule status
darkwatch schedule run-now
darkwatch schedule remove
The task runs the project's pythonw.exe, so no console window appears. It runs as you, only
while you are logged on, which is what lets the notification show. It keeps running on battery
and starts late if the laptop was off at the scheduled time. Output goes to
data/logs/darkwatch.log. A lock file stops a manual run and the scheduled run from
overlapping. It is an OS lock, so a killed run never leaves it stuck.
Alerts
| Channel | Configure with | Receives |
|---|---|---|
| Windows notification | notify.desktop: true (default) |
Counts and target names. Clicking it opens latest.html. |
| ntfy phone push | notify.ntfy_topic or DARKWATCH_NTFY_TOPIC |
Severity counts only, with no names. ntfy topics are readable by anyone who knows the name. Use a long random topic, then subscribe to it in the ntfy app. |
| Webhook (Discord/Slack) | DARKWATCH_WEBHOOK_URL |
The full summary, with identifiers and URLs. |
DARKWATCH_SMTP_HOST, _PORT, _USER, _PASSWORD, _FROM, _TO, _STARTTLS |
The full summary. Port 465 uses implicit TLS. Certificates are verified, and Darkwatch never logs in without TLS. |
Only new or escalated hits at or above notify.min_severity (default MEDIUM) are sent.
Severity
A hit's score adds up four parts:
- The identifier's weight: phone 3, email 2, domain 2, name 1, username 1, keyword 1.
- One point per signal group. There are six: credentials, financial, government ID, sale, doxxing, and access (which covers RDP, VPN, initial access and ransomware wording).
- The evidence's weight: leak site 4, infostealer infection 4, reported attack 3, onion page 2, seed page 2, breach 2, paste 2, Telegram channel 2, Ahmia listing 1, public profile 0.
- Minus one point if the evidence is at least 3 years old. The hit is then marked
dated.
Two things are scored down rather than up, because they were the false positives that mattered:
a bare run of digits with nothing around it to say it is a phone number, and a single dictionary
word repeated as a username with no @handle context. Both are marked uncorroborated and
credited at most one point, so they surface without shouting.
| Score | Severity |
|---|---|
| Under 3 | LOW |
| 3 to 4 | MEDIUM |
| 5 to 6 | HIGH |
| 7 or more | CRITICAL |
Where signals come from depends on the evidence:
- Onion and seed pages: the page title plus 160 characters on either side of the match.
- Leak-site posts: the tracker's whole record for that victim — the description, website, sector and country — not the sentence Darkwatch writes around it.
- Breaches: the data classes the breach exposed, such as "Passwords" or "Dates of birth", rather than the prose describing it.
- Infostealer infections: the fact of the infection. A stealer takes saved passwords and session cookies by definition, so that is not something to look for in prose; corporate services on the same machine add the access signal.
- Public profiles: none. A profile a person put up is expected, so it scores LOW; the page's own wording never manufactures a breach signal. It rises only if another source finds the same identifier somewhere worse.
A plaintext combolist is also recognised structurally: two or more user:pass lines count as
credentials even when the page never uses the word.
Every hit lists its signals, so each score can be explained.
VS Code extension
Install Darkwatch (daemon-vi.darkwatch) from the VS Code Marketplace or Open VSX — the
installer above does it for you. It is a thin, safe front end over this CLI, and it walks you
through the rest: if the CLI is missing, the Darkwatch view offers Install Darkwatch; if there
is no watchlist, Set Up Darkwatch asks who to watch; then Run First Scan.
- a Findings view in the Activity Bar, grouped by severity, with a status-bar count;
- Run Scan, Deep Scan, Open Dashboard, Investigate in a terminal that shows progress live, refreshing the view when the scan ends;
- Search stored findings; triage (acknowledge / resolve / false positive) from a finding's menu or the Command Palette;
- Open Watchlist, Open Latest Report, Check Tor, Check the Install.
It finds the CLI by itself (on PATH, in ~/.local/bin, or uv run in an open checkout); set
darkwatch.command only to override that. Like the dashboard, it never opens an evidence URL —
those point at leak sites and onion services — it shows the URL as text and copies it on request.
Every CLI call is a direct process spawn with no shell. It reads data through
darkwatch doctor --json, darkwatch hits --json and darkwatch search --json.
Build, test and install it from source:
cd vscode-extension
npm install
npm test # launches VS Code and drives every view state against the real CLI
npm run package # produces darkwatch-<version>.vsix
code --install-extension darkwatch-0.2.0.vsix
Releases: .github/workflows/release.yml builds the wheel and the .vsix and makes a GitHub
Release on a v* tag; .github/workflows/publish-vscode.yml publishes the extension to the
Marketplace and Open VSX on a vscode-v* tag (see vscode-extension/PUBLISHING.md).
Responsible use
- Monitor only identifiers you own or have written permission to monitor.
- The report tells you what to do, but it does not do it for you. Actions include changing passwords, turning on MFA, a SIM-swap lock, contacting the bank, reporting at cybercrime.gov.in or on helpline 1930, and CERT-In's 6-hour incident reporting for organisations in India.
- Preserve evidence with a timestamped Tor Browser screenshot before a page changes. Do not follow links out of a hit or download anything from an onion site. If you come across illegal content, stop and report it.
Project docs
docs/PROJECT_STATE.md records what has been verified, with numbers.
docs/ARCHITECTURE.md explains how the pieces fit and why. docs/ROADMAP.md lists what is next.
Release files for darkwatch 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| darkwatch-0.5.1.tar.gz | 374.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| darkwatch-0.5.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 585.8 kB
Release files / darkwatch-0.5.1.tar.gz
| Download URL | darkwatch-0.5.1.tar.gz |
|---|---|
| Size | 374.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
17770dd2f7d5da8a7cc65e87577f9b59289e40dc01a93c5ac444d6d2aedce491
|
|
BLAKE2b-256 checksum How to use checksums |
941ec21d16f865769f0d833fcbdd0c917b88a6e61a9395a30e72b7ceddc50119
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / darkwatch-0.5.1-py3-none-any.whl
| Download URL | darkwatch-0.5.1-py3-none-any.whl |
|---|---|
| Size | 211.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3842eb91e36b76a7fc2d401df868af1c01ec6e102d0b1e0007705c134fca3e67
|
|
BLAKE2b-256 checksum How to use checksums |
13ceff020b86f1576b35b240161eab980445808581b70fcf1e94d9247a56c13d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency log