All projects

Vigil

See which app on your phone is talking to whom.

On-device DNS monitor that attributes every lookup to the app that made it.

Platform
Android 10+ (min SDK 29)
Approach
Local VPN, DNS traffic only
Behaviour
Audits, never blocks
Architecture
Multi-module Gradle, Hilt
90,000+
tracker domains bundled
76
unit tests
512
app lookups cached
Android 10+
minimum version

In the app

Drag, swipe or use the arrows

What it is

Vigil sets up a local-only VPN tunnel, reads the DNS queries your apps send, and shows which app looked up which domain, flagging the ones that match a bundled list of tracker domains. Everything happens on the phone. It never blocks anything: it logs each lookup, forwards it to a normal DNS resolver, and gets out of the way.

  1. 01

    An app asks for a domain

    Before any app can reach a server, it has to look the name up over DNS.

  2. 02

    Vigil's tunnel catches the lookup

    The VPN only routes a sentinel DNS address (10.0.0.2) into the tunnel, so just port 53 queries come through. The rest of your traffic never touches it.

  3. 03

    It works out who asked

    The query is mapped back to the app that sent it, with a 512-entry LRU cache so repeat lookups cost almost nothing.

  4. 04

    The domain gets a verdict

    A reverse-label suffix trie holding 90,000+ domains checks it in time proportional to the name's length, and the result is tracker or clean.

  5. 05

    Logged, then forwarded

    The log is batched into Room, 50 records at a time or every 2 seconds. Meanwhile the query goes to your upstream resolver through a protected socket that bypasses the tunnel, and the answer is handed back to the app.

What it does

Live feed

A searchable stream of every lookup, filterable to trackers only or safe only, with the app that made each one.

Per-app stats

See which apps are the chatty ones, and how many of their lookups were trackers.

A private DNS warning

Encrypted DNS (DoT or DoH) skips the tunnel entirely, so Vigil detects it and shows a banner instead of silently missing traffic.

Themes

System, Catppuccin, Gruvbox, Nord, Dracula and more, set in Geist type.

How it works

  1. 1

    App

    DNS query, UDP 53

  2. 2

    Local VPN tunnel

    10.0.0.2/32 sentinel

  3. 3

    Packet engine

    hostname, txid, UID

  4. 4

    Suffix trie

    tracker or clean

  5. 5

    Batched log

    Room, 50 or 2 s

  6. 6

    Upstream DNS

    protect()ed socket

  7. 7

    Answer back

    to the app

Questions

What does Vigil do?
Vigil sets up a local-only VPN tunnel, reads the DNS queries your apps send, and shows which app looked up which domain. Domains that match a bundled list of 90,000+ known trackers are flagged.
Does Vigil block trackers?
No. It audits only. It logs each lookup, forwards it to a normal DNS resolver and gets out of the way.
Does Vigil route all my traffic?
No. Only DNS queries enter the tunnel, so everything else bypasses it. Vigil never sees the content of your other traffic, and nothing leaves the phone.
Where does Vigil's tracker list come from?
Vigil bundles StevenBlack/hosts, an open-source hosts list of about 90,000 domains maintained by its community. I didn't compile the list; Vigil's job is matching every DNS query against it quickly.
What does Vigil need?
Android 10 or newer (min SDK 29). It does not need root.

Under the hood

Only DNS goes through the tunnel

Rather than capturing everything, Vigil routes only the sentinel DNS address into the VPN. That keeps it light on battery and speed, and it means the app never sees the contents of your other traffic.

Packets decoded by hand

Vigil reads bytes straight from the tunnel's file descriptor and decodes the UDP header and DNS question itself, pulling out the hostname and the transaction ID, so it can rebuild the response for the app afterwards.

A trie that reads domains backwards

Domains are stored label by label from the right, so ads.tracker.example.com is walked as com, example, tracker, ads. One pass costs the length of the name, not the size of the list, and a match on example.com automatically covers every subdomain under it, with no database query.

Audit, never block

Vigil logs a verdict for each lookup but always forwards the query, so your connectivity stays exactly as it was. It's a way to see what's happening, not a firewall.

Writes that never stall the UI

Logs sit in a memory buffer and are flushed to Room in one go when it reaches 50 items or after 2 seconds, guarded by a mutex. The main thread never waits on the disk.

Honest about its limits

If Private DNS is on, lookups bypass the tunnel. Vigil checks for that and says so, instead of looking quiet and implying nothing is happening.

Strict module boundaries

The code is split across Gradle modules (app, feature, vpn, data, a Room database module, and a pure-JVM domain module with zero Android dependencies), so the networking core can be tested without a device.

Stack

Language
Kotlin
UI
Jetpack Compose, Geist fonts
Dependency injection
Hilt
Networking
VpnService, protect()ed upstream socket
Classification
Reverse-label suffix trie, ~90K hosts
Tracker list
StevenBlack/hosts, an open-source list (not mine)
Caching
LRU cache (512 entries)
Storage
Room
Concurrency
Coroutines, mutex-guarded buffer
SDK range
Min 29, target 36
KotlinVpnServiceComposeHiltRoomCoroutinesLRU cache

Written about

More from the same desk

Command menu

Jump to a page, open a profile, or run an action