KOKosaaaa/NetGuard
KOKosaaaa/NetGuard has 31 GitHub stars and 4 forks. GitHot measured 0 stars in 24 hours, 0 in 7 days, and 0 in 15 days. Last checked 2026-11-12 12:28 UTC. Last push 2026-10-05.
Android VPN client built for privacy, security, and stealth. Powered by xray-core.
What you can do
- Connection Map - Animated world map showing user-to-server connection arc
- Speed Test - Download/upload/ping through VPN tunnel (OkHttp + raw SOCKS5)
- WiFi Auto-Connect - Auto-enable VPN on untrusted WiFi + Evil Twin detection
- Material You - Dynamic color theme on Android 12+
Measured star growth
- in 24h
- 0
- in 7 days
- 0
- in 15 days
- 0
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalNetGuard
Android VPN client built for privacy, security, and stealth. Powered by xray-core.
Releases
The current release is 3.0.0, with WB Stream, adaptive routing, a refreshed glass interface and sixteen languages. Download the signed ARM64 APK from GitHub Releases. The application reports 3.0.0 (versionCode 114); the bundled server agent is 0.5.13.
See the 2.0.0 release notes for automatic per-site routing, Happ subscription compatibility, conservative service failover and active-server display. AUTO compares reachability and response latency in the background without holding the first request or changing established connections. A direct route uses your normal public IP; choose Global proxy when all traffic should use the VPN. HTTP/3 and ECH-hidden names keep the core's routing rules without per-site comparison.
Licensed under the Apache License, Version 2.0 - see LICENSE. You are free to use, modify, distribute, and sublicense the code (including for commercial use), provided you preserve the copyright notice. The Apache-2.0 patent grant also protects downstream users from patent claims by contributors.
What makes NetGuard different
NetGuard combines an authenticated local proxy, encrypted profile storage, per-site routing and conference transports. These protections describe this codebase; they are not a security assessment of other VPN clients.
Zero-leak network switching
During a managed Wi-Fi/mobile reconnect, NetGuard keeps the TUN interface alive while restarting its internal transport. Packets wait or are dropped until the transport is available. Protection after the app is stopped depends on Android's Always-on VPN and Block connections without VPN settings. AUTO/direct routing and explicit app exclusions intentionally permit direct traffic.
Ephemeral authenticated SOCKS5
Every connection generates a fresh random port, a UUID username, and a 32-character random password for the local SOCKS5 bridge between tun2socks and xray. Credentials are cleared from memory on disconnect or network reconnect (they are kept alive during a session so that internal speed-test / service-test requests can reauthenticate through the HTTP bridge). Even if another app scans localhost ports, it cannot authenticate without the ephemeral password.
Config minimally exposed on disk
Xray receives its JSON configuration in memory through the libXray JNI API in a dedicated Android service process. The configuration is not passed through a native child process's command line or a persistent JSON configuration file. The hev JNI bridge reads a private, app-internal YAML file asynchronously. That file contains the temporary bridge credentials while the tunnel runs and is removed when the bridge stops; it is not copied to external storage.
Built-in security self-test
11 automated checks that run against your own device:
- Open SOCKS5 / HTTP proxy ports (10808, 1080, 8080, etc.)
- Own SOCKS5 inbound rejects no-auth handshakes (regression guard for
XrayConfigGenerator.buildInbounds) - Xray gRPC API exposure
- Clash REST API exposure
- Wide port scan for known VPN-tooling ports
/proc/net/tcpanalysis for unexpected listeners- VPN transport flag detection
- MTU informational report (non-decisive - see self-test details)
- Package name stealth analysis
- Neighboring VPN clients (informational inventory, not proof of a vulnerability)
Evil Twin WiFi protection
Stores SSID+BSSID pairs for trusted WiFi networks. When connecting to a WiFi with a known SSID but unknown BSSID (possible Evil Twin attack), automatically enables VPN and sends a warning notification.
Service availability testing
Test which services actually work through each server - YouTube, Telegram, Instagram, ChatGPT, Discord, Google, X/Twitter, Spotify. See a score like "3/8" before you connect.
Stealth branding
Package name com.smarttools.netguard. Notification wording is localized and
deliberately compact. Android still identifies this app as a VPN and can display
its VPN indicator; branding does not hide the VPN from the operating system.
Features
Protocols: VLESS (+ REALITY), VMess, Trojan, Shadowsocks, Telemost and WB Stream. Imported profiles require a protocol/transport supported by the bundled core; successful import alone does not establish that a server works.
Transports: TCP, WebSocket, gRPC, HTTP/2, HTTP Upgrade, SplitHTTP, KCP, QUIC
| Feature | Details |
|---|---|
| Connection Map | Animated world map showing user-to-server connection arc |
| Speed Test | Download/upload/ping through VPN tunnel (OkHttp + raw SOCKS5) |
| WiFi Auto-Connect | Auto-enable VPN on untrusted WiFi + Evil Twin detection |
| Material You | Dynamic color theme on Android 12+ |
| Per-app routing | Whitelist / Blacklist / Disabled |
| Auto-select best server | Compare service responses and latency; bounded selection with cancellation |
| Subscription management | Auto-update via WorkManager (6/12/24/48h) |
| QR code | Scan (ML Kit + CameraX) and generate (ZXing) |
| Deep link import | vless://, vmess://, trojan://, ss://, hy2:// |
| Traffic stats | Real-time speed, session/daily/weekly/total counters |
| Home screen widget | One-tap connect/disconnect |
| Quick Settings tile | Notification panel connect/disconnect toggle |
| Boot auto-connect | Reconnect to last server on device restart |
| DNS | Custom primary/secondary, optional DoH through proxy |
| Routing modes | AUTO (per-site comparison, weekly cache) / Global proxy / Rule-based (RU direct) / Direct |
| LAN bypass | Access local network devices while connected |
| Themes | Dark, Light, OLED Black, Ocean, fsociety (Mr. Robot phosphor terminal), Dynamic (Material You) |
| Languages | 16 languages |
| Backup/Restore | Export/import full config as JSON |
| Log viewer | Real-time xray logs with auto-redaction of credentials |
| Subscription headers | Per-group row in the Servers list shows traffic counter (used / total or used / ∞), clickable globe / Telegram-style icons that open the provider's website / support, and the provider's announcement text (parsed from subscription-userinfo, support-url, profile-web-page-url, announce response headers) |
| Sort toggle | Sort by ping; second tap on the same menu item restores the default subscription order |
| Friendly profile names | Built-in subscriptions emit user-readable labels (🚀 Основной, 🇷🇺 Если в РФ не работает, 🛡 Резерв, ☁ Через Cloudflare, ⚡ Быстрый UDP, 🔒 Trojan / Shadowsocks резерв) instead of raw protocol abbreviations |
Security hardening
- Local SOCKS5 and the HTTP bridge for internal tests require session credentials. Regression tests check rejection of unauthenticated SOCKS5 clients.
- The TUN-to-SOCKS bridge runs through hev JNI in the app process; its credentials are no longer passed to a native child process through command-line arguments.
- SQLCipher encrypts the Room database, with migration from the existing plaintext database. EncryptedSharedPreferences holds key material and small secrets, including the site-route cache.
- Log redaction - UUIDs, passwords, Bearer tokens masked automatically
- SSRF protection - private/loopback/link-local IPv4 and IPv6 blocked in profile parser
- Tapjacking protection on critical buttons (filterTouchesWhenObscured)
- Deep link validation with confirmation dialog
- Atomic file writes (temp + rename pattern)
- No cleartext traffic (except speed test domains through VPN tunnel)
- No backup (
android:allowBackup="false") - DNS leak prevention - all port 53 traffic forced through proxy
- DNS address validation - loopback, private ranges and garbage strings rejected before they reach xray
- Input size limits on URIs, subscriptions, imports
- Evil Twin WiFi detection (SSID+BSSID pair validation)
Known limitations
- AUTO uses independent, bounded probes to compare reachability and response latency, not full download speed or every authenticated page. Probe results and actual forwarded traffic are recorded separately. HTTP/3 and ECH-hidden names use the core route without per-site comparison. A direct route exposes the connection's normal public IP to the destination.
- WB Stream and Telemost depend on third-party signaling and media relays. Their bandwidth, room lifetime and availability vary; parallel channels do not guarantee a particular speed.
- Local fixtures and emulator tests do not establish performance on a particular carrier or long-running stability on a physical phone.
- Real network or server failures can still interrupt established connections. On-device migration and long-running Remote sessions need device validation.
Acknowledgements
NetGuard is composed of a small original glue layer wired around a stack of open-source components that do the heavy lifting. The honest credit list - without these projects, this app could not exist.
Tunnel core (native)
- xray-core - Mozilla Public License 2.0. Project X. The proxy engine is packaged through the libXray AAR as
libgojni.soand runs in a dedicated Android service process. - hev-socks5-tunnel - MIT. Converts the Android
VpnServiceTUN file descriptor into TCP/UDP streams throughlibhev-socks5-tunnel.soJNI.
Android libraries
- AndroidX (Core, AppCompat, ConstraintLayout, SwipeRefreshLayout, Navigation, Lifecycle, Room, Preference, WorkManager, CameraX, Security-Crypto) - Apache-2.0. Google.
- Material Components for Android - Apache-2.0. Google. Material You theming, dialogs, bottom-nav, dynamic colors.
- OkHttp - Apache-2.0. Square. HTTP client used for subscription fetching, certificate pinning, and DNS-rebinding-safe resolution.
- Gson - Apache-2.0. Google. JSON serialisation for xray config generation and profile import/export.
- kotlinx.coroutines - Apache-2.0. JetBrains. Async runtime for
TunnelVpnService, watchdogs, network callbacks. - ZXing Core - Apache-2.0. Generates QR codes for sharing profiles.
- ML Kit Barcode Scanning - Apache-2.0 (Google Play Services component). Scans QR-coded subscription / profile URIs.
- JUnit 4 - EPL-1.0. Test runner.
Inspiration / prior art
The architecture of NetGuard's TunnelVpnService (TUN file descriptor handover via Unix-domain socket, watchdog loop, no-leak network switching) draws from the broad Android-VPN-with-xray prior art established by:
- v2rayNG - GPL-3.0. The reference implementation for an Android Xray client; defined many of the patterns we still use.
- Hiddify - GPL-3.0. Subscription / profile parser conventions.
- NekoBoxForAndroid - GPL-3.0. UI patterns for per-app routing and traffic stats.
NetGuard does not reuse code from these projects directly (NetGuard is Apache-2.0; copying GPL-3.0 code would force a relicense), but ideas and protocol-level conventions are owed to them.
Tooling
- Claude Code - Anthropic. Performed the v1.1.4 / v1.1.5 / v1.1.6 security audit, wrote the
ServerPreflight/PackageInstallReceiver/ TLS-rotation / MTU-probing changes, the JVM unit-test suite, and most of this README. v1.1.7 (trigger mode), v1.1.8 (onboarding wizard, flexible trigger routing), v1.1.9 (P0+P1 sprint), v1.1.91 (UX follow-up), and v1.2.0 (subscription headers, group rows, sort toggle, ping fixes) were also produced through the same assistant. AI-assisted commits are tagged withCo-Authored-By: Claudein their trailer when material code was generated by the assistant.
If we missed your project here, please open an issue - credit is the one thing we can give back, and we want the list to be complete.
Release notes
v1.3.1 (2026-05-27) — Telemost: phantom-lock fix, joiner-side slot patch, idle CPU
Iteration on the Telemost protocol shipped in v1.3.0. Three fixes in
librelay.so, all behind the same telemost:// profile path — no UI
changes, no new settings, just a recompile of the embedded Go binary.
Phantom-tolerant peer lock (server side, deployed in the creator on
the conference-host VM). When a Yandex Telemost room contains more
than one peer (the creator + any leftover phantom participants from
prior sessions), the obfuscator's peerEpoch used to ping-pong between
peers on every other frame, triggering PeerRestart → OnPeerRestart
→ bridge closeAll, which kills every active TCP/UDP tunnel. The
creator now locks to the first peer that delivers real payload (not
keepalive) and silently drops frames from other epochs. 30s idle
release if the locked peer goes quiet. Frames from phantoms decode to
nothing and never reach the relay bridge.
Joiner-side slotsConfig handler rewrite (client side, in
librelay.so). Yandex's slotsConfig is a UI-layout signal — it
shuffles which video tile shows whose track, and per-slot UNBOUND
events fire constantly during layout reshuffles, especially when the
creator publishes multiple video tracks. The previous handler treated
UNBOUND as "peer gone" and called forceReconnect, which caused a
join → leave storm at ~1 cycle per second whenever the creator side
had WLB_EXTRA_VIDEO_TRACKS>0. Now slotsConfig only updates
diagnostic bound-peer state; the real peer-gone signal comes from
removeDescription, and tunnel lifecycle is governed by WebRTC PC
state. The same patch was mirrored on the creator side, since the
creator's own kicking self path fired off the same bug.
Idle dual-ticker in VP8DataTunnel.writerLoop. The writer loop ran
a fixed ~1.4ms ticker (720 Hz) regardless of whether there was
anything to send. With multi-x6 mode this meant six librelay
instances each waking up 720 times a second on the phone. The loop
now uses two tickers — a fast one at sample interval, and a slow
500ms one for keepalive-only periods. After 240 consecutive idle
ticks the writer switches to the slow ticker; the first non-keepalive
chunk flips it back. On real traffic the rate is identical to before;
during idle the CPU/wakeup load drops by about two orders of
magnitude.
Net effect on download throughput: speedtest on the multi-x6 profile improved from ~7 Mbps to 9.46 Mbps down / 4.17 Mbps up (via Aeza Stockholm). Latency stays high (~1 s ping is normal for any WebRTC-tunnelled SOCKS path).
Multi-publisher path was explored and abandoned. We tried
publishing 3 simulcast-like video tracks per creator with
sticky-by-connID routing in the tunnel, hoping to break past
Yandex's per-stream ~1.25 Mbps cap. SDP negotiates fine (answer grew
from 1431 to 2755 bytes) and packets do flow, but Yandex SFU
aggressively rate-limits multi-stream output from a single publisher,
so total throughput dropped roughly tenfold compared to single-track.
Single-track multi-x6 stays the best path on Yandex Telemost as the
SFU; the multi-track scaffolding remains in the code for future use
on a different SFU.
v1.3.0 (2026-05-26) — fsociety theme + Telemost multi-channel tunnel
fsociety theme. Mr. Robot inspired phosphor-terminal redesign (Settings → Theme → fsociety). Pure-black background, classic Apple-IIe / VT100 green text, red destructive accent, monospace typography across the whole app. Other themes (Dark / Light / OLED / Ocean / Dynamic) are untouched and the redesign is opt-in.
What changes when the theme is active:
- One-line VT100-style boot sequence on cold start of
MainActivity(5 lines typed in ~1.4s then faded out, plays once per process). tv_fsoc_headershows the hello, friend. pilot opener above the status line on the home screen.- Connection status text rewritten:
[ OFFLINE ]/[ DECRYPTING TUNNEL... ]/[ ROOT // ALIVE ]/[ SEGFAULT ], with a blinking cursor (transparent-color span trick so the line never jitters). - Profile list renders in
vlessctl lsstyle — zero-padded[NN]index counted over visible profile rows, name uppercased, address and protocol on one row separated by│. - Settings section headers prefixed with
// SECTION_NAMEwalker applied recursively to bold wrap-content TextViews in the fragment root. - Easter egg: tap the profile name 5 times within a 3-second window
to fade in a random Mr. Robot quote (23 quotes, translated to all
15 supported locales; English source line shown, native translation
shown beneath separated by
—).
Telemost protocol. New native Protocol.TELEMOST for relaying
Yandex Telemost / Yandex SFU traffic that other obfuscation protocols
can't carry well (per-stream UDP cap on the Yandex backend).
How it works at the wire layer:
- A profile pasted as a single
telemost://...URI (or multiple URIs in a subscription that gets auto-detected as single/multi) carries the upstream Telemost relay credentials. TelemostRelayManagerspins up one or more loopbacklibrelay.soGo binaries (ARM64 static, ~11.7 MB, shipped injniLibs/arm64-v8a/) — one per relay-link in the profile.SocksRoundRobinLb(Kotlin, byte-transparent) load-balances each inbound TCP connect across the healthy local SOCKS5 instances with a 15-second cooldown for upstreams that fail to handshake.XrayConfig/Ping/Preflightskip the Telemost profile (no xray inbound is generated for it).TunnelVpnServicedispatches TELEMOST through the LB watchdog, not the xray pipeline.- Loopback-only path doesn't carry SOCKS5 auth — the byte-transparent
LB used to fragment auth handshakes when round-robining the same
connection across instances. Without auth the relay is restricted
to
127.0.0.1and isn't exposed to other apps on the device.
Multi-channel x3 / x6 is what makes the protocol useful: a single
profile spawning N parallel librelay.so workers means new TCP
connects are balanced across N independent upstream channels,
breaking past the per-stream Yandex SFU cap (~1.25 Mbps down /
~23.5 Mbps up per channel in our measurements). Single-channel
Telemost is still useful for reach; multi-channel is what gives
usable bulk throughput.
Subscription parser. parseSubscription no longer feeds plaintext
URI lists through the base64 decoder — if the body already starts with
vless:// / telemost:// / etc. we skip the decode step. Some
providers and copy-paste flows hand us plain text that the base64
decoder accepts as valid garbage, yielding empty profile lists.
Localization. 585 new translation strings added across 15 non-English locales (ar, de, es, fr, hi, in, it, ja, ko, pt, ru, th, tr, vi, zh-rCN) for theme labels, fsociety status strings, quote translations, and 11 previously-RU-only keys that needed parity. Every locale now reports 0 missing keys.
v1.2.2 (2026-05-11) — handover rewrite + Hysteria2 fix
Hysteria2 outbound config fix (GitHub issue #2). v1.2.16 generated
streamSettings.network = "hysteria2", which xray-core rejects with
"unknown transport protocol: hysteria2". Hysteria2 in xray-core is an
outbound protocol, not a stream transport — the QUIC/UDP layer is
implicit. streamSettings is only emitted now if TLS is enabled, and
obfs moved into settings (not streamSettings.hysteria2Settings).
Network handover rewrite. The handover code shipped in v1.2.16 ("registerDefaultNetworkCallback + SIM-swap detection") looked correct on paper but didn't actually trigger in practice. Three real-world scenarios (WiFi→cellular fallback, cellular → WiFi promotion, cellular tower-loss recovery) all left the VPN bound to a dead network until the user manually toggled it.
Five bugs found via logcat traces and fixed in TunnelVpnService.kt:
registerDefaultNetworkCallbackis useless for a VpnService — the default for our own UID is the VPN, so the callback only ever fires with the VPN-self handle. All the SIM-swap / validation / transport logic guarded onif (network != currentNetwork) returnwas dead code. Default callback is now a stub that just logs; aux callback (which filtersNET_CAPABILITY_NOT_VPN) became the sole handover detector.- Aux compared against
cm.activeNetworkwhich for a VPN service is either the VPN itself or the candidate network. Switched to comparing againstlastPublishedNetwork— the network we actually set assetUnderlyingNetworks. - Aux
onLostdid nothing useful — the lost network just got removed from afiredForset. Now: if the lost network was our underlying, repick a replacement and reconnect; if no replacement is available yet, setlastPublishedNetwork = nullso the next validated aux network counts as a strict improvement. reconnectTargetNetworkonly cleared on success path. A reconnect job that died via cancel/timeout/error lefttarget=<dead network>forever. SubsequentscheduleReconnectcalls for the same Network saw "already running, not cancelling" and silently dropped. Now cleared infinallyof every exit path.cancelAndJoindeadlock inrestartTunnelProcessesKeepTun. The watchdog body suspends inProcess.waitFor()— uncancellable blocking IO onDispatchers.IO. Joining the watchdog before destroying the process meantwaitFor()never returned, the watchdog couldn't observe cancellation, andcancelAndJoinhung indefinitely. This was the main reason WiFi-off→cellular fallback never recovered: the old xray on the dead WiFi stayed alive, watchdog stayed stuck, the keep-tun reconnect job deadlocked. Reordered: kill processes first → watchdogs'waitForunblocks →cancelAndJoinreturns instantly.
Also fixed the log-fragment filter chips: deselecting Error/Warn/Info
used to re-apply the level filter (the per-chip OnClickListener fired
on both check and uncheck and unconditionally set currentFilter to
that level). Switched to ChipGroup.setOnCheckedStateChangeListener
which sees the resulting checked-IDs set, so an empty selection
correctly maps to "all".
Known issue: after airplane-mode toggle, the keep-tun reconnect to
cellular completes successfully (Tunnel reconnected without TUN restart
fires), but xray's upstream TCP doesn't actually carry traffic until
WiFi comes back. Tracked separately — the handover detection is correct,
the bug is now somewhere inside xray's protect/bind to the new Network.
versionCode 50, versionName "1.2.2".
v1.2.15 (2026-05-05) — БС-mode (Russian whitelist) hardening
Russia turned on "white-list mode" for mobile data on May 5 (until May 9). Under that
regime the operator's edge router drops every TCP SYN that targets a non-whitelisted
IP before ТСПУ DPI looks at SNI — so a plain TCP preflight to a foreign VPS gets
RST-injected even though the actual VLESS+Reality TLS handshake (with SNI=max.ru
or another whitelisted domain) would pass through.
failoverMaxAttempts3 → 14. Try the entire 16-profile subscription before surfacing an error. Previously the budget exhausted before reaching CF tunnel IPs that sometimes overlap with whitelisted ranges.ServerPreflightis now non-fatal. Log a warning instead of throwingIllegalStateException. xray attempts the real Reality handshake; the protocol passes the operator's L7 check even when L4 SYN gets RST-ed by ТСПУ. v2rayTUN doesn't preflight and works in this scenario — match its behavior.- Whitelist hint in error message. Detect ECONNREFUSED + timeout pattern
across multiple failovers, surface human-readable
"БС-режим? VPN не пройдёт пока оператор не снимет белые списки. Попробуй Wi-Fi."
instead of the abstract
TCP: ECONNREFUSED. - DNS rebinding guard (private/reserved address rejection) inside preflight retained — security boundary unchanged.
versionCode46,versionName"1.2.15".
v1.2.0 (2026-05-03)
Big UI + plumbing release. The Servers tab now shows subscription metadata
(traffic, support / website icons, announcement) and a real subscription
header per group. Several long-standing bugs around selection state, ping,
and frame stability fixed.
Subscription headers + metadata
- New
Subscriptionfields:usedBytes,totalBytes,supportUrl,webPageUrl,announce. Parsed from the standard subscription response headers -subscription-userinfo(upload/download/total),support-url,profile-web-page-url,announce(plain UTF-8 orbase64:...prefixed). - DB migration
5 → 6:ALTER TABLE subscriptionsadds the five columns. ProfileAdapterrebuilt as a multi-viewType list: each subscription gets a row of its own (item_subscription_header.xml) before its profile rows. The header shows the subscription name (left, ellipsizes if too long), a traffic capsule centered in the gap (used / total or used / ∞ for unlimited quotas), and clickable globe / paper-plane icons on the right that open the provider's website / support chat. Icons hide automatically when the matching URL is empty.SubscriptionGroupDecorationsimplified: text and icons live in the header row now; the decoration only paints the colored frame around each group. Frame is stable during scroll -top/bottomextrapolate past the viewport when one end of the group is offscreen, so it no longer collapses onto the visible subset.- Item-change animations disabled on the servers list - selecting a profile or refreshing pings no longer makes the group frame "jump".
Bug fixes
- "Server not selected" while VPN is running.
replaceSubscriptionProfilesnow preservesisSelectedandisFavoriteacross a subscription refresh by matching old → new profiles on the stable identity tuple (protocol + address + port + uuid + password). Previously the auto-update worker wiped the selected server and the home screen showed "no profile selected" while the tunnel kept running on the now-orphaned config. - Hysteria2 / UDP ping.
PingHelper.pingForProfiledispatches by protocol: TCP-handshake for VLESS / VMess / Trojan / Shadowsocks, ICMP echo (via Android's setuid/system/bin/ping) for Hysteria2 (QUIC over UDP). Hy2 servers no longer always show-in the ping column. - Subscription title decoder.
decodeProfileTitlenow tries the standard base64 alphabet before URL-safe; some providers (GruVPN among them) sendbase64:payloads containing/, which the URL-safe decoder rejected and the subscription name silently fell back to the host.
Servers tab UX
- Sort menu item is a toggle. First tap sorts by ping; while sorted by ping the menu item reads Sort by Subscription and a second tap restores the default order (grouped by subscription, profiles in the original order the server returned them).
- Friendly profile names. The labels we generate for our own subscriptions are now user-readable ("🚀 Основной - быстрый", "🇷🇺 Если в РФ не работает", "🛡 Резерв 1 (Reality)", "☁ Через Cloudflare 1", "⚡ Быстрый UDP", "🔒 Trojan / Shadowsocks (резерв)") instead of the previous "DE Reality" / "CDN WS WARP" abbreviations.
Theming + status bar
- Light theme now sets
windowLightStatusBar=trueand (API 27+)windowLightNavigationBar=true, so the system clock / battery icons switch to dark on the white status bar instead of disappearing. - Card backgrounds for selected / unselected profiles are theme-tied via two
new attrs (
cardNormalBackground,cardSelectedBackground); each theme (Default Dark / Light / OLED Black / Ocean / Dynamic) supplies values that stay readable on its own surface palette. Previously the dark-theme defaults rendered as semi-transparent slivers on the light theme.
Internals
- New string keys:
sort_by_subscription(en + ru). Other locales fall through to English. - New drawables:
ic_globe.xml,ic_paper_plane.xml,bg_traffic_capsule.xml,capsule_progress_drawable.xml. Tints follow?android:attr/textColorPrimaryso a single asset works on every theme. versionCode40,versionName"1.2.0".
v1.1.91 (2026-05-03)
UX + plumbing follow-up to v1.1.9. No new security claims - every change here either widens what the app accepts as input, makes existing state visible, or hides a confusing error behind a clearer one.
- Onboarding accepts subscription URLs. Previously the first-server step only parsed
vless:/vmess:/trojan:/ss:/hy2:URIs and rejectedhttps://subscription links with aCouldn't parseerror. The wizard now detects anhttp(s)://input, validates it through the sameSubscriptionRepository.validateUrlguard, inserts it as a subscription, and triggers a realupdateSubscriptionso the profile list is fetched in-place. - TLS errors translated.
chain validation failed/trust anchor for certification path not found/certPathValidator/notBefore/notAfter/expired/not yet validare mapped to a one-liner about device date/time, which is the actual cause 90% of the time.Certificate pinning failureis rephrased as a cert-rotation hint,unable to find acceptable trust anchoras a server chain config issue. - Trigger menu redesign. Header section with switches, explainer and dual-app warning is now collapsed by default behind a Material 3 card that shows the current state at a glance:
Disabled · tap to set uporEnabled · strict · 5 apps. The card has a chevron icon that rotates 180° on expand. Replaces the easy-to-missHide info/Show info & switchestext button - the apps list now occupies most of the screen. - Onboarding wizard skipped on upgrade. First launch of a fresh install still shows the wizard. An upgrade from any pre-1.1.8 version is detected by checking SharedPreferences for any non-
onboarding_donekey - a returning user has saved settings, so we setonboarding_done=trueand go straight to the main UI. - Subscription name auto-fills from server.
updateSubscriptionnow parses theprofile-titleresponse header (plain UTF-8 orbase64:…prefixed, URL-safe + standard alphabets, missing padding allowed). When the server doesn't expose that header, the URL fragment (#MyName) is used - same convention asvless://…#NodeNameURIs. Hostname is the final fallback. The previous behaviour of stamping every subscription with the literal string "Subscription" is gone. - Long-press to rename. Long-press a subscription row in the list to rename it. Sets
Subscription.userRenamed=true, which protects the user-chosen name from being overwritten on the next refresh. Manual entry in the Add Subscription dialog also flips the flag. - Add Subscription dialog clearer. Name field hint now reads
Name (optional)with a small subtext explaining that the provider'sprofile-titlewill fill it in. Empty input falls back to the URL host instead of the literal "Subscription". - Trigger settings switches auto-save. v1.1.8 patch - Enable / Strict / AutoStop now persist immediately on toggle via
viewModel.updateSettings, no longer requiring the user to scroll past the apps list to find the Save button (the button still applies the apps-list selection + VPN permission flow). - DB migration 4 → 5.
subscriptions.userRenamed INTEGER NOT NULL DEFAULT 0. Existing rows pick up the auto-fill behaviour on next refresh. - Internals. New string keys:
onb_profile_subscription_fetching,import_subscription_confirm,add_subscription_name_optional_hint,add_subscription_name_optional_help,subscription_rename_title,subscription_name_hint,trigger_options_card_title,trigger_options_summary_off/on,trigger_mode_strict/flexible. EN + RU translated; other locales fall through to English. New drawableic_chevron_down.versionCode34,versionName"1.1.91".
v1.1.9 (2026-05-02)
Sprint-1 + Sprint-2 of the post-v1.1.8 code-review fix list. Closes 6 P0 (critical) and 9 P1 (high) findings around SSRF guards, network-level fingerprint, log redaction, and confirmation flows.
P0 - SSRF / leak surface
parseVmessnow runs the sameAddressValidator.requirePublicAddresscheck as the other URI parsers, plus1..65535port range. The base64-encoded JSON could previously slip in127.0.0.1/ private / CGNAT addresses that the rest of the app trusted.ProfileEditViewModel.isValidAddressdelegates toAddressValidator.isPrivateOrReservedand validates IPv6 viaInetAddress.getByName("[…]"). Manual save of127.0.0.1,192.168.x,100.64.x, hex/octal IPv4 or IPv4-mapped IPv6 is now rejected.ServerPreflight.checkre-validates the resolved IP afterInetAddress.getByName. Closes a DNS-rebinding window where a hostname could resolve to a private IP between profile parsing and TCP probe.MainViewModel.autoSelectAndConnectno longer fires N parallel TCP SYNs from the user's real IP. Uses cachedlastPingMsif any profile has a fresh value; otherwise probes only 3 random candidates sequentially with an early stop on<200ms. Eliminates the "burst of SYNs to scattered cloud IPs" fingerprint that ISP/corp DPI uses to flag VPN clients in the auto-select phase.- xray and tun2socks output is now run through
LogBuffer.redactPublicbeforeLog.d, not just inside the in-app Log viewer. ProGuard now stripsLog.d/Log.vfrom release builds entirely. - QR scan no longer imports profiles or subscriptions silently. Mirrors
MainActivity.handleDeepLink's confirmation dialog: VPN URIs go throughProfileParser.parseSingleUri+ aMaterialAlertDialog; HTTPS subscriptions show a confirmation with the host preview; HTTP and unrecognised QR content are rejected with a Toast.
P1 - high-priority hardening
LogBuffer.redactrebuilt: coverspassword/passwd/pwd/pass/secret/api_key/token/bearer/authorization(case-insensitive, both=and:, optional quotes), JSON form"key":"value", Realitypbk/sidlong base64, Shadowsocks URL base64 between://and@, hostname/IP in network-error lines (dial/connect to/dns/resolve). Quick-check keywords removed (the-keyword matched almost every line).LogBufferswitched fromArrayList.removeAt(0)toArrayDeque.removeFirst()(O(1) vs O(n)). The "throttle" flag was a no-op; replaced with a real CONFLATEDChanneldebounced at 120ms forStateFlowupdates.SubscriptionRepository.validateUrlis now public, called before DB insert inSubscriptionViewModel.addSubscriptionandSettingsViewModel.importConfig. Junk subscription URLs no longer persist and are not retried by the periodicSubscriptionUpdateWorker. Added 256-char limit to subscription names (mirrorsProfileParser.MAX_NAME_LENGTH).ProfileEditViewModel.testConnectionrefuses to TCP-ping when the tunnel is down. When connected, performs a SOCKS5 CONNECT through the local authenticated bridge and times the handshake. No more direct probe from the user's real IP.HomeFragmentskipsGeoLookup.fetchUserLocation()when the tunnel is down - theipwho.isrequest previously went out from the user's real IP on every Home open, and once a week even with a warm cache.- Subscription list masks the URL: shown as
host/…/abcd(last 4 chars of path). The full token-bearing URL is no longer rendered into aRecyclerViewwhere any screenshot leaks it. SettingsFragment.isValidDnsnow goes throughAddressValidator.isPrivateOrReserved, covering CGNAT, IPv6, hex/octal IPv4, IPv4-mapped IPv6 - all of which the previous regex missed.SecuritySelfTestadds an "Own SOCKS5 auth" check that connects to our own ephemeral SOCKS port and asserts that the no-auth handshake is rejected. Catches future regressions inXrayConfigGenerator.buildInboundsautomatically.ServiceTesterreturns uniform error results when the tunnel is down instead of testing youtube.com / openai.com / instagram.com / discord.com from the user's real IP.buildClient()now returnsnullif credentials aren't ready - there's no direct-connection fallback path left.- All interactive elements in the onboarding wizard (Back / Next / Grant VPN / Grant Usage Stats / Paste / Import / Skip) carry
android:filterTouchesWhenObscured="true". Onboarding is the highest-stakes UI flow (VPN permission, mode selection, first profile) and was the only place README's tapjacking-protection claim didn't apply.
Internals
- New strings:
import_subscription_confirm(en + ru). Other locales fall back to English via Android's locale resolution. versionCode33,versionName"1.1.9".
v1.1.8 (2026-05-02)
First-launch onboarding wizard - the app now opens with a 7-step setup flow on first install: language → welcome (with feature cards) → routing-mode picker → VPN permission → Usage Stats permission (only when Trigger mode is picked) → first-server import → done. State is persisted in SharedPreferences (onboarding_done), so the wizard is shown exactly once. Subsequent launches go straight to the main UI. Profile import accepts vless:// / vmess:// / trojan:// / ss:// / hy2:// links via paste-from-clipboard or manual paste, with an explicit "Add later" skip button if the user has no profile yet. The bottom progress bar uses Material 3 LinearProgressIndicator with trackCornerRadius, animated via ObjectAnimator between steps; transitions use a slide+fade choreography (180ms out, 220ms in).
- Live language switching, no flicker.
OnboardingActivitydeclaresconfigChanges="locale|layoutDirection|uiMode|fontScale"and overridesonConfigurationChangedso a locale switch viaAppCompatDelegate.setApplicationLocalesdoes NOT recreate the activity. Instead, arefreshMapof(TextView, R.string.X)pairs is walked and every translated string is re-set in place - including theTextInputLayouthint and the bottom Back/Next button labels. Result: pick a language, the entire wizard updates instantly without a single frame of black. - Selection cards rebuilt without
isCheckable.MaterialCardViewwithisCheckable=true+checkedIcon=nullcrashes on Android 14 with NPE inc5.b.onAnimationUpdate(Material's checked-icon animator tries to set alpha on a nullDrawable). The language cards are now plainMaterialCardViewwith manual stroke-color toggling (colorOutlineVariant↔colorPrimary); selection is conveyed by theRadioButtonplus the colored outline. No checkmark icon, no crash. - Translations. All onboarding + trigger-mode strings have been added to all 16 supported locales:
ar,de,en,es,fr,hi,in,it,ja,ko,pt,ru,th,tr,vi,zh-rCN. Around 50 new strings × 14 non-base translations = ~700 new translations.
Flexible trigger routing mode - new triggerStrictMode setting (default true to preserve v1.1.7 behavior). When false, the trigger watcher acts as a launcher only: opening a trigger app brings up the regular global tunnel governed by the user's normal perAppMode (DISABLED = global, WHITELIST, BLACKLIST); closing it (with triggerAutoStop) calls TunnelVpnService.stop(). Strict mode keeps the v1.1.7 allow-list semantics (only trigger apps route through the VPN, blackholed when down). The new switch is in Settings → Trigger apps with an inline explainer.
App.onCreate: skipsstartQuarantinewhen flexible mode is enabled.App.updateTriggerWatcher: only pre-warms when strict.ForegroundAppWatcher.onForegroundChanged: branches betweenactivateTrigger/deactivateTrigger(strict) andstart(profileId)/stop()(flexible).TriggerAppsFragment.save: stops force-disabling per-app routing and stops the BLACKLIST overlap auto-cleanup when flexible mode is on - composing trigger detection with per-app rules is the whole point.- Always-on auto-start path in
TunnelVpnService.onStartCommandalso respects the flag.
Dual-app warning surface. Cloned variants of an app (Telegram via MIUI Dual Apps, Samsung Dual Messenger, Parallel Space) cannot be added to the VPN allow list - Android's public addAllowedApplication(packageName) only resolves the calling user's UID, while the clone runs in a separate user space (UID ≥ 999000 on MIUI). The Trigger Apps screen now shows a red Material 3 card explaining this, with a "Why?" dialog detailing the limitation and three workarounds (use original Telegram, disable "Block connections without VPN" in system settings, or add the cloned package separately if the OEM exposes it).
Internals
OnboardingActivitylives inui/onboarding/; registered in the manifest withsingleTasklaunch mode and the configChanges flags above.MainActivity.onCreateshort-circuits toOnboardingActivityon first launch (onboarding_donepref absent), and respects anEXTRA_OPEN_TRIGGERextra to navigate directly to the trigger picker after onboarding when the user picked Trigger mode.- Wizard state (current step, picked routing mode, picked language, profile-import counter) survives configuration-change events via
onSaveInstanceState. versionCode32,versionName"1.1.8".
v1.1.7 (2026-05-02)
App-launch trigger mode - the headline feature. Pick which apps go through the VPN; everything else (banking, maps, your usual browser) keeps using the regular connection. The selected apps have no fallback path: if the tunnel is down, their packets are black-holed in the TUN - they never see your real IP, not even for a moment.
- New
ForegroundAppWatcherservice pollsUsageStatsManager(250ms) to detect when a trigger app comes to the foreground. - Pre-warm: when trigger mode is enabled, the tunnel comes up immediately and stays ready, so opening a trigger app is instant - no "Connecting…" delay.
- Optional auto-stop on background to save battery (xray killed, TUN keeps the apps black-holed).
- Boot-survival:
BootReceiverre-arms the watcher and tunnel after device reboot. - Auto-recover: if xray crashes, up to 3 silent restarts before any user-visible failure. In trigger mode the TUN is preserved as a quarantine even after xray gives up - never falls back to "no VPN" for trigger apps.
- Network sanity: skipping activation when no underlying network is available, surfacing it as "Нет сети" / "No network" instead of a stale "Connecting…".
- UI: dedicated screen with Material 3 switches, collapsible explainer, search box, system-app filter, save/cancel. Big red banner explains the Always-on VPN sweet spot ("turn ON Always-on, leave Block-without-VPN OFF").
Bug fixes
- Backup / Restore: profiles now keep their subscription link. Old format (v1, plain URI list) is still accepted; new exports use v2 with
{uri, subscription}per profile. - Settings: scroll position is preserved when you navigate into a sub-screen and back.
- Settings: bottom-nav tap always returns to the root of the tab - no more "I went into Trigger then tapped Servers and came back to Trigger with the wrong tab highlighted".
- Quick Settings tile: when there is no saved profile, opening the tile now triggers auto-select (best non-RU server) instead of silently doing nothing.
- DNS: when a non-empty Per-App blacklist is active, the VPN no longer overrides system DNS for the excluded apps. Previously they were forced onto Cloudflare/Google DNS, which is filtered by some Russian ISPs and broke their resolution.
- Connect button now triggers auto-select when no profile is picked, instead of doing nothing.
- Per-App routing description rewritten to make the relationship to Trigger mode obvious; toggling Trigger automatically disables Per-App routing to avoid silent conflict.
Internals
- Tunnel start re-ordered: TUN comes up before xray now, eliminating the leak window during start.
- Faster activation: tighter polling in
sendTunFdandwaitForPort, 100ms watcher tick, 50ms post-establish delay - saves ~600-700ms on cold open of a trigger app. setUnderlyingNetworksnow passes ALL non-VPN networks (cellular + wifi if both available) instead of just the first one.
v1.1.6 - 2026-04-30
Security audit (P0/P1 fixes)
- No more HTTP fallback in geo-IP lookup.
GeoLookup.ktpreviously fell back tohttp://ip-api.com/json/when the HTTPSipwho.isprovider was unreachable. Any on-path observer would have seen the real source IP in plaintext during that fallback. The HTTP path is now removed entirely; if HTTPS lookup fails, the result is simplynull. - Geo cache moved to
EncryptedSharedPreferences. User coordinates were previously cached in plainSharedPreferences(/data/data/<pkg>/shared_prefs/geo_cache.xml). A privileged co-resident process or root could read them. The cache is now stored undergeo_cache_encwithMasterKeyAES-256-GCM, with a one-shot migration that wipes the legacy plaintext on first launch. - Profile validation before xray config build.
XrayConfigGenerator.validateProfile()now rejectsportoutside1..65535and any control-character (\x00..\x1f,\x7f) inaddress,host,path,sni,serviceName,authority,publicKey,shortId,alpnbefore the JSON is fed to xray. A corrupt stored profile no longer kills the tunnel through the watchdog loop; instead a single readable error surfaces. - No more
Thread.sleepon Dispatchers.IO.sendTunFd()andstartTun2socksProcess()are nowsuspendand usedelay()while waiting for the tun2socks Unix-domain socket. The previousThread.sleep(200)calls were blocking IO worker threads up to 2 s per startup. - Concurrency:
@Volatileon cross-thread service state.vpnFd,serviceScope,trafficMonitor,xrayProcess,tun2socksProcess,networkCallback,currentProfileId,showSpeedNotification,xrayWatchdogJob,tun2socksWatchdogJob,lastNotificationUpdateare now all@Volatile(orsynchronized(fdLock)forvpnFd). Writes from the main thread are now visible immediately to the watchdogs running onDispatchers.IO, so a watchdog cannot see a stalenulland skip recovery. - Faster network-switch reconnect. WiFi↔LTE handover debounce reduced from 2000 ms to 500 ms - the TUN black-hole window during a clean handover is now sub-second.
ACCESS_FINE_LOCATIONcapped at API 32.NEARBY_WIFI_DEVICES(neverForLocation) covers SSID/BSSID reads on Android 13+, so the privacy-sensitive permission is no longer requested on devices where it is no longer needed.
VPN-detection bypass (per-app)
- New "Exclude all Russian apps" / "Исключить приложения РФ" button in Settings → Per-app routing. One tap sweeps every installed non-system app whose
packageNamestarts withru.and adds them to the per-app list, plus a curated set of Russian apps that publish undercom.*/io.*(Tinkoff, Sberbank, Otkritie, Wildberries, Ozon, ICBC, AliExpress AER, the full Yandex stack, etc.). WhenPerAppMode.BLACKLISTis active, those apps see the real underlying network instead of the tunnel - their IP / GeoIP /TRANSPORT_VPNflag stays consistent with the SIM/Wi-Fi region. This is the only client-side mitigation available without root against the published "VPN/Proxy detection on client devices" methodology, which relies onConnectivityManager.NetworkCapabilities.TRANSPORT_VPN,VpnTransportInfo, anddumpsys vpn_management- none of which can be hidden by an app from another app at the OS level. The button is locale-gated (visible only onru/en) so non-Russian-locale users do not see an unhelpful action. - Auto-bypass for newly installed RU apps. A
PACKAGE_ADDEDBroadcastReceiver(registered dynamically because Android 8+ disallows static manifestPACKAGE_ADDEDfor regular apps) appends every freshly-installed non-system app whosepackageNamestarts withru.toperAppList, but only when the newautoBypassRuPackagestoggle is on. Without this the one-shot button only protects apps that exist at the moment the user taps it.
Tunnel reliability
- MTU-probing.
VpnService.Builder.setMtu()andtun2socks --tunmtuare now derived fromConnectivityManager.getLinkProperties().getMtu()of the active non-VPN network, capped to[1280, 1500]. On many mobile carriers the LTE/5G link MTU is 1428 or 1450 - the previous hard-coded1500caused silent IP fragmentation and a 5–15 % throughput drop. - Pre-flight reachability check. Before xray + tun2socks are spun up,
ServerPreflightdoes a 1.5 s DNS + 2 s TCP probe of the target address. A dead server short-circuits toConnectionState.Errorin <2 s instead of burning the full 30 s tunnel-establishment budget. UDP-only protocols (Hysteria2) fall back to DNS-success-only since we cannot meaningfully ping UDP without speaking the protocol. - Auto-failover. When
startTunnel()fails (timeout, preflight dead, xray crash, tun2socks exit) the service automatically tries the next untried profile from the same subscription (or the next one in the database if the current profile has no subscription), with a budget of 3 attempts. The ledger resets on a successfulConnectedstate and on user-initiatedstopTunnel. - TLS fingerprint randomisation. New
TlsFingerprintModesetting (CHROME/FIREFOX/SAFARI/IOS/EDGE/RANDOM) feeds the uTLS fingerprint string used for VLESS / VMess / Trojan TLS and REALITY outbounds. InRANDOMmode a fresh fingerprint is picked per connect, so DPI systems cannot correlate consecutive sessions by ClientHello shape. Per-profile override (set byProfileParserwhen the URL carriesfp=…) still wins over the global setting. Fixed a long-standing bug whereServerProfile.fingerprintdefaulted to"chrome"instead of"", which made any global rotation a no-op for stored profiles.
Tests
- New JVM unit tests under
app/src/test/java/com/smarttools/netguard/core/:XrayConfigValidationTest- port range, control-character rejection in 9 string fields, fingerprint resolver (default / global / random / per-profile override), SOCKS port + credentials sanity (11 cases).ServerPreflightTest- empty / unresolvable / closed-port / listening-port / UDP-only paths (5 cases).
app/build.gradle.ktsenablestestOptions.unitTests.isReturnDefaultValues = truesoandroid.util.Logcalls in code under test do not throwMethod d in android.util.Log not mocked.
v1.1.4 - 2026-04-18
Security
- SSRF hardening. All private / loopback / link-local / CGNAT (
100.64.0.0/10) / reserved (240.0.0.0/4) / IPv4-mapped-IPv6 / hex-and-octal IPv4 literals (0x7f.0.0.1,0177.0.0.1) are now rejected by a singleAddressValidator. The old spot checks onstartsWith("10."),"192.168."etc. were trivially defeated by alternative textual forms; the new validator resolves literals numerically viaInetAddressand inspects the resulting bytes. - DNS rebinding guard. Subscription fetches now go through a custom OkHttp
Dnsresolver that re-validates every address returned at connect time, so an attacker who controls the subscription host's DNS cannot swap the validated public IP for a private LAN IP between validation and connection. - Ephemeral SOCKS credentials. The local SOCKS5 / HTTP bridge credentials are now stored as
CharArrayinsideCredentialManagerand actively zeroed onclear(), instead of lingering as immutableStringheap residue until GC. - No-leak network switching.
VpnService.setUnderlyingNetworks()is now pinned to the active non-VPN network both at tunnel establishment and on every WiFi↔LTE switch, eliminating the narrow window where xray's outbound socket could transiently route over the old interface. - Honest README. Claims that were not strictly true in the code - "credentials wiped from memory immediately after handshake", "config never touches disk", "encrypted database" - have been rewritten to reflect what actually happens. The matching Known limitations section documents tracked items (SQLCipher integration,
security-cryptodeprecation, tun2socks credential argv exposure).
Platform compat
- Android 14 FGS. Foreground-service type migrated to
specialUse | systemExemptedwith the requiredPROPERTY_SPECIAL_USE_FGS_SUBTYPEdeclaration. The previousspecialUseon Android 14 raised a runtimeSecurityExceptionatstartForeground(); a naive migration toconnectedDevicealso fails because that type requires holding one of BLUETOOTH/USB/NFC permissions that a VPN legitimately cannot claim. - Android 13+ WiFi.
NEARBY_WIFI_DEVICESpermission (withneverForLocation) is now declared and requested at runtime when the user opens the Trusted WiFi dialog or flips the auto-connect toggle; without it, SSID read silently returned<unknown ssid>on stock Android 13+. - IPv6 route is now conditional on the
Enable IPv6setting. PreviouslyaddRoute("::", 0)was claimed unconditionally - on IPv4-only servers, AAAA-bound traffic disappeared into tun2socks instead of falling back gracefully.
WiFi auto-connect reliability
- SSID/BSSID lookup falls back to
WifiManager.connectionInfowhen Android stripstransportInfofrom background callbacks. - Events are de-duplicated by
(SSID, BSSID)so the throttledonCapabilitiesChangedfirehose (RSSI / link-speed / validation updates) no longer spamsTunnelVpnService.start(). - If the first capability event arrives with
<unknown ssid>(Wi-Fi still associating), the manager retries at 2 s / 5 s / 10 s viaWifiManagerinstead of losing the event. - An initial probe runs after
registerNetworkCallbackso sessions that start while already on Wi-Fi are handled, not only subsequent network switches. - Trusted WiFi dialog works even before enabling auto-connect - it uses a transient manager instance to read SSID/BSSID.
Code quality
- New
Security self-test: "Neighboring VPNs" check scans for other installed VPN clients (v2rayNG, Hiddify, Happ, SFA, Karing, WireGuard, OpenVPN, strongSwan, …) that may be vulnerable to the April 2026 local-SOCKS leak. MTUself-test is now correctly reported as informational: flaggingMTU != 1500as a "VPN anomaly" was both false-positive on well-behaved VPNs and contradicted NetGuard's own stealth choice of MTU=1500.- Speed-test URLs switched to HTTPS where the endpoint supports it; cleartext exception narrowed to
speedtest.tele2.netonly (was three domains). - Removed unused
ConfigBuilder.ktwrapper, cleaned out imaginarycom.github.nicknob.*entries fromKNOWN_VPN_PACKAGES, removed the deadSQLCipherdependency (was declared but never wired into Room). - First unit tests:
AddressValidatorTestcovers the SSRF-critical logic (16 cases, including CGNAT, hex/octal IPv4, IPv4-mapped IPv6). - Run
./gradlew testDebugUnitTest lintReleaselocally with the native inputs described inapp/libs/README.md; those inputs are not downloaded by CI. SECURITY.mdwith responsible-disclosure policy.User-Agentnow reads fromBuildConfig.VERSION_NAMEinstead of the hard-coded"NetGuard/1.0".
v1.1.3 - 2026-04-17
Security
- Dropped the unauthenticated local SOCKS5 inbound (
speedtest-in). That same pattern was disclosed as a leak for Happ, v2rayTUN, Hiddify and v2rayNG in April 2026: any app on the device could reach127.0.0.1and tunnel traffic through the VPN to learn the real server IP. Internal speed/service tests now talk to the authenticatedhttp-inbridge instead. - Restored DNS validation on the settings save path. It had regressed when the explicit "Save" button was removed, so
127.0.0.1,localhost, private ranges and random typos were being silently accepted. - TLS fragment and routing fields reject malformed input with a short toast instead of passing garbage through to xray.
New features
- TLS Fragment. Splits the TLS ClientHello into smaller pieces to slip past DPI that matches on SNI. Packets / length / interval are configurable in Settings.
- Favorites. Star a server to pin it to the top of the list. Room migrates v2 → v3 on first launch.
- Domain / IP bypass list. Lines of domains and IPs that go direct, regardless of the global routing mode.
- Traffic statistics graph. 7-day chart on the Home tab. 30 days of history retained.
- Widget. The home-screen widget is now 3×1 and shows the selected server + connection state. Tap anywhere on it to toggle the VPN.
UI / cosmetic
- Subscription list: the Share / Update / Delete buttons no longer overlap the subscription name and URL. Icons are theme-tinted so they stay visible on both light and dark backgrounds.
- Flat vector favorite stars in place of the legacy 3D Android star drawable.
- Connection map: adapts to light theme (bitmap is grayscale-inverted, land/ocean tints are swapped) and the dashed connection arc animates from the user to the server while the VPN is up.
Reliability
- The widget no longer runs a blocking Room query on the main broadcast thread. It reads the selected server name from a small SharedPreferences cache that's refreshed on profile select and on VPN start. Removes an ANR risk when the DB was locked during a subscription sync.
- Traffic history cleanup is one
apply()per day archive (was 30) and the cleanup window is wide enough to recover history after the app was idle for months.
Setup
Clone the repository.
Drop the libXray AAR into
app/libs/- seeapp/libs/README.mdfor the exact download link and version. Without this step the build will fail with unresolved native symbols.(Release builds only) copy
keystore.properties.template→keystore.propertiesand point it at your signing key. Debug builds auto-sign with the SDK debug key and do not need this.Build:
# Requires Android Studio with JBR (JetBrains Runtime) JAVA_HOME="/path/to/android-studio/jbr" ./gradlew assembleDebug
Requirements:
- Android SDK 34
- Kotlin 1.9+
- Min SDK 26 (Android 8.0)
Architecture
xray-core (VLESS/VMess/Trojan/SS/Hy2)
|
| authenticated SOCKS5 (random port, ephemeral creds)
|
badvpn-tun2socks
|
| TUN file descriptor
|
Android VpnService (TUN interface)
|
| per-app routing rules
|
apps
License
This project is provided as-is for personal use.
Explore related projects
trimstray/the-book-of-secret-knowledge
A collection of inspiring lists, manuals, cheatsheets, blogs, hacks, one-liners, cli/web tools and more.
257,971 starsHack-with-Github/Awesome-Hacking
A collection of various awesome lists for hackers, pentesters and security researchers
125,360 starsswisskyrepo/PayloadsAllTheThings
A list of useful payloads and bypass for Web Application Security and Pentest/CTF
82,623 starscaddyserver/caddy
Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
79,020 stars