Skip to content

prasta1/BottomlessHole

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BottomlessHole 🕳️

A local, undetectable ad sinkhole for macOS — think Pi-hole, but running entirely on your own MacBook and protecting every app (Safari, Chrome, Firefox, Electron apps, native app telemetry — the whole OS), not just one browser.

Ads and trackers are blocked at the DNS layer, so the ad host never even resolves to an IP. Nothing is injected into web pages, so there's no extension fingerprint for anti-adblock scripts to detect — blocked hosts simply look like ordinary network failures.

How it works

 macOS system DNS ──► 127.0.0.1  (set via networksetup)
                         │
                         ▼
   ┌─────────────────────────────────────────────┐
   │  BottomlessHole daemon  (Go, runs via launchd)│
   │   • DNS server on 127.0.0.1                    │
   │   • blocked domain  → NXDOMAIN / 0.0.0.0       │
   │   • allowed domain  → forwarded upstream (DoH) │
   │   • SQLite query log + metrics                 │
   │   • local HTTP API on 127.0.0.1:8353           │
   │   • blocklist auto-updater (timer)             │
   └─────────────────────────┬─────────────────────┘
                             ▼
   ┌─────────────────────────────────────────────┐
   │  BottomlessHole.app  (Tauri desktop UI)        │
   │   dashboard · metrics · whitelist · pause      │
   └─────────────────────────────────────────────┘
  • Blocklists are pulled from live community repos (StevenBlack/hosts, OISD, Hagezi, AdGuard, EasyList) and refreshed on a timer. Both hosts and Adblock Plus filter syntaxes are parsed.
  • Whitelisting / blacklisting with exact, wildcard, and regex rules that survive blocklist updates.
  • Encrypted upstream — clean queries are forwarded over DNS-over-HTTPS, so your browsing lookups aren't visible to the local network / ISP.
  • Metrics — total queries, % blocked, top blocked domains, top clients, and a live query log, all in labeled tables.
  • Pause / kill switch from the desktop app.
  • Native macOS dashboard — a standard menu bar with keyboard shortcuts, right-click actions on any domain (whitelist / blacklist in one click), a Light/Dark theme that follows the system appearance, and window size/position that persists across launches.

Repo layout

Path What
core/ Go daemon — DNS sinkhole, blocklist engine, metrics, HTTP API
app/ Tauri desktop app (Rust shell + React/TS frontend)
scripts/ Helper scripts to point macOS DNS at the daemon and back
deploy/ launchd plist to run the daemon at boot
ARCHITECTURE.md Design rationale and component detail

Quick start (development)

# 1. Build & run the core daemon
cd core
go build -o ../bin/bottomlesshole .          # build only the main package
sudo ../bin/bottomlesshole --config config.example.yaml     # binds DNS on :53

# 2. Point macOS at it (run once; revert with scripts/unset-dns.sh)
sudo ../scripts/set-dns.sh

# 3. Run the dashboard app (connects to the daemon's API on 127.0.0.1:8353)
cd ../app
npm install
npm run tauri dev

The daemon and the dashboard are independent processes: the daemon serves DNS and a local HTTP API, and the app is only a dashboard that reads that API. tauri dev does not start the daemon for you — run it yourself (step 1) or install it as a background service (below).

Install as a background service (recommended)

Run the daemon as a root launchd service so it starts at boot and can bind port 53, then point macOS at it:

cd core && go build -o ../bin/bottomlesshole . && cd ..
sudo ./scripts/install-daemon.sh    # installs + loads the LaunchDaemon
sudo ./scripts/set-dns.sh           # route macOS DNS through it

Build the distributable dashboard app with:

cd app && npm install && npm run tauri build   # outputs BottomlessHole.app + a .dmg

Note on "undetectable": DNS sinkholing is far stealthier than a browser extension, but nothing is 100%. A site can still infer blocking from a failed request and show an anti-adblock nag. Defeating those specifically needs a cosmetic-filtering browser extension layer, which is intentionally out of scope for the core daemon (see ARCHITECTURE.md → "Cosmetic filtering").

Geolocation

The dashboard shows which country each top blocked domain's server sits in. Blocked domains are sinkholed, so their real IP is resolved on demand and looked up in a local country database — no external geo API calls. Fetch/refresh the database with scripts/fetch-geoip.sh (it's optional; geolocation is silently disabled without it).

IP-to-country data is © DB-IP, licensed under CC BY 4.0.

Privacy intelligence

Beyond blocking, BottomlessHole attributes domains to the companies behind them (via an embedded DuckDuckGo Tracker Radar dataset, Apache-2.0). The dashboard shows:

  • a "Who's Watching You" panel — the companies profiling this machine, ranked;
  • company/category badges on the top-blocked and query-log lists;
  • a transparent privacy score (0–100) with an explainable breakdown — it penalizes known trackers still reaching the machine via allowed queries.

Attribution is read-time only; it never touches DNS resolution. Unknown/long-tail domains simply show no badge.

Refresh the dataset: ./scripts/build-tracker-db.sh (re-compiles core/internal/tracker/tracker_db.json), then rebuild the daemon binary.

Status

Early scaffold. The Go core compiles and runs a working DNS sinkhole with metrics and an HTTP API; the Tauri app provides the dashboard shell. See ARCHITECTURE.md for the roadmap and the TODOs throughout the code.

About

Local, undetectable DNS ad sinkhole for macOS — Pi-hole-style ad/tracker blocking for every app (not just the browser), with a Go daemon and a native Tauri dashboard.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors