Skip to content

v1.1.9 — Security review sprint 1+2 (P0 + P1 fixes)

Choose a tag to compare

@KOKosaaaa KOKosaaaa released this 02 May 20:35
· 92 commits to main since this release

Closes 6 P0 (critical) and 9 P1 (high) findings from the post-v1.1.8 code-review fix list.

P0 — SSRF / leak surface

  • parseVmess validates the JSON add field. All other URI parsers ran through AddressValidator.requirePublicAddress; VMess (base64-encoded JSON) silently accepted 127.0.0.1 / private / CGNAT addresses. Port range 1..65535 is now enforced too.
  • ProfileEditViewModel.isValidAddress delegates to AddressValidator. Manual save of 127.0.0.1, 192.168.x, 100.64.x, hex/octal IPv4 or IPv4-mapped IPv6 from the Profile Edit screen is now rejected. IPv6 is parsed via InetAddress.getByName("[…]"), replacing the previous accept-anything-with-a-colon path.
  • ServerPreflight re-validates the resolved IP. Closes a DNS-rebinding window where a hostname could resolve to a private IP between profile parsing and TCP probe.
  • autoSelectAndConnect no longer fires N parallel TCP SYNs from the user's real IP. Uses cached lastPingMs if any profile has a fresh value; otherwise probes only 3 random candidates sequentially with an early stop on <200ms. Eliminates the burst-of-SYNs fingerprint that ISP / corp DPI uses to flag VPN clients in the auto-select phase.
  • xray / tun2socks Log.d output is now redacted. Lines pass through LogBuffer.redactPublic BEFORE Log.d, not just inside the in-app Log viewer. ProGuard strips Log.d / Log.v from release builds entirely as defence in depth.
  • QR scan confirmation dialog. No more silent imports — VPN URIs go through a parser + MaterialAlertDialog; HTTPS subscriptions show a confirmation with the host preview; HTTP and unrecognised QR content are rejected with a Toast. Mirrors the existing deep-link flow.

P1 — high-priority hardening

  • LogBuffer.redact rewritten. Covers password/passwd/pwd/pass/secret/api_key/token/bearer/authorization (case-insensitive, both = and :, optional quotes), JSON form "key":"value", Reality pbk/sid long 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). ArrayDeque.removeFirst() instead of ArrayList.removeAt(0) (O(1) vs O(n)). Real CONFLATED-channel debounce instead of the previous broken throttle flag.
  • SubscriptionRepository.validateUrl is now public. Called BEFORE DB insert in SubscriptionViewModel.addSubscription and SettingsViewModel.importConfig. Junk subscription URLs no longer persist and are not retried by the periodic SubscriptionUpdateWorker. Subscription name capped at 256 chars (mirrors ProfileParser.MAX_NAME_LENGTH).
  • ProfileEditViewModel.testConnection refuses without VPN. 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 to the VPN server.
  • HomeFragment skips GeoLookup.fetchUserLocation() when tunnel is down. Was leaking real IP to ipwho.is 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 a RecyclerView where any screenshot leaks it.
  • SettingsFragment.isValidDns delegates to AddressValidator. Covers CGNAT, IPv6, hex/octal IPv4, IPv4-mapped IPv6 — all of which the previous regex missed.
  • New self-test: "Own SOCKS5 auth". Connects to our own ephemeral SOCKS port and asserts that the no-auth handshake is rejected. Catches future regressions in XrayConfigGenerator.buildInbounds automatically.
  • ServiceTester refuses without VPN. Returns uniform error results instead of testing youtube.com / openai.com / instagram.com / discord.com from the user's real IP. buildClient() returns null if credentials aren't ready — no direct-connection fallback path remains.
  • Tapjacking on onboarding. All interactive elements (Back / Next / Grant VPN / Grant Usage Stats / Paste / Import / Skip) carry android:filterTouchesWhenObscured="true". Onboarding is the highest-stakes UI flow and was the only place the existing tapjacking-protection claim didn't apply.

Install

Sideload NetGuard-v1.1.9.apk (arm64-v8a, 27 MB). Upgrade in place from any 1.1.x — settings and profiles preserved.