RobOS Security

Security & isolation proofs

Architectural isolation boundaries in RobOS—each backed by live kernel state inspection, automated end-to-end fault-injection tests, and captured evidence.

Network Namespace (`ip netns`) OpenVPN 2.7 / IPVanish 3-Barrier Kernel Kill-Switch Live E2E Proof

Zero-Leak OpenVPN App Sandbox: Proving an App Never Leaks Your Real IP When the VPN Drops

RobOS VPN Servers and RobOS VPN Sandbox let you bind individual Linux desktop applications (such as Firefox, Transmission, or CLI tools) to an OpenVPN provider (with 3,598 built-in IPVanish server profiles) inside a dedicated Linux network namespace (ip netns). Even if the OpenVPN daemon crashes or is force-killed (SIGKILL) mid-session while the app is actively transmitting, three independent kernel barriers prevent the app from ever falling back to your normal public IP.

RobOS VPN Sandbox showing Firefox running inside an isolated VPN namespace and the live 5-step Kill-Switch Proof banner
E2E capture of RobOS VPN Sandbox on Ubuntu 26.04 LTS: Firefox is confined to an isolated namespace routing through IPVanish Atlanta (216.131.74.203) while the normal public IP (105.42.88.19) is blocked, and the top banner reports the live 5-step mid-session OpenVPN drop kill-switch proof.
RobOS VPN Servers showing live OpenVPN account credential verification and 3,598 IPVanish server profiles
RobOS VPN Servers: GPG/AES-256-GCM encrypted account credentials tested live against OpenVPN TLS 1.3 + PUSH_REPLY, paired with 3,598 bundled IPVanish server profiles filterable by country and city.
RobOS VPN Sandbox showing an active Firefox session routed over tun0 with the host normal public IP blocked
RobOS VPN Sandbox: Launching an application creates an ephemeral robos_vpn_<id> namespace, verifies external exit IP over tun0 before starting the binary, and isolates browser profiles and D-Bus sessions.

Why Traditional OS-Wide VPNs Leak When OpenVPN Stops

On a standard desktop VPN setup, OpenVPN installs split default routes (0.0.0.0/1 and 128.0.0.0/1) over tun0 on top of the host's primary default route (0.0.0.0/0 via 192.168.1.1 dev enp2s0). If the OpenVPN process crashes or drops the tunnel interface, the kernel automatically removes the /1 routes on tun0 and immediately routes in-flight sockets over the underlying host interface—exposing the user's real public IP.

The 3-Barrier Kernel Architecture

Instead of sharing the host network stack, RobOS VPN Sandbox (packages/robos-lib/vpn-sandbox-engine.js) constructs a per-application network namespace (robos_vpn_<id>) where physical interfaces do not exist and enforces three independent kernel firewall barriers:

