Download the latest version here.
- Refreshed UI of Timeline, Insights, and app details
- Choose which apps use the remote VPN
- Import WireGuard profiles by QR code, file, or ZIP
- More reliable VPN, DNS, tethering, and tracker detection
- Reduced screen-off battery use
- Updated tracker lists, translations, and dependencies
- Many bug fixes
What's Changed
- Stop asking about Private DNS during onboarding, notify when it breaks by @kasnder in #711
- Notice a pinned Private DNS when it happens, and warn in the app too by @kasnder in #713
- deps: bump gradle-wrapper from 9.6.1 to 9.7.0 in the gradle-minor-patch group by @dependabot[bot] in #717
- deps(rust): bump base64 from 0.23.0 to 0.23.1 in /wgbridge-rs in the cargo-minor-patch group by @dependabot[bot] in #716
- deps: bump androidx.appcompat:appcompat from 1.7.1 to 1.8.0 in the gradle-minor-patch group by @dependabot[bot] in #726
- Split agent guidance into a shared agents/ directory and add a Gradle quiet hook by @kasnder in #727
- Restore DNS policy parity under WireGuard by @kasnder in #738
- Replace the three per-app toggles with one protection state by @kasnder in #729
- Route apps around the remote VPN without leaving TrackerControl by @kasnder in #730
- Collapse the per-app controls into rows that open on demand by @kasnder in #733
- Decide per-app routing once, in Rust, with tests by @kasnder in #737
- Guard the agent-config topology with a unit test by @kasnder in #728
- Dedup DNS attribution rows in one pass, not per candidate row by @kasnder in #741
- Fix two battery risks in the per-app remote routing feature by @kasnder in #742
- Align lockdown troubleshooting text with actual behaviour (#743) by @kasnder in #745
- Deduplicate DNS response rewriting by @kasnder in #739
- Skip SNI research mode for tunnelled flows by @kasnder in #744
- Fix offline release build failing on desugar_jdk_libs (CI failure blocking #718) by @kasnder in #746
- ci: bump the actions group across 1 directory with 2 updates by @dependabot[bot] in #718
- Gate DoH retries and keep-alives on screen state by @kasnder in #747
- Bound the update check's network calls with timeouts by @kasnder in #748
- Synchronize TrackerBlocklist access across packet and UI threads by @kasnder in #750
- Fix misc correctness bugs: channel bypass-DND, atomic hosts rename, locale, fresh-install version by @kasnder in #755
- Serialize WG profile store and fix rotation recovery by @kasnder in #754
- Gate TCP DNS answer recording on known framing by @kasnder in #767
- Slow the WG connectivity watchdog when the screen is off by @kasnder in #749
- Fix ServiceSinkhole thread leak and teardown races by @kasnder in #752
- Harden DNS proxy pool, TCP framing, and fallback validation by @kasnder in #753
- Cap the DoH response read at 65535 bytes (#761) by @kasnder in #771
- Trip the DoH circuit breaker per attempt and drop the dead delayed-screen-off alarm by @kasnder in #768
- Debounce database change notifications without sleeping the handler thread by @kasnder in #770
- Fix memory-safety and hang bugs in native packet engine by @kasnder in #751
- Normalise DNS qnames to lowercase on the database write path by @kasnder in #772
- Fix blocklist load integrity: short reads and premature hosts mtime stamp by @kasnder in #773
- Fix WireGuard rotation edge cases (#766) by @kasnder in #774
- Count an oversized DoH body as a failed attempt (#760) by @kasnder in #775
- Align the UI's shared-IP view with the runtime blocking decision (#759) by @kasnder in #776
- Fix DNS cache invalidation race in tracker attribution by @kasnder in #777
- Fix IPv6 extension header walk in handle_ip() by @kasnder in #778
- Document the fragmented-traffic drop on the direct path (#779) by @kasnder in #780
- deps: bump the gradle-minor-patch group with 4 updates by @dependabot[bot] in #783
- Re-apply horizontal padding on configuration change by @kasnder in #793
- Say what clearing an app's activity does, and confirm it first by @kasnder in #794
- Fix route prefixes, stale tracker caches and blocklist loss by @kasnder in #790
- Let a user-initiated start clear the background-user flag by @kasnder in #792
- Retry a failed VPN replacement instead of stranding a blocking tunnel by @kasnder in #791
- deps(rust): bump gotatun from 0.8.1 to 0.9.0 in /wgbridge-rs by @dependabot[bot] in #782
- Port ProtectedUdpFactory to the gotatun 0.9 UDP API by @kasnder in #796
- Fix alarm timebase, guard numeric prefs, and small correctness nits by @kasnder in #800
- Warn when a routed LAN resolver loses non-DNS traffic by @kasnder in #799
- Skip tracker-cache invalidation for refreshed DNS rows by @kasnder in #802
- Run the network validation probe off the ConnectivityManager thread by @kasnder in #801
- Retry failed WireGuard startup by @kasnder in #803
- Publish tracker cache entries atomically by @kasnder in #804
- Publish tracker-list reloads atomically by @kasnder in #805
- Recover WireGuard monitoring after stats failures by @kasnder in #806
- Address review nits from #803, #804, #805 and #806 by @kasnder in #810
- Accessibility follow-ups from #636: contrast, stat grouping, announcements by @kasnder in #815
- Preserve DDG's hostname-level breakage exceptions in the bundled TDS by @kasnder in #814
- Use safer --force-with-lease refspec when pushing update-lists branch by @kasnder in #818
- Update bundled tracker/host lists by @github-actions[bot] in #821
- Advertise iOS version in About dialog by @kasnder in #812
- Walk every DNS-over-TCP frame per recv() with a bufferless stream cursor by @kasnder in #816
- Guide Proton VPN WireGuard config import by @kasnder in #813
- Warn when encrypted DNS silently defeats tracker detection by @kasnder in #817
- Migrate Crowdin synchronization to GitHub Actions by @kasnder in #823
- Allow reviewed repository translations to sync to Crowdin by @kasnder in #825
- New Crowdin translations by @github-actions[bot] in #824
- Define the tc-dns C ABI in the wgbridge cdylib crate by @kasnder in #827
- Block DNS-over-TCP responses split across socket reads by @kasnder in #828
- Merged Protection screen with a temporary per-app pause by @kasnder in #826
- Stop UID-keyed state from outliving the app that set it by @kasnder in #829
- Dedupe the UID-keyed stores and stop sweeping them on every install by @kasnder in #830
- Blocking-mode UX: timeline pointer, mode-independent compatibility exclusions, per-app essential blocking by @kasnder in #834
- New Crowdin translations by @github-actions[bot] in #833
- Untangle the app details screens by @kasnder in #835
- Minimal mode: monitor with every tracker list, block only the DuckDuckGo set by @kasnder in #839
- Rename per-app "essential-only" to "minimal-only" (Minimal blocking only) by @kasnder in #841
- New Crowdin translations by @github-actions[bot] in #837
- New Crowdin translations by @github-actions[bot] in #842
- Finish the Material 3 migration in the theme and cards by @kasnder in #840
- Reintroduce the tracker explanation at the top of the Trackers tab by @kasnder in #845
- Close the details screen when the intent names no app by @kasnder in #846
- Surface Proton VPN setup on the VPN tab by @kasnder in #843
- Redesign TRACKERS tab as calm category feed by @kasnder in #847
- Flatten Manage App data-rights sections by @kasnder in #849
- Modernise the per-app protection screen by @kasnder in #850
- Make tracker company rows responsive by @kasnder in #851
- Add stable Compose foundation for app details by @kasnder in #853
- Migrate Data Rights details to Compose by @kasnder in #854
- Migrate Trackers details to Compose by @kasnder in #855
- Migrate Countries details to Compose by @kasnder in #856
- Migrate Tracker Libraries to Compose by @kasnder in #857
- Migrate Protection details to Compose by @kasnder in #858
- Migrate Insights to Compose by @kasnder in #862
- Migrate Blocklists to Compose by @kasnder in #863
- Migrate Timeline to Compose by @kasnder in #864
- Migrate WireGuard profiles to Compose by @kasnder in #865
- Unify Material colours and headings by @kasnder in #866
- Polish tracker insights and domain grouping by @kasnder in #867
- Polish Data Rights and Countries details by @kasnder in #868
- Request local network access from Troubleshooting by @kasnder in #869
- Unify screens on Material surface roles by @kasnder in #870
- Align main tab typography with Material 3 by @kasnder in #871
- Polish the Compose migration stack for release by @kasnder in #876
- Codex/issue 832 calm category feed by @kasnder in #877
- Re-register a deleted provider device instead of reusing its dead key by @kasnder in #861
- Refresh copyright notices by @kasnder in #844
- New Crowdin translations by @github-actions[bot] in #848
- Cut avoidable main-thread work in the Compose details screens by @kasnder in #879
- Add WireGuard profiles via QR code by @kasnder in #878
- Take per-app protection writes off the main thread by @kasnder in #883
- ci: bump actions/setup-java from 5 to 6 in the actions group by @dependabot[bot] in #881
- deps(rust): bump the cargo-minor-patch group in /wgbridge-rs with 2 updates by @dependabot[bot] in #880
- Persist unresolved package names when saving details-screen blocklist by @kasnder in #886
- Stop clearing every network session when the device leaves Doze by @kasnder in #887
- Fix five high-severity bugs from the codebase review by @kasnder in #889
- Fix resource leaks on TCP connect failure and service teardown by @kasnder in #890
- Guard browser intents and preserve pending internet-block entries by @kasnder in #885
- Cut two avoidable battery costs from the codebase review by @kasnder in #896
- Fix four protocol bugs in the native packet engine by @kasnder in #897
- Keep settings import and port forwarding state consistent by @kasnder in #899
- Fix hosts-list matching misses and harden the DNS path by @kasnder in #900
- Stop stale async results landing on the wrong view by @kasnder in #901
- Make WireGuard key rotation crash-consistent and identity-checked by @kasnder in #898
- Propagate database migration failures instead of swallowing them by @kasnder in #902
- deps: bump org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin from 2.3.21 to 2.4.10 in the gradle-minor-patch group by @dependabot[bot] in #882
- Toggle WireGuard keepalive per peer instead of reapplying the config by @kasnder in #903
- Download blocklists conditionally and skip the reload when unchanged by @kasnder in #905
- Import WireGuard profiles from a file or a zip archive by @kasnder in #907
- New Crowdin translations by @github-actions[bot] in #908
- Fix mixed tracker evidence expiry by @kasnder in #910
- Fix UDP packet-path state handling by @kasnder in #909
- Fix native credential logging and failed session admission by @kasnder in #921
- Fix Linux native regression test portability by @kasnder in #922
- Preserve DNS question provenance and refresh blocking evidence by @kasnder in #924
- Recover DNS detection across split TCP responses by @kasnder in #925
- Consolidate bounded DNS and native socket fixes by @kasnder in #926
- Fix WireGuard verdict caching and TCP overlap handling by @kasnder in #913
- Implement TCP half-close and back off zero-window probes by @kasnder in #915
- Recover WireGuard DNS reassembly and TUN write failures by @kasnder in #916
- Repair remaining TCP/SOCKS5 and WireGuard DNS handling by @kasnder in #919
- deps(rust): bump gotatun from 0.9.1 to 0.9.2 in /wgbridge-rs in the cargo-minor-patch group by @dependabot[bot] in #929
- Guard SCREEN_OFF/package-change receivers against RejectedExecutionException by @kasnder in #930
- New Crowdin translations by @github-actions[bot] in #920
- Guard main-screen autofill lookup against missing descriptors by @kasnder in #934
- Improve DoH setup and recover immediately after endpoint changes by @kasnder in #936
- Keep concurrent ICMP echo identifiers in separate sessions by @kasnder in #939
- New Crowdin translations by @github-actions[bot] in #938
- Fix TCP close handling and TUN failure recovery qualification by @kasnder in #942
- Make displayed values locale-safe by @kasnder in #945
- Resolve async and adapter lint findings by @kasnder in #943
- Remove obsolete Android API compatibility paths by @kasnder in #948
- Preserve app ownership across WireGuard policy reloads by @kasnder in #933
- Resolve layout and accessibility lint findings by @kasnder in #947
- Complete missing translations for all locales by @kasnder in #940
- Resolve remaining locale lint warnings by @kasnder in #944
- Remove unused Android resources by @kasnder in #949
- New Crowdin translations by @github-actions[bot] in #950
- New Crowdin translations by @github-actions[bot] in #951
- New Crowdin translations by @github-actions[bot] in #955
- New Crowdin translations by @github-actions[bot] in #958
- Stop marking unmetered networks as metered while the VPN runs by @kasnder in #960
- test: move unit tests to Robolectric 4.17 by @kasnder in #963
- Remove the DHCP responder that hijacks tethered leases by @kasnder in #964
- deps: bump the gradle-minor-patch group across 1 directory with 3 updates by @dependabot[bot] in #957
- Compare search domains and excluded routes in Builder.equals by @kasnder in #965
- Check for a killed VPN from outside the service by @kasnder in #966
- Remove obsolete legacy watchdog by @kasnder in #967
- New Crowdin translations by @github-actions[bot] in #961
- Update bundled tracker/host lists by @github-actions[bot] in #972
- New Crowdin translations by @github-actions[bot] in #973
- ci: bump the actions group with 2 updates by @dependabot[bot] in #978
- deps: bump the gradle-minor-patch group across 1 directory with 6 updates by @dependabot[bot] in #980
- deps(rust): bump the cargo-minor-patch group across 1 directory with 2 updates by @dependabot[bot] in #984
- New Crowdin translations by @github-actions[bot] in #985
- ci: bump actions/upload-artifact from 7.0.1 to 7.0.2 in the actions group by @dependabot[bot] in #986
- deps: bump the gradle-minor-patch group with 2 updates by @dependabot[bot] in #987
- deps(rust): bump tokio from 1.53.2 to 1.53.3 in /wgbridge-rs in the cargo-minor-patch group by @dependabot[bot] in #988
- deps(rust): bump gotatun from 0.9.2 to 0.10.1 in /wgbridge-rs by @dependabot[bot] in #989
- Route traffic outside a split tunnel's AllowedIPs directly by @kasnder in #981
- Show a persistent notice when Research disables tracker blocking by @kasnder in #992
- Clarify custom blocklists and download added lists immediately by @kasnder in #994
- fix(wgbridge): replace deprecated AsyncFd::with_interest with AsyncFd::register_with_interest by @kasnder in #997
- fix(wgbridge): rebind UDP sockets to a fresh ephemeral port by @kasnder in #999
- Reload rules when apps on external storage become available by @kasnder in #998
- New Crowdin translations by @github-actions[bot] in #993
- deps(rust): bump the cargo-minor-patch group in /wgbridge-rs with 3 updates by @dependabot[bot] in #1001
- wg: tell the user when a Mullvad subscription has lapsed by @kasnder in #1005
Full Changelog: 2026080...2026102