Find a file
okhsunrog 2395db891e feat(lsposed): H+O mutex, inline help, issue severity split
Three UX-and-diagnostics fixes for issues surfaced by user reports.

H+O mutual exclusion in App Hiding:
Users marking the same app as both Hidden and Observer caused it to
crash on startup — the app would query its own PackageInfo during init,
system_server hook matched it as an observer and stripped its own
package from the result, and the framework bailed on NameNotFound.
Roles are now mutually exclusive: toggling one clears the other. On
first launch, any pre-existing H+O config is migrated to O-only and
dirty=true so the Save button prompts the user to persist the fix.

Help is always-visible, not behind a ? icon:
Real-world usage showed nobody taps the HelpOutline in the top bar —
users configure observers, flip toggles blind, then open issues when
things don't work. Help dialogs on Tun / Apps / Ports screens are
replaced with collapsible cards at the top of each list, expanded by
default and remembered-per-screen in SharedPreferences so experienced
users can collapse them without losing the affordance.

Issues → Errors + Warnings:
The single red-banner "Issues" list mixed "your setup is broken"
(LSPosed missing, no targets) with "your setup works but is suboptimal"
(version skew, extra scope entries). Split into two sections with
theme colors (error / tertiary). Five new warnings cover misconfigs
that went unreported before:

 * kernel supports kmod but only Zygisk is installed (zygisk is
   theoretically detectable; kmod isn't)
 * kmod and Zygisk both active (redundant hooks, larger fingerprint)
 * package marked as both Tun target and Ports observer (traps users
   with transparent-proxy clients — vpnhide_out REJECTs redirected
   loopback traffic and the app loses internet)
 * debug logging left enabled after a bug report (leaks tag-matched
   lines to any root-reading forensic scan — reads the flag file
   written by the Diagnostics toggle in the sibling PR, so fires
   only once that merges)
 * SELinux in Permissive mode (exposes six detection vectors we rely
   on the kernel to block — see the coverage table in README.md)

Also aligns lsposed/native/Cargo.lock with Cargo.toml (0.6.1 → 0.6.2)
— same stale-lock fix as in the other pending PRs.
2026-04-18 01:43:26 +03:00
.githooks Add pre-commit hook for ktlint 2026-04-14 03:04:57 +03:00
.github ci: rename vpnhide APK artifact + publish as draft release 2026-04-17 15:54:32 +03:00
assets docs: refresh README screenshots and install docs 2026-04-15 20:21:00 +03:00
docs ci: rename vpnhide APK artifact + publish as draft release 2026-04-17 15:54:32 +03:00
kmod chore: release v0.6.2 2026-04-17 16:24:53 +03:00
lsposed feat(lsposed): H+O mutex, inline help, issue severity split 2026-04-18 01:43:26 +03:00
portshide chore: release v0.6.2 2026-04-17 16:24:53 +03:00
scripts ci: rename vpnhide APK artifact + publish as draft release 2026-04-17 15:54:32 +03:00
update-json chore: update-json for v0.6.2 2026-04-17 19:45:15 +03:00
zygisk feat: debug logging toggle, off by default 2026-04-17 19:09:07 +03:00
.editorconfig style: add clang-format, ktlint, editorconfig and format all code 2026-04-12 23:26:36 +03:00
.gitignore chore(scripts): rewrite update-version in python, add changelog-add 2026-04-16 00:18:25 +03:00
.gitmodules monorepo: combine vpnhide-zygisk, vpnhide (lsposed), and vpnhide-kmod 2026-04-11 15:01:49 +03:00
CHANGELOG.md feat(lsposed): H+O mutex, inline help, issue severity split 2026-04-18 01:43:26 +03:00
CONTRIBUTING.md refactor(scripts): unreleased section + rename scripts 2026-04-17 14:34:47 +03:00
LICENSE license: unify entire project under MIT 2026-04-11 21:58:07 +03:00
README.md docs: refresh README screenshots and install docs 2026-04-15 20:21:00 +03:00
README.ru.md docs: refresh README screenshots and install docs 2026-04-15 20:21:00 +03:00
VERSION chore: release v0.6.2 2026-04-17 16:24:53 +03:00

VPN Hide

VPN Hide

Hide an active Android VPN connection from selected apps.

CI Release Downloads License

Русская версия

Why vpnhide over alternatives?

Existing modules like NoVPNDetect and NoVPNDetect Enhanced only cover Java API detection and hook inside the target app's process via Xposed. This has two critical problems:

  1. Invisible to anti-tamper — any app with memory injection checks detects the Xposed hooks and refuses to work. The NoVPNDetect Enhanced author explicitly states: "The module will not work if the target app has LSPosed protection or memory injection checks. For example, MirPay, T-Bank."
  2. No native coverage — apps using C/C++ code, cross-platform frameworks (Flutter, React Native), or direct syscalls can detect VPN through ioctl, getifaddrs, netlink sockets, and /proc/net/*. These vectors are completely missed by Java-only hooks.

vpnhide solves both problems with a layered architecture:

Layer 1 — Java API (lsposed module): hooks system_server, not the target app. NetworkCapabilities, NetworkInfo, and LinkProperties are filtered at the Binder level before data reaches the app's process. The app receives clean data over IPC — no injection into its process, nothing for anti-tamper to detect.

Layer 2 — Native (kmod or zygisk): covers every native detection path:

  • kmod (recommended) — kernel-level kretprobe hooks. Filters ioctl (SIOCGIFFLAGS, SIOCGIFNAME, SIOCGIFCONF), getifaddrs/netlink dumps (RTM_GETLINK, RTM_GETADDR), and /proc/net/* reads — all before the syscall returns to userspace. Zero in-process footprint. No library injection. Nothing to detect.
  • zygisk (alternative) — inline-hooks libc.so inside the app process. Same native coverage as kmod but runs in-process, so it's theoretically detectable by advanced anti-tamper. Use this if your kernel isn't supported by kmod.

Layer 3 — Additional app-level controls (integrated into the VPN Hide app):

  • Interface hiding — hide VPN interfaces, routes, and Java API VPN state from selected apps
  • Port hiding — block selected apps from reaching 127.0.0.1 / ::1 so they cannot probe locally bound VPN / proxy daemons
  • App hiding — hide selected apps from selected observer apps at the PackageManager level

The target app's process is completely untouched (with kmod + lsposed) — no Xposed, no inline hooks, no modified memory regions. Because of that, vpnhide works with banking and government apps that actively detect and block Xposed-based modules.

What vpnhide hides

vpnhide is not just one toggle. It combines three different protections that can be enabled per app:

  1. Interface hiding — the core VPN-hiding layer. It removes VPN interfaces and routes from native APIs (ioctl, getifaddrs, /proc/net/*, NetworkInterface) and from Java APIs (NetworkCapabilities, NetworkInfo, LinkProperties).
  2. Port hiding — blocks localhost access for selected apps so they cannot detect Clash, sing-box, V2Ray, Happ, and similar tools by probing local ports.
  3. App hiding — hides selected installed apps from selected observer apps. Useful against package visibility checks, for example when an app tries to determine whether a VPN or proxy client is installed.

Which modules do I need?

You always need the VPN Hide app (vpnhide.apk) plus one native module for interface hiding. The app can also use the optional Ports module if you want localhost port blocking:

  • kmod (recommended) — fully out-of-process, invisible to anti-tamper. Requires a supported GKI kernel.
  • zygisk — use this if your kernel isn't supported by kmod.
  • portshide (optional) — install this if you want to block selected apps from probing localhost ports.

See Install for step-by-step instructions.

Install

Download the latest release from Releases.

Step 1 — VPN Hide app + LSPosed

  1. Install vpnhide.apk as a regular app
  2. In LSPosed manager, enable the VPN Hide module and add "System Framework" to its scope
  3. Reboot (required — LSPosed hooks are injected into system_server at boot, so the module must be active before system_server starts)
  4. Open the VPN Hide app and grant it root access (Magisk will prompt automatically; on KernelSU-Next, grant permission manually in the manager)

Step 2 — Native module for interface hiding

Open the VPN Hide app. The Dashboard tab will detect your device and kernel, and tell you exactly which native module to install:

  • If your kernel is supported, it will recommend a specific kmod file (e.g. vpnhide-kmod-android14-6.1.zip)
  • If not, it will recommend the zygisk module (vpnhide-zygisk.zip)

Install the recommended module:

  • kmod: via KernelSU-Next manager → Modules → Install from storage
  • zygisk: via KernelSU-Next or Magisk manager → Modules

Reboot after installing the native module.

Step 3 — Optional: install the Ports module

If you want localhost port blocking, install vpnhide-ports.zip via KernelSU-Next or Magisk manager.

This module is independent from kmod / zygisk and is only needed for the Ports mode in the app.

Step 4 — Configure protections

Open the VPN Hide app → Protection tab.

  • Tun mode: use the L / K / Z toggles to control interface hiding layers for each app (LSPosed, Kernel module, Zygisk), or tap the row to toggle all layers at once
  • Apps mode: choose which apps should be hidden and which apps should act as observers
  • Ports mode: choose which apps should be blocked from accessing localhost ports

Tap Save after making changes.

After changing targets, force-stop and restart the affected apps — hooks take effect on the next app launch.

Note: some apps detect Zygisk hooks. For those apps, keep Z disabled and rely on kmod + LSPosed.

Shell configuration (advanced)

Edit /data/adb/vpnhide_kmod/targets.txt, /data/adb/vpnhide_zygisk/targets.txt, or /data/adb/vpnhide_lsposed/targets.txt directly (one package name per line). Force-stop and restart affected apps for changes to take effect.

Manual GKI lookup (if you want to pick the kmod file yourself)
  1. On your phone, go to Settings → About phone and find the Kernel version line. It looks something like 6.1.75-android14-11-g...
  2. You need two parts from this string: the kernel version (6.1) and the android generation (android14). Together they form your GKI generation: android14-6.1
  3. Download the matching file from the release: vpnhide-kmod-android14-6.1.zip

Alternatively, run adb shell uname -r to see the kernel version string.

Important: android14 in the kernel string is NOT your Android version — it's the kernel generation. For example, Pixels from 6 to 9a all use the android14-6.1 kernel regardless of whether they run Android 14 or 15.

Screenshots

Dashboard — all OK Dashboard — issues Install recommendation
Protection — Tun App hiding Ports hiding
App hiding help Ports hiding help Diagnostics

Verify

The app has a built-in diagnostics system that catches most setup problems automatically.

Dashboard (runs on every app launch):

  • Module status for all three layers (installed, active, version, target count)
  • LSPosed configuration validation — reads the LSPosed database to verify that VPN Hide is enabled, System Framework is in scope, and no extra apps are scoped (a common misconfiguration)
  • Version mismatch detection — compares installed module versions with the running app version and tells you exactly what to update
  • Native module recommendation — detects your kernel and maps it to the right kmod artifact, or recommends zygisk if unsupported
  • Live protection check (when VPN is active) — runs 16 native checks and 5 Java API checks to verify that VPN is actually hidden

Any issues found are shown as actionable cards with specific instructions.

Diagnostics tab — detailed per-check breakdown with individual PASS/FAIL results for all 26 detection vectors. Useful for troubleshooting when the Dashboard shows partial protection.

Components

Directory What How
kmod/ Kernel module (C) kretprobe hooks in kernel space. Zero footprint in the target app's process. (details)
lsposed/ LSPosed module + app (Kotlin + Rust) Hooks writeToParcel in system_server for per-UID Binder filtering. The APK provides a dashboard (module status, version checks, LSPosed config validation, install recommendations), Protection modes for interface / port / app hiding, and diagnostics. (details)
portshide/ Ports module (Shell + iptables) Blocks selected apps from reaching 127.0.0.1 / ::1, hiding locally bound VPN / proxy daemons from localhost port probes. (details)
zygisk/ Zygisk module (Rust) Inline-hooks libc.so in the target app's process. Alternative to kmod. (details)

Detection coverage

# Detection vector SELinux kmod zygisk lsposed
1 ioctl(SIOCGIFFLAGS) on tun0 x x
2 ioctl(SIOCGIFNAME) resolve index to name x x
3 ioctl(SIOCGIFMTU) MTU fingerprinting x x
4 ioctl(SIOCGIFCONF) interface enumeration x x
5 All other SIOCGIF* (INDEX, HWADDR, ADDR, etc.) x x
6 getifaddrs() (uses netlink internally) x x
7 netlink RTM_GETLINK dump blocked x x
8 netlink RTM_GETADDR dump (IPv4 + IPv6) blocked x
9 netlink RTM_GETROUTE dump blocked
10 /proc/net/route blocked x x
11 /proc/net/ipv6_route blocked x
12 /proc/net/if_inet6 blocked x
13 /proc/net/tcp, tcp6 blocked
14 /proc/net/udp, udp6 blocked
15 /proc/net/dev blocked
16 /proc/net/fib_trie blocked
17 /sys/class/net/tun0/ blocked
18 NetworkCapabilities (hasTransport, NOT_VPN, transportInfo) x
19 NetworkInfo (getType, getTypeName) x
20 ConnectivityManager.getActiveNetwork() x
21 ConnectivityManager.getAllNetworks() + VPN scan x
22 LinkProperties (interfaceName) x
23 LinkProperties (routes via VPN interfaces) x
24 NetworkInterface.getNetworkInterfaces() x x
25 System.getProperty (proxy settings) x
26 /proc/net/route via Java FileInputStream blocked x x

blocked = SELinux denies access for untrusted apps (Android 10+). No hook needed.

Rows 1-6, 21, and 24 are the only vectors reachable by regular apps. Everything else is either blocked by SELinux or goes through Java APIs (covered by lsposed).

Building from source

  • kmod: cd kmod && make && ./build-zip.sh — see kmod/BUILDING.md
  • zygisk: cd zygisk && ./build-zip.sh (Rust + NDK + cargo-ndk)
  • lsposed: cd lsposed && ./gradlew assembleDebug (JDK 17 + Rust + NDK + cargo-ndk)

Verified against

Both implement the official Russian Ministry of Digital Development VPN/proxy detection methodology (source).

Split tunneling

Works correctly with split-tunnel VPN configurations. Only the apps in the target list are affected.

Using split tunneling together with VPN Hide is strongly recommended.

Detection apps that compare the device-reported public IP against external checkers should stay outside the tunnel — their traffic should go through the carrier, not the VPN.

Threat model

vpnhide hides an active VPN from specific apps. It is NOT designed for:

  • Hiding root or custom ROM presence
  • Bypassing Play Integrity
  • Fooling server-side detection (DNS leakage, IP blocklists, latency/TLS fingerprinting)

Known limitations

  • kmod requires a GKI kernel with CONFIG_KPROBES=y (standard on Android 12+ devices)
  • lsposed requires LSPosed, LSPosed-Next, or Vector
  • zygisk is arm64 only
  • Direct svc #0 syscalls bypass zygisk's libc hooks — that's what kmod is for
  • Server-side detection is unfixable client-side — use split tunneling

License

MIT. See LICENSE.

The kernel module declares MODULE_LICENSE("GPL") as required by the Linux kernel to resolve EXPORT_SYMBOL_GPL symbols at runtime.

Star History

Star History Chart