* feat: upgrade diagnostic captures
Collect richer debug and full-logcat bundles with device, backend, hook, portshide, and kernel partition evidence.
Add an explicit kernel image export for maintainer-requested kernel analysis.
* fix: harden diagnostic capture sessions
* fix(lsposed): harden diagnostic export error paths from review
Address correctness findings from a review of the diagnostic-capture upgrade,
all validated on a Pixel 4a via the agent bridge (kernel/debug/logcat exports).
- Kernel image export: wrap exportKernelImagesZip so a zip/IO failure returns
null (and cleans the parts dir) instead of escaping and stranding the export
button disabled; add try/finally at the DiagnosticsScreen call site. Cap the
per-partition dd (skip unknown/oversized partitions, bound with count=) so a
mislabelled or huge partition can't fill /data.
- HookDiagnostics: drive counter deltas off an explicit "baseline captured"
flag instead of baselineCounters.isEmpty(), so a fresh boot with an empty
baseline no longer reports every delta as n/a. Add HookDiagnosticsTest.
- DebugShellSnapshot: raise the 20s snapshot timeout to 60s and preserve+flag a
timeout-truncated section instead of silently dropping it and every later one.
- LogcatRecorder: delete the intermediate raw logcat file after zipping and on
failed start, so repeated captures stop leaking multi-MB files into cache.
- DebugExport: rethrow CancellationException before the generic catch so
navigating away mid-capture cancels cleanly instead of logging a false error.
- DebugCaptureLogging: when the on-disk canonical config is present but
unparseable, refuse the targets-snapshot rebuild (which would reset settings
and clear autoHiddenPackages); fall back to an app-process-only toggle.
- Dashboard: don't warn "ports rules not active" for a module the user disabled
via their manager (new ports_disabled probe, mirrors the activator's guard).
- activator write_atomic: per-process temp name so a boot-time and app-triggered
activation can't corrupt each other's load_status.
- Drop the wasted multi-user pm enumeration from the counter baseline command.
* docs: refresh installation and backend docs
* docs: fix review findings and add KPM to update-json
Cross-checked the docs refresh against the code and corrected:
- protocol.md: config parser is vpnhide_parse_config (not the legacy
vpnhide_parse_target_uids); path is kmod/shared/vpnhide_logic.h; drop the
unverified 0x2026 KPatch-Next marker (activator drives it via the CLI).
- zygisk/README: module declares Zygisk API v2 (not v5); add the recv hook
row and the if<N> renamed-netdev match.
- README (en/ru): live-check counts are 13 native / 12 Java; Diagnostics is
under Settings, not a tab; mention the Statistics tab; note KPM beta parity.
- detection-vectors: KPM host-route helper is kpm_is_public_host_route{4,6};
restore CONFIG_KPROBES and the Zygisk qualifiers; codegen renders four targets.
- storage.md: legacy files are no longer user-managed (migration inputs), not
'gone'. CLAUDE.md: activator has four bins (kmod/kpm/zygisk/ports).
- development/ROADMAP/kmod/lsposed/portshide READMEs + AGENTS.md: assorted
accuracy fixes (KPM needs a KernelPatch runtime, per-KMI kmod zip name, etc.).
- CONTRIBUTING.md: clang-format uses scripts/clang-format-c.sh (covers KPM src).
- portshide customize.sh: 'Hiding -> Ports' (renamed tab).
- update-json.sh: emit update-kpm.json so KPM gets Magisk/KSU update checks.
* docs: correct diagnostics check count (25, not 26 vectors)
* docs: refresh README screenshots for the 1.0.0 UI
Replace the v0.5.3-era screenshots (old per-tab Protection UI) with a
new 1.0.0 set: VPN-hidden dashboard, the unified Hiding list with
J/N/A/P, the Statistics tab + per-hook breakdown, per-app hook selection,
diagnostics, settings, and the community screen. Extra shots
(statistics-apps, settings-advanced, diagnostics-java, settings-developer)
are included for the release notes.
* docs: fix the how-it-works layer breakdown
The old 'Layer 3 — additional app-level controls' bucket was wrong:
interface hiding is what Layers 1+2 already do, app-hiding is done by the
LSPosed/Java layer (system_server PackageManager hooks), and ports is a
separate module. Fold app-hiding into Layer 1 (LSPosed) and make Layer 3
the standalone portshide module.
* docs: conceptual-coherence pass on the 5 core docs
Second review pass, this time for architectural coherence rather than
code-facts. Fixes:
- README (en/ru): 'What vpnhide hides' stopped reifying interface hiding
as a standalone 'layer'/'role' and now ties the three hiding goals to
the four J/N/A/P roles (interface = Java + Native, toggled independently)
- README (ru): add the KPM-beta hook-parity caveat (parity with the EN side)
- protocol.md: app-hiding is a normal lsposed mask bit, not a separate
'package/observer records' entry in the §6 profile table
- detection-vectors.md: a complete install is two components (native + lsposed),
not 'two roles'; name both lsposed jobs
- storage.md: scope the text protocol to the config wire (LSPosed reuses the
format for its state file); 'by function' not 'by role'; note the Apps role
shares the same resolver; heading no longer files ports under 'Native layer'
* feat(kpm): extend the public host-route concealment to the legacy kernels
I had disabled the host-route check on the non-GKI kernels (4.14/4.19/5.4) on the
mistaken belief they weren't QEMU-tested — but kpm-qemu-legacy covers exactly
those, and they ARE the kernels the KPM exists to serve. Enable it properly:
- IPv4 now works on every supported kernel. On <5.6 (5.4/4.19/4.14) fib_dump_info
passes the __be32 dst and dst_len as args 6/7 instead of a fib_rt_info*, so
kpm_is_public_host_route4 reads them from the right place per ABI. (The __be32
value in the arg register stores to network-order bytes, so the same
vpnhide_is_public_ipv4 applies.)
- IPv6 now works on 5.4 and 4.19 too: their fib6_info head matches 5.10, so
fib6_dst is at +64 (derived from the AOSP/vanilla sources, QEMU-validated on
the legacy images). 4.14 stays disabled — it uses rt6_info, whose
cacheline-aligned rt6i_dst has no hand-derivable offset.
- Harness: test hostroute4 on every kernel and hostroute6 on every kernel except
4.14 (init-kpm.sh gate by uname -r).
Validated A/B in QEMU locally on 5.4, 4.19, 4.14 (legacy images, cortex-a57) and
re-checked 5.10 GKI for no regression on the refactored 5.6+ path.
* feat(kpm): also hide the public IPv6 host-route on 4.14 (rt6_info path)
Completes host-route coverage across every supported kernel. 4.14 predates
fib6_info — rt6_fill_node takes a struct rt6_info*, and the route's destination
is rt6_info.rt6i_dst (a rt6key), not fib6_info.fib6_dst. Add a per-kver rt6_dst
offset and read the rt6key there on the rt6_via_dst path.
The offset is derivable, not guessed: struct dst_entry is 160 bytes (its __pads
keep the size constant regardless of CONFIG_XFRM/CLASSID), the rt6_info prefix
runs to +220, and rt6i_dst is ____cacheline_aligned_in_smp, so it rounds up to
+256 (same for a 64- or 128-byte cacheline). QEMU-validated A/B on the legacy
4.14 image (hostroute6 now hidden for the target, 18/18, no panic), and the
harness host-route gate is dropped — every KPM kver now covers v4 + v6.
* docs(roadmap): record the 32-bit compat SIOCGIFCONF gap as low-priority
* docs(changelog): note the legacy-kernel KPM host-route coverage
Add a dashboard warning when KPM is installed under APatch but dormant
because no superkey is saved (runtime=apatch, detail=awaiting_superkey for
the current boot) — it previously read as inactive with no reason. Demote
the no-targets message from error to info, since a fresh, not-yet-configured
install isn't broken. Drop the drifted I1/I2/W2/W4/W5 comment prefixes (the
err/warn/info call already conveys severity) and note the open permissive-
SELinux severity/coverage questions in the roadmap.
Zygisk hooks run inside each target app's own sandbox and can't export counters
anywhere VPN Hide can read without either leaving a self-detectable artifact in
the target's sandbox or adding a world-writable + sepolicy surface — both
degrade stealth for the least-preferred, soon-to-be-retired native backend. KPM
covers the same vectors invisibly and already reports counters, so that's the
path for users who want native statistics.
Reshape the Statistics screen from per-backend × per-(uid,hook) counter rows
into a per-app view: each app that has triggered a hook is one card showing its
total probe count and a breakdown by detection method. The 25 raw hooks fold
into a small user-facing taxonomy (routes, interfaces, ioctl, policy rules,
NetworkCapabilities, LinkProperties, NetworkInfo, network handle,
ConnectivityService, package enumeration) tagged by surface (Java API / native /
packages). Counts are aggregated across every active backend (Java + the one
installed native backend, including Zygisk's libc hooks).
Scoped to target apps only — the apps the hooks already count. Monitoring of
non-target apps to discover new probers is deferred (docs/ROADMAP.md).
- ProbeStats.kt: DetectionMethod / MethodSurface taxonomy + buildAppProbeStats
aggregator (pure, unit-tested).
- StatisticsScreen: per-app cards with method chips; hero shows probes / apps /
methods / active backends.
- Drop the now-unused per-row strings; EN + RU method/surface labels.
Consolidate "which app probe is hidden by which component" into a single
authoritative doc, and correct the README coverage table, which marked the
netlink path as SELinux-blocked and predated the issue #86 fix.
- Add docs/detection-vectors.md: threat model, the kmod/zygisk/lsposed/SELinux
layers, a per-vector coverage matrix (interface enum, routes, socket procfs,
framework Java APIs, package visibility), importance ranking, known gaps, and
where each layer is defined in code
- Fix README.md / README.en.md table: netlink RTM_GETLINK/GETADDR/GETROUTE are
not SELinux-restricted; RTM_GETROUTE is now covered by kmod and zygisk;
RTM_GETADDR/ipv6_route/tcp coverage corrected; rewrite the conclusion to
explain the netlink path and per-device SELinux variance
- Note that SELinux policy differs across devices, ROMs, and Android versions,
so the layers never rely on it
- Cross-link the new doc from CLAUDE.md, ROADMAP.md, and state.md
Two refinements on top of the netlink route-dump hooks:
1. Drop the rt_fill_info hook. It is a static, directly-called function,
so the compiler may ignore AAPCS64 and place its args in arbitrary
registers. Verified in QEMU on a no-LTO android12-5.10 build: regs[3]
held table_id (254), not the struct rtable* the source signature
places there — a fixed regs[N] read is build-dependent guesswork that
differs between LTO device builds and no-LTO builds. The vector is also
low value: IPv4 route enumeration (RTM_GETROUTE + NLM_F_DUMP, what
detection apps use) goes through the global, ABI-stable fib_dump_info
hook; single lookups respect the caller's routing, which is physical
under the recommended split-tunnel setup. ROADMAP documents the
rtnl_unicast-based alternative if single-lookup hiding is ever wanted.
2. Extend rt6_fill_node with an IPv6 analogue of the physical host-route
hint: a public /128 route pinned to a physical interface leaks the VPN
server's IPv6 even when the tun interface is hidden.
Verified: all 7 GKI KMIs (5.10..6.12) compile; route hooks filter VPN
routes for target UIDs on no-LTO 5.10 in QEMU and on a Pixel 8 Pro
(android14-6.1), no panics.