Technical architecture diagram of the RobOS Zero-Leak OpenVPN Network Namespace Sandbox and 3-Barrier Kernel Kill-Switch
Technical schematic of the 3-Barrier Linux Kernel Kill-Switch inside robos_vpn_<id>.
flowchart LR
  subgraph NS["Isolated Linux Network Namespace (robos_vpn_25db)"]
    APP["Sandboxed App (Firefox / Transmission)
uid=1000, CAP_NET_ADMIN stripped"] DNS["Namespace DNS & NSS
/etc/netns/robos_vpn_25db/resolv.conf
nsswitch.conf: hosts: files dns"] IPT_NS["Barrier 1: In-Namespace iptables OUTPUT
Policy: DROP
1. -o lo ACCEPT
2. -o tun+ ACCEPT
3. -o veth_n_25db -d 216.131.74.202:443/udp -m owner --uid-owner 0 ACCEPT
4. -o veth_n_25db DROP (blocks uid 1000 -> EPERM)"] OVPN["OpenVPN 2.7 Daemon
uid=0 (--dns-updown disable)"] TUN0["tun0 (172.21.23.221)"] VETH_N["veth_n_25db (10.203.41.2/30)"] APP --> DNS APP -->|Allowed when tun0 UP| TUN0 TUN0 --> OVPN OVPN -->|uid=0 encrypted UDP:443| IPT_NS APP -.->|When tun0 DOWN: falls back to default route| IPT_NS IPT_NS -->|uid=0 ONLY to 216.131.74.202:443| VETH_N end subgraph HOST["Host Kernel (enp2s0 — Normal Public IP 105.42.88.19)"] VETH_H["veth_h_25db (10.203.41.1/30)"] IPT_FWD["Barrier 2: Host FORWARD Chain (ROBOS_VPN_FWD_25db)
ACCEPT only 10.203.41.2 -> 216.131.74.202:443/udp
DROP all other packets from veth_h_25db"] IPT_NAT["Barrier 3: Host NAT POSTROUTING
MASQUERADE only -s 10.203.41.2 -d 216.131.74.202:443/udp
No NAT rule exists for any other destination"] VETH_N --> VETH_H VETH_H --> IPT_FWD IPT_FWD --> IPT_NAT end subgraph EXT["External Internet"] VPNSRV["IPVanish Server (atl-a01)
216.131.74.202:443/udp
Exit IP: 216.131.74.203"] REALIP["Direct Host Egress (105.42.88.19)
UNREACHABLE FROM SANDBOX"] IPT_NAT -->|Encrypted OpenVPN Carrier| VPNSRV IPT_FWD -.->|0 packets allowed| REALIP end
  1. Barrier 1 — In-Namespace iptables Default-DROP + UID Owner Lock: The target application is spawned as the unprivileged desktop user (uid 1000) with setpriv --inh-caps=-net_admin,-net_raw so it can never alter routes or firewall rules. Inside robos_vpn_<id>, iptables -P OUTPUT DROP and ip6tables -P OUTPUT DROP drop all traffic by default. Only -o tun+ is open to uid 1000. On the carrier link veth_n_<id>, only -m owner --uid-owner 0 (the root OpenVPN daemon) destined to the exact VPN server IP and port (216.131.74.202:443/udp) is accepted, followed immediately by -A OUTPUT -o veth_n_<id> -j DROP. When openvpn stops and tun0 vanishes, any packet sent by the app is routed toward veth_n_<id> and immediately rejected by the kernel with EPERM.
  2. Barrier 2 — Host Kernel FORWARD Chain Lock (ROBOS_VPN_FWD_<id>): On the host side of the virtual Ethernet cable (veth_h_<id>), a dedicated chain inserted at FORWARD 1 only permits packets from 10.203.X.2/32 to <vpn_server_ip>/32:<vpn_port> and unconditionally DROPs all other traffic from veth_h_<id> before NAT.
  3. Barrier 3 — Destination-Scoped Host POSTROUTING NAT: The host MASQUERADE rule is scoped strictly to -s 10.203.X.2/32 -d <vpn_server_ip>/32 -p udp --dport 443. Even if FORWARD did not exist, the host kernel has no NAT translation rule to masquerade 10.203.X.2 onto the normal public IP for any other destination.

Live 5-Phase End-to-End Kill-Switch Proof

To prove that stopping OpenVPN mid-session never exposes the host's real IP, the automated E2E test suite (tests/e2e/vpn-sandbox-killswitch-proof.test.cjs) executes proveKillSwitchOnVpnDrop() against the live Linux kernel:

  1. Phase 1 — Host Baseline: Queries https://api.ipify.org on the host outside the sandbox and records the real public IP (105.42.88.19).
  2. Phase 2 — In-Namespace Egress While OpenVPN (tun0) is UP: Starts an isolated namespace (robos_vpn_25db) connected to IPVanish Atlanta (atl-a01.ipvanish.com). Running inside robos_vpn_25db as unprivileged user uid 1000 with CAP_NET_ADMIN stripped, the test queries https://api.ipify.org and verifies the returned IP is the VPN exit IP (216.131.74.203, tunnel IP 172.21.23.221) and never 105.42.88.19.
  3. Phase 3 — Force-Kill OpenVPN Mid-Flight (kill -9): Sends SIGKILL to the OpenVPN daemon inside robos_vpn_25db while leaving the namespace, the running app, and the fallback default route (default via 10.203.41.1 dev veth_n_25db) up. Verifies tun0 is destroyed.
  4. Phase 4 — 4 Post-Kill Egress Probes from the Running App: With tun0 gone and the veth_n_25db default route active, the unprivileged app inside robos_vpn_25db attempts four distinct egress connections:
    • Probe A (HTTPS IP Check): GET https://api.ipify.org → leaked: false (blocked: EAI_AGAIN, UDP DNS packets dropped).
    • Probe B (Direct TCP to Public IP): net.connect('1.1.1.1', 80) bypassing DNS → leaked: false (blocked: ETIMEDOUT, TCP SYN packets dropped).
    • Probe C (Direct UDP DNS Packet): dgram.send('8.8.8.8', 53) → leaked: false (blocked immediately by kernel: EPERM).
    • Probe D (Direct UDP to Whitelisted VPN Server IP as App UID): dgram.send('216.131.74.202', 443) as uid 1000 → leaked: false (blocked immediately by kernel: EPERM because -m owner --uid-owner 0 only allows uid 0).
  5. Phase 5 — Kernel iptables Packet Counter Proof: Inspects ip netns exec robos_vpn_25db iptables -L OUTPUT -v -n -x and verifies that 14 packets (1,070 bytes) were intercepted and dropped by -A OUTPUT -o veth_n_25db -j DROP, with 0 packets leaking to 105.42.88.19.
# Run the live kernel E2E kill-switch proof ROBOS_VPN_LIVE_E2E=1 node --test tests/e2e/vpn-sandbox-killswitch-proof.test.cjs # Output from Ubuntu 26.04 LTS verification host: { "ok": true, "mode": "live-kernel-proof", "netnsName": "robos_vpn_25db", "normalPublicIp": "105.42.88.19", "vpnExitIpBeforeDrop": "216.131.74.203", "tunnelIpBeforeDrop": "172.21.23.221", "tun0DestroyedAfterKill": true, "vethDefaultRouteStillUp": true, "nsRoutesAfterKill": "default via 10.203.41.1 dev veth_n_25db \n10.203.41.0/30 dev veth_n_25db proto kernel scope link src 10.203.41.2 \n216.131.74.202 via 10.203.41.1 dev veth_n_25db", "probeResults": { "httpsIpify": { "leaked": false, "blockedError": "EAI_AGAIN" }, "tcpDirect": { "leaked": false, "blockedError": "ETIMEDOUT" }, "udpDnsDirect": { "leaked": false, "blockedError": "EPERM" }, "udpVpnServerAsAppUid": { "leaked": false, "blockedError": "EPERM" } }, "ipLeakedAfterDrop": false, "droppedPacketsInKernel": 14, "iptablesOutputTable": "Chain OUTPUT (policy DROP 0 packets, 0 bytes)\n pkts bytes target prot opt in out source destination \n 0 0 ACCEPT all -- * lo 0.0.0.0/0 0.0.0.0/0 \n 19 2090 ACCEPT all -- * tun+ 0.0.0.0/0 0.0.0.0/0 \n 28 5694 ACCEPT udp -- * veth_n_25db 0.0.0.0/0 216.131.74.202 udp dpt:443 owner UID match 0\n 14 1070 DROP all -- * veth_n_25db 0.0.0.0/0 0.0.0.0/0" }