[GH-ISSUE #2457] [Android] [Critical] Per-app split tunneling bypass for AmneziaWG 2.0 allows blacklisted apps to see the ip address of the VPN. #1093

Open
opened 2026-10-13 20:34:06 +00:00 by bsakaguchi · 19 comments
Owner

Originally created by @R4L34 on GitHub (Apr 9, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2457

Summary
It was mentioned in the previous commit but the person responsible for fixing it did it only for XRay so I feel obligated to mention it again. You can get the ip of the VPN server by explicitly going through the tun0 interface. Like this (in Termux). curl -s https://ifconfig.me/ip --interface tun0
That will allow any spyware (which now includes every russian big-tech app) to not only get the fact that you use a VPN but also get the ip address of the server. Then they can log it, and later block it.

Environment

  • App version: 4.8.14.5
  • Android version: 14
  • Device: Samsung Galaxy A33 5G
  • Protocol used: AmneziaWG 2.0
  • Split tunneling: Enabled (Termux excluded from VPN)

To Reproduce
Steps to reproduce the behavior:

  1. Install AmneziaVPN and configure a working connection.
  2. Enable Per-app VPN (split tunneling) and exclude Termux (or any test app with INTERNET permission).
  3. Connect to the VPN.
  4. In Termux, run:
#Send traffic through the tun0 interface
curl -s https://ifconfig.me/ip --interface tun0
# Observed: VPN exit IP`

Expected behavior
Applications excluded from VPN routing should not be able to send traffic through the VPN tunnel via a localhost proxy, unless this behavior is explicitly documented and user-controlled. If a local listener is required for internal purposes, access should be restricted to the app's own components.

Security Impact
If the proxy accepts unauthenticated connections and forwards traffic through the VPN tunnel:

  • User-configured per-app routing policies can be bypassed by any app with INTERNET permission.

  • Traffic that the user explicitly intended to route directly may leak through the VPN (or vice versa).

  • The presence of an open, unauthenticated localhost proxy may serve as a detectable signal of VPN usage to monitoring applications.

Operational relevance
In environments where applications or platforms monitor network behavior for signs of VPN usage, this issue may increase the detectability of VPN use. Because an app excluded from VPN routing can still access the VPN tunnel via localhost, it can both infer that the VPN is active and send probe traffic through the tunnel. This may also aid enumeration of VPN egress IPs and contribute to blocking of VPN endpoints. This consideration is particularly relevant given recent reports of app-level VPN detection guidelines being distributed to IT platforms [1][2].

Suggested Mitigations
The same that solved SOCKS5 leak (will be solved if someone merges this pull request).

  • Require authentication (username/password) for the tun0 interface, if the feature is intended for external use.
  • If there is any alternative way to get the VPN ip address without being in the split-tunneling whitelist - fix it.

I repeat that this vulnerability while not as critical poses a significant threat to the VPN's hosted on private servers which were mostly immune to the direct ip blocking from Russian censors.

Originally created by @R4L34 on GitHub (Apr 9, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2457 **Summary** It was mentioned in the previous commit but the person responsible for fixing it did it only for XRay so I feel obligated to mention it again. You can get the ip of the VPN server by explicitly going through the tun0 interface. Like this (in Termux). `curl -s https://ifconfig.me/ip --interface tun0` That will allow any spyware (which now includes every russian big-tech app) to not only get the fact that you use a VPN but also get the ip address of the server. Then they can log it, and later block it. **Environment** - App version: 4.8.14.5 - Android version: 14 - Device: Samsung Galaxy A33 5G - Protocol used: AmneziaWG 2.0 - Split tunneling: Enabled (Termux excluded from VPN) **To Reproduce** Steps to reproduce the behavior: 1. Install AmneziaVPN and configure a working connection. 2. Enable Per-app VPN (split tunneling) and exclude Termux (or any test app with INTERNET permission). 3. Connect to the VPN. 4. In Termux, run: ``` #Send traffic through the tun0 interface curl -s https://ifconfig.me/ip --interface tun0 # Observed: VPN exit IP` ``` **Expected behavior** Applications excluded from VPN routing should not be able to send traffic through the VPN tunnel via a localhost proxy, unless this behavior is explicitly documented and user-controlled. If a local listener is required for internal purposes, access should be restricted to the app's own components. **Security Impact** If the proxy accepts unauthenticated connections and forwards traffic through the VPN tunnel: - User-configured per-app routing policies can be bypassed by any app with INTERNET permission. - Traffic that the user explicitly intended to route directly may leak through the VPN (or vice versa). - The presence of an open, unauthenticated localhost proxy may serve as a detectable signal of VPN usage to monitoring applications. **Operational relevance** In environments where applications or platforms monitor network behavior for signs of VPN usage, this issue may increase the detectability of VPN use. Because an app excluded from VPN routing can still access the VPN tunnel via localhost, it can both infer that the VPN is active and send probe traffic through the tunnel. This may also aid enumeration of VPN egress IPs and contribute to blocking of VPN endpoints. This consideration is particularly relevant given recent reports of app-level VPN detection guidelines being distributed to IT platforms [1][2]. **Suggested Mitigations** The same that solved SOCKS5 leak (will be solved if someone merges [this](https://github.com/amnezia-vpn/amnezia-client/pull/2453) pull request). - Require authentication (username/password) for the tun0 interface, if the feature is intended for external use. - If there is any alternative way to get the VPN ip address without being in the split-tunneling whitelist - fix it. I repeat that this vulnerability while not as critical poses a significant threat to the VPN's hosted on private servers which were mostly immune to the direct ip blocking from Russian censors.
Author
Owner

@theagrik commented on GitHub (Apr 9, 2026):

couldn't reproduce on POCO M4 Pro 5G, Android 13, app version 4.8.14.5 and with Termux excluded. tested on AmneziaWG Legacy, AmneziaWG 2.0 and plain WireGuard without obfuscation.

<!-- gh-comment-id:4212625663 --> @theagrik commented on GitHub (Apr 9, 2026): couldn't reproduce on POCO M4 Pro 5G, Android 13, app version 4.8.14.5 and with Termux excluded. tested on AmneziaWG Legacy, AmneziaWG 2.0 and plain WireGuard without obfuscation.
Author
Owner

@uranik777 commented on GitHub (Apr 9, 2026):

I confirm the issue. I'm using a vivo x100 pro (Android 16) The "curl -s https://ifconfig.me/ip --interface tun0" command displays the VPN server's external IP in Termux app. Even though the Termux app is not included to split app tunneling whitelist.

<!-- gh-comment-id:4216108740 --> @uranik777 commented on GitHub (Apr 9, 2026): I confirm the issue. I'm using a vivo x100 pro (Android 16) The "curl -s https://ifconfig.me/ip --interface tun0" command displays the VPN server's external IP in Termux app. Even though the Termux app is not included to split app tunneling whitelist.
Author
Owner

@JinEnMok commented on GitHub (Apr 9, 2026):

Can confirm while running 4.8.14.5 on GrapheneOS with self-hosted VPN with split tunneling (allowed apps only), both AWGv2 and XRay. Recently a few apps (https://github.com/xtclovver/RKNHardering and https://github.com/loop-uh/yourvpndead) have been published that analyse the network settings and connections to simulate the kind of check the Russian authorities can perform (provided they manage to install something on your phone), and Amnezia VPN fails in at least 3 ways: exposing itself as a VPN app (can't get around that, I guess), exposing a tun interface to all apps, and exposing the socks proxy when running XRay. Needless to say, if it was a actual malevolent app running those checks, vpn users are done (and who's to say any sort of Russian app, be it a bank or a marketplace, isn't running those already). This is also tied to https://github.com/amnezia-vpn/amnezia-client/issues/2452

<!-- gh-comment-id:4216289807 --> @JinEnMok commented on GitHub (Apr 9, 2026): Can confirm while running 4.8.14.5 on GrapheneOS with self-hosted VPN with split tunneling (allowed apps only), both AWGv2 and XRay. Recently a few apps (https://github.com/xtclovver/RKNHardering and https://github.com/loop-uh/yourvpndead) have been published that analyse the network settings and connections to simulate the kind of check the Russian authorities can perform (provided they manage to install something on your phone), and Amnezia VPN fails in at least 3 ways: exposing itself as a VPN app (can't get around that, I guess), exposing a tun interface to all apps, and exposing the socks proxy when running XRay. Needless to say, if it was a actual malevolent app running those checks, vpn users are done (and who's to say any sort of Russian app, be it a bank or a marketplace, isn't running those already). This is also tied to https://github.com/amnezia-vpn/amnezia-client/issues/2452
Author
Owner

@SY573M404 commented on GitHub (Apr 10, 2026):

I believe there's no way to fix that. There's no such thing as "tun0 authentication" as it is just an network interface. Network interfaces don't do authentication.

The issue is with how Android manages tun0 permissions and we can (unfortunately) do nothing about it.

UPD: Oh wait, it is possible - the way SingBox does it while routing. Every Android app runs with its own (static per installation) UID and SingBox finds connection owner UID by matching source + destination

Source link

<!-- gh-comment-id:4226894283 --> @SY573M404 commented on GitHub (Apr 10, 2026): I believe there's _no_ way to fix that. There's no such thing as "tun0 authentication" as it is just an network interface. Network interfaces don't do authentication. The issue is with how Android manages tun0 permissions and we can (unfortunately) do nothing about it. UPD: Oh wait, it _is_ possible - the way SingBox does it while routing. Every Android app runs with its own (static per installation) UID and SingBox finds connection owner UID by matching source + destination [Source link](https://github.com/SagerNet/sing-box/blob/e6427e824412a919368e0af5d985ae85f0f845cd/experimental/libbox/internal/procfs/procfs.go#L33)
Author
Owner

@Gerk17 commented on GitHub (Apr 10, 2026):

Just to clarify — PR #2453 only fixes the SOCKS5 inbound bypass for Xray profiles (issue #2452). It does not and cannot fix what you describe here. The two issues look similar but operate at very different layers:
#2452 was a userspace SOCKS5 listener with no auth — fixable by enabling the auth field that the protocol already supports.
#2457 is the kernel tun0 interface being directly addressable by any process via SO_BINDTODEVICE / --interface tun0. This is a known limitation of Android's VpnService API: split tunneling there controls default routing per UID, not interface visibility. There is no "authentication" for a kernel network interface — the concept doesn't exist at that layer.
Real mitigations require either root (to install per-UID iptables -m owner -o tun0 -j REJECT rules), system privileges (eBPF cgroup filters), or a different OS architecture entirely. WireGuard for Android, Mullvad and other major Android VPN clients have the same limitation, and as far as I know none of them solve it on non-rooted devices.
The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device.

<!-- gh-comment-id:4227069706 --> @Gerk17 commented on GitHub (Apr 10, 2026): Just to clarify — PR #2453 only fixes the SOCKS5 inbound bypass for Xray profiles (issue #2452). It does not and cannot fix what you describe here. The two issues look similar but operate at very different layers: #2452 was a userspace SOCKS5 listener with no auth — fixable by enabling the auth field that the protocol already supports. #2457 is the kernel tun0 interface being directly addressable by any process via SO_BINDTODEVICE / --interface tun0. This is a known limitation of Android's VpnService API: split tunneling there controls default routing per UID, not interface visibility. There is no "authentication" for a kernel network interface — the concept doesn't exist at that layer. Real mitigations require either root (to install per-UID iptables -m owner -o tun0 -j REJECT rules), system privileges (eBPF cgroup filters), or a different OS architecture entirely. WireGuard for Android, Mullvad and other major Android VPN clients have the same limitation, and as far as I know none of them solve it on non-rooted devices. The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device.
Author
Owner

@SY573M404 commented on GitHub (Apr 11, 2026):

other major Android VPN clients have the same limitation

Well, it turned out NekoBox/SingBox actually solved the problem. But implementation is rather gimmicky

<!-- gh-comment-id:4227867442 --> @SY573M404 commented on GitHub (Apr 11, 2026): > other major Android VPN clients have the same limitation Well, it turned out NekoBox/SingBox actually solved the problem. But implementation is rather gimmicky
Author
Owner

@R4L34 commented on GitHub (Apr 11, 2026):

I believe there's no way to fix that. There's no such thing as "tun0 authentication" as it is just an network interface. Network interfaces don't do authentication.

The issue is with how Android manages tun0 permissions and we can (unfortunately) do nothing about it.

UPD: Oh wait, it is possible - the way SingBox does it while routing. Every Android app runs with its own (static per installation) UID and SingBox finds connection owner UID by matching source + destination

Source link

Just to clarify — PR #2453 only fixes the SOCKS5 inbound bypass for Xray profiles (issue #2452). It does not and cannot fix what you describe here. The two issues look similar but operate at very different layers: #2452 was a userspace SOCKS5 listener with no auth — fixable by enabling the auth field that the protocol already supports. #2457 is the kernel tun0 interface being directly addressable by any process via SO_BINDTODEVICE / --interface tun0. This is a known limitation of Android's VpnService API: split tunneling there controls default routing per UID, not interface visibility. There is no "authentication" for a kernel network interface — the concept doesn't exist at that layer. Real mitigations require either root (to install per-UID iptables -m owner -o tun0 -j REJECT rules), system privileges (eBPF cgroup filters), or a different OS architecture entirely. WireGuard for Android, Mullvad and other major Android VPN clients have the same limitation, and as far as I know none of them solve it on non-rooted devices. The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device.

I tested it for a while and came to the conclusion that there is one totally safe configuration in AmneziaVPN on android and it is to use the Whitelist Site based split tunneling. It is the only configuration that disallowed me to connect to the https://ifconfig.me/ip through tun0 interface when the ip was excluded (Blacklist might be fine as well because I only added ifconfig.me into it and therefore it only added ip it resolved to, if it has more then 1one ip then it wont be excluded properly, but again I'm not sure so stick to the Whitelist solution). I dont know the exact reason but I assume its because the selection on "should it go through the vpn or not" happens inside the tun0 interface. And the solution that were brought up here do exactly that but instead of IPs they use apps' UUIDs.

So (correct me if Im wrong) theoretically you could just copy and paste the site based split tunneling code and implement it with app UUIDs. Again this wont help with spyware apps finding out whether you use a VPN or not, but it will prevent them from knowing your VPN ip and compromise it later.

But for now use only Site Based Split-Tunneling in Whitelist mode. Use this website for that https://iplist.opencck.org/ru

<!-- gh-comment-id:4228011078 --> @R4L34 commented on GitHub (Apr 11, 2026): > I believe there's _no_ way to fix that. There's no such thing as "tun0 authentication" as it is just an network interface. Network interfaces don't do authentication. > > The issue is with how Android manages tun0 permissions and we can (unfortunately) do nothing about it. > > UPD: Oh wait, it _is_ possible - the way SingBox does it while routing. Every Android app runs with its own (static per installation) UID and SingBox finds connection owner UID by matching source + destination > > [Source link](https://github.com/SagerNet/sing-box/blob/e6427e824412a919368e0af5d985ae85f0f845cd/experimental/libbox/internal/procfs/procfs.go#L33) > Just to clarify — PR [#2453](https://github.com/amnezia-vpn/amnezia-client/pull/2453) only fixes the SOCKS5 inbound bypass for Xray profiles (issue [#2452](https://github.com/amnezia-vpn/amnezia-client/issues/2452)). It does not and cannot fix what you describe here. The two issues look similar but operate at very different layers: [#2452](https://github.com/amnezia-vpn/amnezia-client/issues/2452) was a userspace SOCKS5 listener with no auth — fixable by enabling the auth field that the protocol already supports. [#2457](https://github.com/amnezia-vpn/amnezia-client/issues/2457) is the kernel tun0 interface being directly addressable by any process via SO_BINDTODEVICE / --interface tun0. This is a known limitation of Android's VpnService API: split tunneling there controls default routing per UID, not interface visibility. There is no "authentication" for a kernel network interface — the concept doesn't exist at that layer. Real mitigations require either root (to install per-UID iptables -m owner -o tun0 -j REJECT rules), system privileges (eBPF cgroup filters), or a different OS architecture entirely. WireGuard for Android, Mullvad and other major Android VPN clients have the same limitation, and as far as I know none of them solve it on non-rooted devices. The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device. I tested it for a while and came to the conclusion that there is one totally safe configuration in AmneziaVPN on android and it is to use the Whitelist Site based split tunneling. It is the only configuration that disallowed me to connect to the https://ifconfig.me/ip through tun0 interface when the ip was excluded (Blacklist might be fine as well because I only added ifconfig.me into it and therefore it only added ip it resolved to, if it has more then 1one ip then it wont be excluded properly, but again I'm not sure so stick to the Whitelist solution). I dont know the exact reason but I assume its because the selection on "should it go through the vpn or not" happens inside the tun0 interface. And the solution that were brought up here do exactly that but instead of IPs they use apps' UUIDs. So (correct me if Im wrong) theoretically you could just copy and paste the site based split tunneling code and implement it with app UUIDs. Again this wont help with spyware apps finding out whether you use a VPN or not, but it will prevent them from knowing your VPN ip and compromise it later. But for now use only Site Based Split-Tunneling in Whitelist mode. Use this website for that https://iplist.opencck.org/ru
Author
Owner

@Fast-and-Pythonic commented on GitHub (Apr 14, 2026):

couldn't reproduce on POCO M4 Pro 5G, Android 13, app version 4.8.14.5 and with Termux excluded. tested on AmneziaWG Legacy, AmneziaWG 2.0 and plain WireGuard without obfuscation.

People are reporting that this issue appears starting with kernel 5.7 and newer.
https://habr.com/ru/articles/1020080/#comment_29793082

<!-- gh-comment-id:4243990973 --> @Fast-and-Pythonic commented on GitHub (Apr 14, 2026): > couldn't reproduce on POCO M4 Pro 5G, Android 13, app version 4.8.14.5 and with Termux excluded. tested on AmneziaWG Legacy, AmneziaWG 2.0 and plain WireGuard without obfuscation. People are reporting that this issue appears starting with kernel 5.7 and newer. https://habr.com/ru/articles/1020080/#comment_29793082
Author
Owner

@Fast-and-Pythonic commented on GitHub (Apr 14, 2026):

I'll mention it here as well.

Amnezia wg2 4.8.14.5 also has this issue.
Poco f7
Android 16
Hyper OS 3.0.6.0
Traffic splitting is also enabled. Traffic splitting is also enabled. Termux is not included in the tunneling list. Its traffic does not go through the tunnel. However, Termux can see the server's IP address using the command curl -s https://ifconfig.me/ip --interface tun0.

<!-- gh-comment-id:4243998492 --> @Fast-and-Pythonic commented on GitHub (Apr 14, 2026): I'll mention it here as well. Amnezia wg2 4.8.14.5 also has this issue. Poco f7 Android 16 Hyper OS 3.0.6.0 Traffic splitting is also enabled. Traffic splitting is also enabled. Termux is not included in the tunneling list. Its traffic does not go through the tunnel. However, Termux can see the server's IP address using the command curl -s https://ifconfig.me/ip --interface tun0.
Author
Owner

@Fast-and-Pythonic commented on GitHub (Apr 14, 2026):

The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device.

In this case, AmneziaVPN becomes useless for many users who need to bypass the blocks of their crazy governments. AmneziaVPN will simply lose a large portion of its users.

<!-- gh-comment-id:4244033472 --> @Fast-and-Pythonic commented on GitHub (Apr 14, 2026): > The most useful thing AmneziaVPN can probably do here is document this clearly in the split tunneling UI, so users understand it's a routing convenience and not a security boundary against hostile apps on the same device. In this case, AmneziaVPN becomes useless for many users who need to bypass the blocks of their crazy governments. AmneziaVPN will simply lose a large portion of its users.
Author
Owner

@MrsedD commented on GitHub (Apr 19, 2026):

if not using split tunneling bypass still it allows apps to see the ip address of the VPN.

<!-- gh-comment-id:4274816308 --> @MrsedD commented on GitHub (Apr 19, 2026): if not using split tunneling bypass still it allows apps to see the ip address of the VPN.
Author
Owner

@Gerk17 commented on GitHub (Apr 19, 2026):

Colleagues, this can't be circumvented on Android. It's a fundamental vulnerability. Any application can access tun0 directly, bypassing split tunneling. But you can configure blacklists on the server itself for all potentially spying applications—their domains (banned at the DNS level) and subnets (banned at the iptables level). Additionally, you can enable only your own AmneziaDNS and ban all DoH/DoT resolvers.
This doesn't guarantee 100% protection, as they can create an IP resolver on an unknown server and access it. However, most requests for your server's IP will still be banned.
Another, more reliable option is to configure whitelists on the server if you need a clearly defined, limited set of resources via VPN.

<!-- gh-comment-id:4275863313 --> @Gerk17 commented on GitHub (Apr 19, 2026): Colleagues, this can't be circumvented on Android. It's a fundamental vulnerability. Any application can access tun0 directly, bypassing split tunneling. But you can configure blacklists on the server itself for all potentially spying applications—their domains (banned at the DNS level) and subnets (banned at the iptables level). Additionally, you can enable only your own AmneziaDNS and ban all DoH/DoT resolvers. This doesn't guarantee 100% protection, as they can create an IP resolver on an unknown server and access it. However, most requests for your server's IP will still be banned. Another, more reliable option is to configure whitelists on the server if you need a clearly defined, limited set of resources via VPN.
Author
Owner

@atchekho commented on GitHub (Jun 7, 2026):

One way of hiding the server IP address is to add an extra hop using WireGuard: this way the IP address seen by all apps going through the VPN would be the IP of the extra hop server and not the IP of your VPN server.

The example WireGuard config files route the Amnezia VPN traffic (both for AWG and Xray protocols) through the "extra hop" server (and leaves the rest of the traffic routing unchanged).

This configuration uses a tunnel between the Amnezia server and an "extra hop" WireGuard server. Note that this assumes that:

Amnezia VPN server:

  1. Amnezia VPN uses port UDP 8080.
  2. Xray REALITY/VLESS uses port TCP 443.
  3. Internal IP is 192.168.86.2/32.
  4. Default internal gateway IP is 192.168.86.1/32.
  5. Network interface is ens0.
  6. Amnezia VPN interface is amn0.

WireGuard tunnel:

  1. Uses 10.77.77.0/29 internal IP block
  2. Extra hop WireGuard server IP = 10.77.77.1/29
  3. Amnezia server IP = 10.77.77.2/29

Extra hop WireGuard server:

  1. Network interface is ens3

Contents of /etc/wireguard/exit-hop.conf on the Amnezia VPN server:

[Interface]
Address = 10.77.77.2/29
PrivateKey = <private key of your Amnezia VPS server>
Table = off

# Keep the source server's own replies on the normal WAN path
PostUp = ip route add default via 192.168.86.1 dev ens0 table 100
PostUp = ip rule add from 192.168.86.2/32 table 100 priority 100

# Route only selected Amnezia client traffic via extra hop server
PostUp = ip route add default dev %i table 2468
PostUp = ip rule add fwmark 0x66 table 2468 priority 10010

# Do NOT mark Xray REALITY/VLESS replies.
# Xray uses TCP 443.
PostUp = iptables -t mangle -I PREROUTING 1 -i amn0 -p tcp --sport 443 -j ACCEPT

# Do NOT mark AmneziaWG/AWG replies (assumes Amnezia VPN uses UDP 8080).
PostUp = iptables -t mangle -I PREROUTING 2 -i amn0 -p udp --sport 8080 -j ACCEPT

# Mark the rest of the non-local traffic arriving on amn0 for extra hop egress.
PostUp = iptables -t mangle -I PREROUTING 3 -i amn0 -m addrtype ! --dst-type LOCAL -j MARK --set-mark 0x66

# Forward marked client traffic to the exit hop and allow return traffic back
PostUp = iptables -I FORWARD 1 -i amn0 -o %i -j ACCEPT
PostUp = iptables -I FORWARD 2 -i %i -o amn0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# NAT the marked client traffic out through the exit hop
PostUp = iptables -t nat -I POSTROUTING 1 -m mark --mark 0x66 -o %i -j MASQUERADE

PreDown = iptables -t nat -D POSTROUTING -m mark --mark 0x66 -o %i -j MASQUERADE 2>/dev/null || true
PreDown = iptables -D FORWARD -i %i -o amn0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
PreDown = iptables -D FORWARD -i amn0 -o %i -j ACCEPT 2>/dev/null || true
PreDown = iptables -t mangle -D PREROUTING -i amn0 -m addrtype ! --dst-type LOCAL -j MARK --set-mark 0x66 2>/dev/null || true
PreDown = iptables -t mangle -D PREROUTING -i amn0 -p udp --sport 8080 -j ACCEPT 2>/dev/null || true
PreDown = iptables -t mangle -D PREROUTING -i amn0 -p tcp --sport 443 -j ACCEPT 2>/dev/null || true
PreDown = ip rule del fwmark 0x66 table 2468 priority 10010 2>/dev/null || true
PreDown = ip route flush table 2468 2>/dev/null || true
PreDown = ip rule del from 192.168.86.2/32 table 100 priority 100 2>/dev/null || true
PreDown = ip route flush table 100 2>/dev/null || true

[Peer]
PublicKey = <public key of the exit hop WireGuard server>
Endpoint = <public ip of the exit hop server>:<port>
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Contents of /etc/wireguard/wg0.conf on the "extra hop" WireGuard server:

[Interface]
Address = 10.77.77.1/29
ListenPort = 51820
PrivateKey = <private key of the WireGuard extra hop server>

PostUp = iptables -I INPUT 1 -p udp --dport 51820 -j ACCEPT
PostUp = iptables -I FORWARD 1 -i %i -j ACCEPT
PostUp = iptables -I FORWARD 2 -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -I POSTROUTING 1 -s 10.77.77.0/29 -o ens3 -j MASQUERADE

PostDown = iptables -t nat -D POSTROUTING -s 10.77.77.0/29 -o ens3 -j MASQUERADE
PostDown = iptables -D FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT  2>/dev/null || true
PostDown = iptables -D FORWARD -i %i -j ACCEPT 2>/dev/null || true
PreDown = iptables -D INPUT -p udp --dport 51820 -j ACCEPT 2>/dev/null || true

[Peer]
PublicKey = <public key of your WireGuard instance running on your Amnezia VPN server>
AllowedIPs = 10.77.77.2/32
PersistentKeepalive = 25

<!-- gh-comment-id:4643699695 --> @atchekho commented on GitHub (Jun 7, 2026): One way of hiding the server IP address is to add an extra hop using WireGuard: this way the IP address seen by all apps going through the VPN would be the IP of the extra hop server and not the IP of your VPN server. The example WireGuard config files route the Amnezia VPN traffic (both for AWG and Xray protocols) through the "extra hop" server (and leaves the rest of the traffic routing unchanged). This configuration uses a tunnel between the Amnezia server and an "extra hop" WireGuard server. Note that this assumes that: Amnezia VPN server: 1. Amnezia VPN uses port UDP `8080`. 2. Xray REALITY/VLESS uses port TCP `443`. 3. Internal IP is `192.168.86.2/32`. 4. Default internal gateway IP is `192.168.86.1/32`. 5. Network interface is `ens0`. 6. Amnezia VPN interface is `amn0`. WireGuard tunnel: 1. Uses `10.77.77.0/29` internal IP block 2. Extra hop WireGuard server IP = `10.77.77.1/29` 3. Amnezia server IP = `10.77.77.2/29` Extra hop WireGuard server: 1. Network interface is `ens3` Contents of `/etc/wireguard/exit-hop.conf` on the Amnezia VPN server: ```ini [Interface] Address = 10.77.77.2/29 PrivateKey = <private key of your Amnezia VPS server> Table = off # Keep the source server's own replies on the normal WAN path PostUp = ip route add default via 192.168.86.1 dev ens0 table 100 PostUp = ip rule add from 192.168.86.2/32 table 100 priority 100 # Route only selected Amnezia client traffic via extra hop server PostUp = ip route add default dev %i table 2468 PostUp = ip rule add fwmark 0x66 table 2468 priority 10010 # Do NOT mark Xray REALITY/VLESS replies. # Xray uses TCP 443. PostUp = iptables -t mangle -I PREROUTING 1 -i amn0 -p tcp --sport 443 -j ACCEPT # Do NOT mark AmneziaWG/AWG replies (assumes Amnezia VPN uses UDP 8080). PostUp = iptables -t mangle -I PREROUTING 2 -i amn0 -p udp --sport 8080 -j ACCEPT # Mark the rest of the non-local traffic arriving on amn0 for extra hop egress. PostUp = iptables -t mangle -I PREROUTING 3 -i amn0 -m addrtype ! --dst-type LOCAL -j MARK --set-mark 0x66 # Forward marked client traffic to the exit hop and allow return traffic back PostUp = iptables -I FORWARD 1 -i amn0 -o %i -j ACCEPT PostUp = iptables -I FORWARD 2 -i %i -o amn0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # NAT the marked client traffic out through the exit hop PostUp = iptables -t nat -I POSTROUTING 1 -m mark --mark 0x66 -o %i -j MASQUERADE PreDown = iptables -t nat -D POSTROUTING -m mark --mark 0x66 -o %i -j MASQUERADE 2>/dev/null || true PreDown = iptables -D FORWARD -i %i -o amn0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true PreDown = iptables -D FORWARD -i amn0 -o %i -j ACCEPT 2>/dev/null || true PreDown = iptables -t mangle -D PREROUTING -i amn0 -m addrtype ! --dst-type LOCAL -j MARK --set-mark 0x66 2>/dev/null || true PreDown = iptables -t mangle -D PREROUTING -i amn0 -p udp --sport 8080 -j ACCEPT 2>/dev/null || true PreDown = iptables -t mangle -D PREROUTING -i amn0 -p tcp --sport 443 -j ACCEPT 2>/dev/null || true PreDown = ip rule del fwmark 0x66 table 2468 priority 10010 2>/dev/null || true PreDown = ip route flush table 2468 2>/dev/null || true PreDown = ip rule del from 192.168.86.2/32 table 100 priority 100 2>/dev/null || true PreDown = ip route flush table 100 2>/dev/null || true [Peer] PublicKey = <public key of the exit hop WireGuard server> Endpoint = <public ip of the exit hop server>:<port> AllowedIPs = 0.0.0.0/0 PersistentKeepalive = 25 ``` Contents of `/etc/wireguard/wg0.conf` on the "extra hop" WireGuard server: ```ini [Interface] Address = 10.77.77.1/29 ListenPort = 51820 PrivateKey = <private key of the WireGuard extra hop server> PostUp = iptables -I INPUT 1 -p udp --dport 51820 -j ACCEPT PostUp = iptables -I FORWARD 1 -i %i -j ACCEPT PostUp = iptables -I FORWARD 2 -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT PostUp = iptables -t nat -I POSTROUTING 1 -s 10.77.77.0/29 -o ens3 -j MASQUERADE PostDown = iptables -t nat -D POSTROUTING -s 10.77.77.0/29 -o ens3 -j MASQUERADE PostDown = iptables -D FORWARD -o %i -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true PostDown = iptables -D FORWARD -i %i -j ACCEPT 2>/dev/null || true PreDown = iptables -D INPUT -p udp --dport 51820 -j ACCEPT 2>/dev/null || true [Peer] PublicKey = <public key of your WireGuard instance running on your Amnezia VPN server> AllowedIPs = 10.77.77.2/32 PersistentKeepalive = 25 ```
Author
Owner

@makekryl commented on GitHub (Aug 10, 2026):

I took the liberty of trying to mitigate this; the https://github.com/amnezia-vpn/amneziawg-go/pull/174 along with the patch for the AWG client make things work (with certain limitations, of course - the necessity of dropping unidentifiable packets)

<!-- gh-comment-id:5236431297 --> @makekryl commented on GitHub (Aug 10, 2026): I took the liberty of trying to mitigate this; the https://github.com/amnezia-vpn/amneziawg-go/pull/174 along with the patch for the AWG client make things work (with certain limitations, of course - the necessity of dropping unidentifiable packets)
Author
Owner

@Fast-and-Pythonic commented on GitHub (Sep 22, 2026):

I've put together a complete fix for this on AmneziaWG and WireGuard, measured on a device, as three PRs:

On a Poco F7 (Android 16) with an AmneziaWG server (protocol 3.1), the probes from an excluded app gave 0 of 6 with the setting on and 6 of 6 with it off. They covered curl --interface tun0, TCP and UDP with SO_BINDTODEVICE, and ICMP. Both include and exclude mode were tested.

The setting, on an English build:

Off by default Turning it on explains the trade-off On Locked while connected

Some findings may save time whoever implements this, whichever code ends up merged:

  1. Ping leaks too. Any app can open an unprivileged ICMP ping socket, bind it to tun0 and ping a host it controls. The owner of an ICMP packet cannot be resolved, so a filter has to drop what it cannot attribute. @makekryl's #174 (amneziawg-go) already does this, and that is how I noticed my first version let ping through.
  2. getConnectionOwnerUid returns INVALID_UID for any app not covered by your own VPN, not only when no socket matches. That is AOSP's ConnectivityService. So "unresolved" is exactly the excluded app, and denying it is correct rather than a fallback.
  3. Closed sockets belong to UID 0. After an app calls close(), the lookup reports UID 0. If a filter re-judges packets after its cache expires, it drops the FIN of legitimate connections in include mode. Deciding TCP on SYN only avoids that.
  4. Private DNS by hostname does not work in include mode. The system resolver's own queries are not covered by an include-mode VPN, so they cannot be attributed, and listed apps resolve nothing. Automatic and Off work. Letting DNS through is not a safe exception: an excluded app could query whoami.cloudflare via tun0.

A test build with all three parts, signed with my own key, is here if you want to check it on your own device: https://github.com/amnezia-tun0-fix/amnezia-client/releases/tag/v5.0.3.1-strict.7 — it installs next to the official app, as «AmneziaVPN Strict»; the notes there say what else to know.

The notes behind all this, with the probes: https://github.com/amnezia-tun0-fix/strict-split-tunneling-notes

The Xray datapath has an implementation in my forks as well: tun2socks, libxray and Xray.kt, linked in the client PR. I could not test it, because Xray does not connect from where I am.

🤖 Generated with Claude Code

<!-- gh-comment-id:5782560525 --> @Fast-and-Pythonic commented on GitHub (Sep 22, 2026): I've put together a complete fix for this on AmneziaWG and WireGuard, measured on a device, as three PRs: - amnezia-vpn/amneziawg-go#199: a filter that drops outbound packets whose owning app the split-tunnel policy does not allow - amnezia-vpn/amneziawg-android#104: a JNI bridge, so the policy can ask `ConnectivityManager.getConnectionOwnerUid` who owns a connection - amnezia-vpn/amnezia-client#3199: an opt-in "Strict split tunneling" setting (off by default, Android only) and the wiring On a Poco F7 (Android 16) with an AmneziaWG server (protocol 3.1), the probes from an excluded app gave 0 of 6 with the setting on and 6 of 6 with it off. They covered `curl --interface tun0`, TCP and UDP with `SO_BINDTODEVICE`, and ICMP. Both include and exclude mode were tested. The setting, on an English build: | <img src="https://raw.githubusercontent.com/amnezia-tun0-fix/strict-split-tunneling-notes/main/screenshots/1-switch-off.jpg" width="200"> | <img src="https://raw.githubusercontent.com/amnezia-tun0-fix/strict-split-tunneling-notes/main/screenshots/2-enable-dialog.jpg" width="200"> | <img src="https://raw.githubusercontent.com/amnezia-tun0-fix/strict-split-tunneling-notes/main/screenshots/3-switch-on.jpg" width="200"> | <img src="https://raw.githubusercontent.com/amnezia-tun0-fix/strict-split-tunneling-notes/main/screenshots/4-locked-while-connected.jpg" width="200"> | |---|---|---|---| | Off by default | Turning it on explains the trade-off | On | Locked while connected | Some findings may save time whoever implements this, whichever code ends up merged: 1. **Ping leaks too.** Any app can open an unprivileged ICMP ping socket, bind it to `tun0` and ping a host it controls. The owner of an ICMP packet cannot be resolved, so a filter has to drop what it cannot attribute. @makekryl's #174 (amneziawg-go) already does this, and that is how I noticed my first version let ping through. 2. **`getConnectionOwnerUid` returns `INVALID_UID` for any app not covered by your own VPN**, not only when no socket matches. That is AOSP's `ConnectivityService`. So "unresolved" is exactly the excluded app, and denying it is correct rather than a fallback. 3. **Closed sockets belong to UID 0.** After an app calls `close()`, the lookup reports UID 0. If a filter re-judges packets after its cache expires, it drops the FIN of legitimate connections in include mode. Deciding TCP on SYN only avoids that. 4. **Private DNS by hostname does not work in include mode.** The system resolver's own queries are not covered by an include-mode VPN, so they cannot be attributed, and listed apps resolve nothing. Automatic and Off work. Letting DNS through is not a safe exception: an excluded app could query `whoami.cloudflare` via `tun0`. A test build with all three parts, signed with my own key, is here if you want to check it on your own device: https://github.com/amnezia-tun0-fix/amnezia-client/releases/tag/v5.0.3.1-strict.7 — it installs next to the official app, as «AmneziaVPN Strict»; the notes there say what else to know. The notes behind all this, with the probes: https://github.com/amnezia-tun0-fix/strict-split-tunneling-notes The Xray datapath has an implementation in my forks as well: tun2socks, libxray and `Xray.kt`, linked in the client PR. I could not test it, because Xray does not connect from where I am. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

@Fast-and-Pythonic commented on GitHub (Sep 23, 2026):

Update: a second test build, v5.0.3.1-strict.2.

strict.1 had a bug in "only the apps from the list" mode: cloned apps (dual apps, a second space, a work profile) could not connect at all with the switch on. Android routes every copy of a listed app into the VPN, each under its own uid, and the check recognised only the original. It now matches apps the way Android does. Verified on XSpace clones of two browsers; bypass attempts from an unlisted app stay at 0 of 6.

If you run strict.1, install strict.2 over it: same key, nothing to back up. The fix is also in amnezia-vpn/amnezia-client#3199.

🤖 Generated with Claude Code

<!-- gh-comment-id:5799026652 --> @Fast-and-Pythonic commented on GitHub (Sep 23, 2026): Update: a second test build, [v5.0.3.1-strict.2](https://github.com/amnezia-tun0-fix/amnezia-client/releases/tag/v5.0.3.1-strict.2). strict.1 had a bug in "only the apps from the list" mode: cloned apps (dual apps, a second space, a work profile) could not connect at all with the switch on. Android routes every copy of a listed app into the VPN, each under its own uid, and the check recognised only the original. It now matches apps the way Android does. Verified on XSpace clones of two browsers; bypass attempts from an unlisted app stay at 0 of 6. If you run strict.1, install strict.2 over it: same key, nothing to back up. The fix is also in amnezia-vpn/amnezia-client#3199. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

@Fast-and-Pythonic commented on GitHub (Sep 25, 2026):

Update: a third test build, v5.0.3.1-strict.3, after two reviews of the PRs by @izhddm.

strict.2 had two problems, both fixed here:

  • It could stall the tunnel. The owner of every new flow was looked up on the thread that reads the tunnel, so a few hundred new flows per second slowed every connection, and an app could do that on purpose. The lookup now runs on separate threads, and a flow's first packets wait for their own verdict. At 1000 new flows per second, connecting through the tunnel went from 1.1 s at the median (2 of 40 attempts failing) to about 200 ms, the same as with the switch off.
  • A verdict could outlive its connection. Verdicts were remembered by source port, so a new socket on a reused port could inherit another app's verdict. TCP is now judged at every connection, and UDP per full address pair.

The second fix, a verdict keyed on the full connection, is what @makekryl's amneziawg-go#174 did from the start. So is filter state kept per device, which this build also adopts.

Bypass attempts from an app outside the VPN stay at 0 of 6. The review also turned up two limits, listed in the release notes and in amnezia-vpn/amnezia-client#3199. First, an allowed app loses TCP connections it binds to tun0 itself. Second, a UDP packet from a socket closed right after sending may be dropped.

If you run strict.1 or strict.2, install strict.3 over it: same key, nothing to back up.

Separately, @izhddm found that on Android 13+ the Xray tunnel publishes the server's address in the VPN's routes, readable by any app without sending a packet. The fix is in amnezia-vpn/amnezia-client#3214. AmneziaWG does not publish it.

🤖 Generated with Claude Code

<!-- gh-comment-id:5830142862 --> @Fast-and-Pythonic commented on GitHub (Sep 25, 2026): Update: a third test build, [v5.0.3.1-strict.3](https://github.com/amnezia-tun0-fix/amnezia-client/releases/tag/v5.0.3.1-strict.3), after two reviews of the PRs by @izhddm. strict.2 had two problems, both fixed here: - **It could stall the tunnel.** The owner of every new flow was looked up on the thread that reads the tunnel, so a few hundred new flows per second slowed every connection, and an app could do that on purpose. The lookup now runs on separate threads, and a flow's first packets wait for their own verdict. At 1000 new flows per second, connecting through the tunnel went from 1.1 s at the median (2 of 40 attempts failing) to about 200 ms, the same as with the switch off. - **A verdict could outlive its connection.** Verdicts were remembered by source port, so a new socket on a reused port could inherit another app's verdict. TCP is now judged at every connection, and UDP per full address pair. The second fix, a verdict keyed on the full connection, is what @makekryl's amneziawg-go#174 did from the start. So is filter state kept per device, which this build also adopts. Bypass attempts from an app outside the VPN stay at 0 of 6. The review also turned up two limits, listed in the release notes and in amnezia-vpn/amnezia-client#3199. First, an allowed app loses TCP connections it binds to `tun0` itself. Second, a UDP packet from a socket closed right after sending may be dropped. If you run strict.1 or strict.2, install strict.3 over it: same key, nothing to back up. Separately, @izhddm found that on Android 13+ the Xray tunnel publishes the server's address in the VPN's routes, readable by any app without sending a packet. The fix is in amnezia-vpn/amnezia-client#3214. AmneziaWG does not publish it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

@Fast-and-Pythonic commented on GitHub (Sep 27, 2026):

For anyone comparing approaches: how other clients handle the two leaks discussed around this issue, as of September 2026. Where only a project's own description was available, that is said.

Two different leaks. The first is a local SOCKS/HTTP inbound on 127.0.0.1 without authentication, which any app on the device can use (Habr, 2026-04-07, eight Xray/sing-box clients named). Amnezia closed it in #2456 with a random port and password. The second is the one this issue is about: an app excluded from the VPN binds a socket to tun0 (SO_BINDTODEVICE, allowed without privileges since Linux 5.7) and reaches the tunnel anyway. Fixing the first does not touch the second.

The tun0 leak, elsewhere:

  • TeapodStream filters in its own gVisor tun2socks: getConnectionOwnerUid per flow, RST for denied TCP (Habr, May 2026).
  • OlConnect's README describes an owner check through getConnectionOwnerUid on Android 10+, answering denied flows with TCP RST or ICMP port unreachable. We have not read its code.
  • sing-box added a package_name_regex rule item after SagerNet/sing-box#4009 (6746fcb). A flow whose owner cannot be resolved can then be blocked by a hand-written rule, {"package_name_regex": [".*"], "invert": true, "outbound": "block"}. It is not a default, and we found no switch for it in the Android app.
  • v2rayNG: 2dust/v2rayNG#5499 reproduced the same curl --interface tun0 from an excluded app and was closed as not planned.
  • Within Amnezia: amnezia-vpn/amneziawg-go#174 by @makekryl, and this series (amnezia-vpn/amneziawg-go#199, amnezia-vpn/amneziawg-android#104, #3199).

We found nothing on it from WireGuard for Android, Mullvad, Proton VPN, Tailscale, Cloudflare WARP, Outline, Orbot or OpenVPN for Android, and no AOSP bug, CVE or security bulletin. The per-app VPN settings screen in Android 17 changes the UI, not the routing.

The local-proxy leak, elsewhere: inbound authentication, or a way to switch the inbound off, shipped in Karing 1.2.16, Throne 1.1.2, FlClash 0.8.97 and Exclave 0.17.35, among others; in most of them it is opt-in. hiddify/hiddify-app#2120 was closed as not planned.

What does not help: a work profile (Shelter, Island). Android's users share one network namespace, so tun0 is visible from inside the profile, and RKNHardering detects it there (Habr, 2026-04-22).

If we missed a project or got one wrong, corrections are welcome.

🤖 Generated with Claude Code

<!-- gh-comment-id:5852959288 --> @Fast-and-Pythonic commented on GitHub (Sep 27, 2026): For anyone comparing approaches: how other clients handle the two leaks discussed around this issue, as of September 2026. Where only a project's own description was available, that is said. **Two different leaks.** The first is a local SOCKS/HTTP inbound on `127.0.0.1` without authentication, which any app on the device can use ([Habr, 2026-04-07](https://habr.com/ru/articles/1020080/), eight Xray/sing-box clients named). Amnezia closed it in #2456 with a random port and password. The second is the one this issue is about: an app excluded from the VPN binds a socket to `tun0` (`SO_BINDTODEVICE`, allowed without privileges since Linux 5.7) and reaches the tunnel anyway. Fixing the first does not touch the second. **The `tun0` leak, elsewhere:** - **TeapodStream** filters in its own gVisor tun2socks: `getConnectionOwnerUid` per flow, RST for denied TCP ([Habr, May 2026](https://habr.com/ru/articles/1030930/)). - **OlConnect**'s [README](https://github.com/Oleglog/OlConnect) describes an owner check through `getConnectionOwnerUid` on Android 10+, answering denied flows with TCP RST or ICMP port unreachable. We have not read its code. - **sing-box** added a `package_name_regex` rule item after SagerNet/sing-box#4009 (`6746fcb`). A flow whose owner cannot be resolved can then be blocked by a hand-written rule, `{"package_name_regex": [".*"], "invert": true, "outbound": "block"}`. It is not a default, and we found no switch for it in the Android app. - **v2rayNG**: 2dust/v2rayNG#5499 reproduced the same `curl --interface tun0` from an excluded app and was closed as not planned. - **Within Amnezia**: amnezia-vpn/amneziawg-go#174 by @makekryl, and this series (amnezia-vpn/amneziawg-go#199, amnezia-vpn/amneziawg-android#104, #3199). We found nothing on it from WireGuard for Android, Mullvad, Proton VPN, Tailscale, Cloudflare WARP, Outline, Orbot or OpenVPN for Android, and no AOSP bug, CVE or security bulletin. The per-app VPN settings screen in Android 17 changes the UI, not the routing. **The local-proxy leak, elsewhere:** inbound authentication, or a way to switch the inbound off, shipped in Karing 1.2.16, Throne 1.1.2, FlClash 0.8.97 and Exclave 0.17.35, among others; in most of them it is opt-in. hiddify/hiddify-app#2120 was closed as not planned. **What does not help:** a work profile (Shelter, Island). Android's users share one network namespace, so `tun0` is visible from inside the profile, and RKNHardering detects it there ([Habr, 2026-04-22](https://habr.com/ru/articles/1026500/)). If we missed a project or got one wrong, corrections are welcome. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Owner

@Fast-and-Pythonic commented on GitHub (Sep 30, 2026):

Update: a new test build, v5.0.3.1-strict.7. The number matches the AmneziaWG library inside; strict.4–6 were internal and never published.

It now installs next to the official app, as «AmneziaVPN Strict», so trying it no longer means removing AmneziaVPN and its configurations.

If you run strict.3, please move to this one. Its VPN service could crash on a disconnect, a reconnect or a network switch: a bug we introduced when moving the owner lookup off the tunnel reader. The worker threads were locked to OS threads, and a locked thread exits with every signal blocked while the JNI bridge detaches it from the JVM. On a test phone that killed the service about once in 60–100 filter swaps under load, with no crash report left behind. Fixed in this build: none in 300.

Also measured on the phone and fixed:

  • an active UDP flow (a call, a game, QUIC) lost part of a burst every 10 s when its verdict expired, about 1% of its packets; the verdict is now renewed before it expires;
  • a new UDP flow kept only 4 packets of its first burst; now 16.

Two more fixes are in this build but on separate branches rather than in amnezia-vpn/amneziawg-go#199: connections coming in through the tunnel to a listed app, and UDP datagrams larger than the MTU. The PR description lists them with the numbers.

Bypass attempts from an app outside the VPN stay at 0 of 6, with all of this on. strict.1–3 took the official app's place, so this build appears next to them: move your configuration by backup and remove the old one.

🤖 Generated with Claude Code

<!-- gh-comment-id:5915803161 --> @Fast-and-Pythonic commented on GitHub (Sep 30, 2026): Update: a new test build, [v5.0.3.1-strict.7](https://github.com/amnezia-tun0-fix/amnezia-client/releases/tag/v5.0.3.1-strict.7). The number matches the AmneziaWG library inside; strict.4–6 were internal and never published. **It now installs next to the official app**, as «AmneziaVPN Strict», so trying it no longer means removing AmneziaVPN and its configurations. **If you run strict.3, please move to this one.** Its VPN service could crash on a disconnect, a reconnect or a network switch: a bug we introduced when moving the owner lookup off the tunnel reader. The worker threads were locked to OS threads, and a locked thread exits with every signal blocked while the JNI bridge detaches it from the JVM. On a test phone that killed the service about once in 60–100 filter swaps under load, with no crash report left behind. Fixed in this build: none in 300. Also measured on the phone and fixed: - an active UDP flow (a call, a game, QUIC) lost part of a burst every 10 s when its verdict expired, about 1% of its packets; the verdict is now renewed before it expires; - a new UDP flow kept only 4 packets of its first burst; now 16. Two more fixes are in this build but on separate branches rather than in amnezia-vpn/amneziawg-go#199: connections coming in through the tunnel to a listed app, and UDP datagrams larger than the MTU. The PR description lists them with the numbers. Bypass attempts from an app outside the VPN stay at 0 of 6, with all of this on. strict.1–3 took the official app's place, so this build appears next to them: move your configuration by backup and remove the old one. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1093
No description provided.