vpnhide: hiding an active Android VPN from selected apps
Hide active VPN from selected Android apps (kernel module + LSPosed + Zygisk)
At a glance
- What is it?
- vpnhide is a multi-layer Android project that hides a running VPN from chosen applications by filtering Android APIs in system_server, hooking kernel interfaces for native code, and optionally blocking localhost ports with iptables. The design goal is to leave the target app's process untouched, since Xposed injection is what anti-tamper checks detect.
- Who is it for?
- vpnhide suits a rooted Android user who needs specific apps, including apps with native code or cross-platform frameworks, to stop seeing a VPN interface, and who can accept installing a kernel module or a KernelPatch runtime. It does not suit an unrooted device, and it does not suit someone who expects the Zygisk fallback to be invisible, since the project states that its inline hooks run inside the app process and can be detected by banking and anti-fraud apps.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The goal is an untouched target process
vpnhide hides an active VPN connection on Android from selected applications. The project is built from three cooperating parts, and the repository layout shows them as separate directories: `lsposed/` for the Java layer, `kmod/`, `zygisk/` and `builtin/` for the native layer, and `portshide/` for the port blocking module, alongside the Kotlin app under `crates/` and the Rust workspace that ties them together.
The design constraint that shapes everything else is stated in the README: with LSPosed plus a kernel level native backend, the target application's process is completely untouched. No Xposed framework, no inline hooks, no modified memory regions. The project presents this as the reason it works with banking and government applications that actively detect and block Xposed based modules.
That is a specific and unusual claim, and it is worth being precise about what it rests on. It rests on moving the interception point out of the application and into the layers below it. The Java layer hooks `system_server` rather than the app, so the filtering happens on the Binder boundary. The native layer hooks kernel interfaces, so the app's own syscalls are answered with filtered data by the kernel. The port layer is separate again, because a proxy listening on localhost is a different kind of evidence.
The repository also carries the usual project infrastructure: `docs/`, `data/`, `testdata/`, `fixtures/`, `tools/`, `update-json/`, `changelog.d/`, an `AGENTS.md`, a `CHANGELOG.md` and a `CONTRIBUTING.md`, which together suggest a project with a structured release and translation workflow rather than a single script.
Why the in-process Xposed approach breaks on banking apps
The README names two existing modules, NoVPNDetect and NoVPNDetect Enhanced, and explains why they are not enough. The critique has two parts, and both are specific rather than general.
The first problem is anti-tamper detection. Those modules hook inside the target application's process through Xposed, and any app that checks for memory injection will detect the hooks and refuse to run. The README quotes the author of NoVPNDetect Enhanced, who writes that the module will not work if the connected application has protection against LSPosed or a memory injection check, giving banking apps as examples.
The second problem is the absence of native coverage. An application that uses C or C++ code, a cross-platform framework such as Flutter or React Native, or direct system calls can detect a VPN through `ioctl`, `getifaddrs`, netlink sockets and `/proc/net/*`. A hook installed in the Java layer of the same app never sees those calls, because they never pass through the Java API.
So the alternative approaches the project is arguing with are the obvious ones, and the difference in approach is about where the interception happens rather than about how much it can hide. In-process hooking is precise but visible. Filtering below the app is invisible but has to cover more ground, which is why this project needs a kernel component at all.
Level one filters the Java APIs in system_server
The first layer is an LSPosed module, and its key move is to hook `system_server` instead of the application. `NetworkCapabilities`, `NetworkInfo` and `LinkProperties` are filtered at the Binder level, before the data reaches the application process. The application then receives clean data over IPC, and since nothing was injected into its process, there is nothing there to detect.
The same module does a second job. It hides selected applications from observer applications at the `PackageManager` level, which is the mechanism behind the role the project calls Apps. That is aimed at package visibility checks, the case where one app tries to work out whether a VPN client or a proxy client is installed on the device.
The scope requirement matters for installation. The documented first step is to install `vpnhide.apk`, enable the VPN Hide module in LSPosed, add System Framework to the module's scope, and reboot. Adding System Framework to the scope is what makes the `system_server` hook possible, and it is also a broad grant, since it lets the module sit in the part of the system that serves every app.
For a reader deciding whether this layer alone is enough: the README is clear that the Java role covers the Java API only, and that an app which looks at the network through native code will still see the interface. That is exactly the gap the native layer exists to close.
Level two: kernel probes, KernelPatch modules, and a Zygisk fallback
The native layer covers what the Java layer cannot, and it comes in three mutually exclusive forms. Exactly one native backend must be active, and the choice is determined by the device.
The kmod backend is the recommended one for supported GKI kernels. It uses `kprobe` and `kretprobe` hooks at kernel level. It filters `ioctl`, filters `getifaddrs` and netlink dumps of interfaces, addresses, routes and policy rules, and rejects `SO_BINDTODEVICE` and `SO_BINDTOIFINDEX` for hidden interfaces before the socket state changes. The project describes the result as zero trace in the application process.
KPM is the KernelPatch Module variant. It has the same eleven logical kernel hooks but is not tied to a GKI specific `.ko`, which makes it useful for older or non-GKI kernels, with 4.9, 4.14, 4.19 and 5.4 named, and for situations where the `.ko` will not load. It needs a KernelPatch runtime, either APatch or KPatch-Next-Module.
Zygisk is the fallback when no kernel level backend is possible. It uses inline hooks of `libc.so` with best-effort filtering of `setsockopt`, and it runs inside the application's own process. The project is explicit about the consequence: direct system calls bypass those hooks, so banking and anti-fraud applications may detect them, and for such applications the advice is to leave Native turned off and rely on the Java level.
That last point is the honest boundary of the whole project, and it is stated by the project itself rather than being a weakness discovered later.
Four roles per application: J, N, A and P
Everything is configured per application through four roles, and the README is unusually clear about which role does what.
Interface hiding is the main task. It removes VPN interfaces and routes from the native APIs, that is `ioctl`, `getifaddrs`, netlink, `/proc/net/*` and `NetworkInterface`, it prevents a socket from being bound to a hidden interface, and it clears the Java APIs `NetworkCapabilities`, `NetworkInfo` and `LinkProperties`. It needs two roles at once, Java and Native, and they are enabled independently of each other.
Port hiding blocks access to localhost for the chosen applications, so that they cannot detect a locally running VPN or proxy daemon by looking for an open port. The project names Clash, sing-box, V2Ray and Happ as the kind of tools this is aimed at, and this is the role the separate `portshide` Magisk module provides, using iptables to block `127.0.0.1` and `::1` for the selected apps.
App hiding goes the other way and hides selected installed applications from chosen observer applications, which is useful against package visibility checks that ask whether a VPN or proxy client is present on the device.
So the four roles are Java, Native, Apps and Ports, and the useful configuration is per application rather than global. A user with a banking app that checks ports and a messenger that does not can set different roles for each, and the README explicitly points at the Zygisk caveat when deciding whether to enable the native role for a given app.
One native backend, chosen by kernel version and by priority
The installation section is where the practical constraints sit. The app itself walks a first-time user through the steps on an Overview tab, and the full instructions live in a built-in guide, with the Russian version of the first-install document in the repository at `docs/help/ru/first-install.md`.
The app is always required, together with LSPosed or Vector for the Java level, and exactly one native backend. The kmod path is described as the stable default, fully out of process and invisible to anti-tamper checks, and it needs a supported GKI kernel: 5.10, 5.15, 6.1, 6.6 or 6.12. The KPM path covers 4.9, 4.14, 4.19 and 5.4 and other cases where the `.ko` does not fit, and needs APatch or KPatch-Next-Module. Zygisk is the option when neither is available or when you do not want a KernelPatch runtime. The portshide module is optional and only needed if you want localhost port blocking.
The project warns against installing several native backends at once. If they are installed anyway, the app selects the active one by priority, kmod first, then the built-in kernel one, then KPM, then Zygisk, and the documentation suggests removing the rest. That ordering is worth understanding rather than relying on, because a leftover Zygisk module that is not the active backend is still a module loaded into the process of every app.
The releases move quickly. Version 1.3.1 is dated 2026-10-12, v1.3.0 on 2026-09-17 and v1.2.5 on 2026-08-20, and the last push was on 2026-10-20. The repository is not archived. For a tool that patches kernel interfaces, that pace is a reason to read the changelog between versions rather than to update on reflex.
Licence wording, build profiles and what the workspace contains
Two administrative details deserve attention before anyone builds on this.
First, the licence needs checking in the file rather than in the badge. The README carries an MIT badge, and the Rust workspace metadata records `license = "MIT"`, but the repository is listed with a custom licence that GitHub cannot classify, with the note that the LICENSE file is the place to look. Anyone distributing a build, or incorporating the kernel module into a larger project, should read that file rather than rely on a badge.
Second, the workspace tells you how the native parts are built. It is a Cargo workspace with resolver version 3, edition 2024, and a minimum Rust version of 1.85. The members list is the clearest map of the project:
[workspace]
resolver = "3"
members = [
"crates/app-helper",
"crates/apatch-abi",
"crates/activator",
"crates/checks",
"crates/checks-jni",
"crates/protocol",
"crates/protocol-diff",
"zygisk",
]The member names describe the architecture. `checks` and `checks-jni` are the detection and probe layer, `protocol` and `protocol-diff` handle the communication between the app and the injected components, `activator` and `apatch-abi` deal with loading a component into the right context, and `app-helper` supports the app side. The Zygisk member is listed alongside them because the fallback backend is a Rust crate too.
The release profile is tuned for size and speed with `lto = "fat"`, `codegen-units = 1` and `panic = "abort"`, and there is a separate `app-native` profile that inherits from release but keeps `panic = "unwind"`, because Android app native artifacts must unwind across the thin JNI adapter so a failed probe can be turned into a Java exception instead of killing the app process. That single comment in the manifest explains a lot about how careful the native code is.
The Python tooling follows a stated policy of standard library only scripts, with ruff used purely as a development and CI tool:
[tool.ruff]
required-version = "==0.16.3"
target-version = "py312" # CI image (Ubuntu 24.04) ships Python 3.12
line-length = 100Editorial conclusion
vpnhide suits a rooted Android user who needs specific apps, including apps with native code or cross-platform frameworks, to stop seeing a VPN interface, and who can accept installing a kernel module or a KernelPatch runtime. It does not suit an unrooted device, and it does not suit someone who expects the Zygisk fallback to be invisible, since the project states that its inline hooks run inside the app process and can be detected by banking and anti-fraud apps. Before installing, check your kernel against the supported GKI list, read the LICENSE file because the recorded licence is a custom one, and install exactly one native backend, since the project says the app picks the active one by priority.
Frequently asked questions
What does vpnhide actually hide from other apps?
Three things, each configured per app through the roles Java, Native, Apps and Ports: VPN interfaces and routes removed from the native and Java network APIs, localhost access blocked so a local proxy daemon cannot be found by its port, and selected installed applications hidden from observer apps at the PackageManager level.
Which VPN Hide module do I need for my Android device?
The app's Overview tab detects the kernel and names the file. A supported GKI kernel of 5.10, 5.15, 6.1, 6.6 or 6.12 uses the kmod backend, older or non-GKI kernels such as 4.9, 4.14, 4.19 and 5.4 use the KPM backend with APatch or KPatch-Next-Module, and Zygisk is the fallback when no kernel level backend is possible.
Why does vpnhide need LSPosed if it uses a kernel module?
The LSPosed module handles the Java layer by hooking system_server and filtering NetworkCapabilities, NetworkInfo and LinkProperties at the Binder level, and it also hides apps at the PackageManager level. The kernel backend covers native code paths that never pass through the Java API.
Can banking apps still detect vpnhide?
With LSPosed and a kernel level native backend the target process is untouched, which the project presents as the reason it works with banking and government apps. With the Zygisk fallback, inline hooks run inside the app process and can be bypassed by direct system calls, so the project advises leaving Native off for such apps and relying on the Java level.
What licence is vpnhide released under?
The README shows an MIT badge and the Rust workspace records `license = "MIT"`, but the repository is listed with a custom licence that GitHub cannot classify, and the LICENSE file is the authoritative place to check before redistributing a build.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/okhsunrog-vpnhide)Was this useful?