Threat, scam and ad domain lists built from public sources, as plain domain lists or sing-box rule-sets. One place for the sources, so a list added here reaches every product that uses it:
- Tidy My Mac (Web Protection): tidymymac.com builds the lists every four hours and publishes them in a signed manifest for the Mac app's DNS filter.
- Unblock Master VPN / Lifetime Premium VPN: the VPN API publishes them as sing-box rule-sets
(
https://vpn.nameocean.net/blocklists/<category>.json), which clients download through the tunnel.
No runtime dependencies. Python 3.8+.
| Category | What | Source |
|---|---|---|
threats |
Malware, phishing, command-and-control, cryptojacking | HaGeZi TIF medium |
scams |
Fake shops, scams, fake support | HaGeZi Fake |
ads |
Ads, trackers, telemetry | HaGeZi Multi NORMAL |
The category names are what the apps call them, so they never change. Sources are in
src/yuix_blocklists/sources.py. The lists themselves are
HaGeZi's (GPL-3.0); this repository only holds the code that fetches them.
A domain also blocks all its subdomains.
pip install "yuix-blocklists @ git+https://github.com/Yuix-Networks/blocklists@v1.0.0"From the command line (what the VPN API's cron runs):
python -m yuix_blocklists singbox --cache /var/lib/vpn/blocklist-sources --out /root/vpn/blocklists --sing-box /usr/local/bin/sing-box
python -m yuix_blocklists txt --cache ./cache --out ./listssingbox writes <category>.json (rule-set source format, version 1) and, with --sing-box,
compiles <category>.srs (binary, far smaller; what the apps download); txt writes
<category>.txt, one domain per line. Exit status 1 means at least one source failed.
From Python:
import yuix_blocklists
from yuix_blocklists import singbox
lists, failed = yuix_blocklists.build("/var/lib/app/blocklist-sources") # {category: set of domains}
singbox.write("/srv/blocklists", lists)
# add blocking to a sing-box (1.11+) client config, in place
singbox.client_blocking(config, ["threats", "scams"], "https://vpn.nameocean.net/blocklists/", detour="proxy")Each source is checked on its own. One that fails, returns nothing usable, or suddenly shrinks to less than half of its last good copy is replaced by that last good copy (kept in the cache directory), so a broken or truncated download never quietly switches protection off. A category is empty only when none of its sources has ever been fetched; writers then keep the old file.
The parser follows the same rules as Tidy My Mac's DomainBlocklist (Swift): plain, hosts
(0.0.0.0 domain) and ||domain^ lines; lowercase; no trailing dot or leading *.; at least
two valid labels.
Edit sources.py, run pytest, bump version in pyproject.toml and __init__.py, tag
vX.Y.Z, then bump the pin in tidymymac.com's and vpn-api-backend's requirements.txt and
fab deploy each.
MIT (the code). The lists it downloads keep their own licenses.