The Alpha

The Alpha is the working prototype of the VPN Works engine: one Go program called vpnw, its tests, a few tools and a demo. On Linux it runs a program in a sealed sandbox whose only way out is vpnw. Each connection is decided by a policy, sent down the path you chose and recorded.

Everything so far ran on one Linux machine, a virtual machine with two CPUs. The demo’s agent, servers, office and attacker are stand-ins in a private network namespace. The engine also compiles for arm64 Linux and for macOS, but neither build has run yet. On macOS it is meant to route and trace programs that honor proxy settings; it can’t seal them. No real coding agent has run under vpnw so far. That is the job of the Beta.

Where it stands. A working engine with a sealed sandbox, tested hard on one Linux machine. Next it has to prove itself with real agents, on more systems.

  • 3.6 MB: the whole engine, one Linux binary with no third-party code (1.5 MB compressed).
  • 14 of 14: ways out of the sealed sandbox tried, and all blocked. Two IPv6 rows couldn’t run: the test machine has no IPv6.
  • 24 of 24: deliberately planted bugs caught by the tests.
  • About 1 ms: added to each new connection in a sealed run.

On this page: In brief · How it is built · The sandbox · Decisions and paths · Record and learn · Tests · Speed and size · Features and limits · The demo · Where it stands

The Alpha in Brief

Area Status Key figures
Engine Complete for the Alpha scope One Go program: 5,459 lines in 24 files, standard library only
Sandbox Working on Linux A network namespace with loopback only, and a seccomp filter. No root needed
Paths Direct and proxy SOCKS5 and HTTP proxies, with names resolved locally or at the exit. WireGuard is Beta work
Tests Extensive, on one machine 57 test functions, 97 with subtests; 14 of 14 ways out blocked; 24 of 24 planted bugs caught; 6.95 million fuzzed inputs; 77.4% of statements covered
Speed Measured on one machine About 9 ms more to start, about 1 ms more per new connection, 789 MB/s through the sandbox
Platforms Linux x86-64, run and tested arm64 Linux and macOS compile, not run yet. On macOS, route and trace only by design
Demo Six recorded runs A stand-in agent, servers, office and attacker in a private network namespace
Readiness TRL 4 “Technology validated in lab”, on the European Commission’s scale

How It Is Built

  • One program. vpnw is one static binary. It is also its own sandbox helper: it starts a copy of itself inside the new namespaces.
  • Go, standard library only. Go 1.24, with no third-party modules at all. That includes the configuration reader: the standard library has no TOML, so vpnw carries a small strict reader of its own.
  • No daemon, no account. vpnw runs when you run it and stops when the program stops. The only thing it leaves behind is the last trace, kept for learn.
  • Strict configuration. Every file carries a version number. An unknown key is an error, reported with its line number, never silently ignored.
  • One core for every command. run, trace and guard are the same code with different options: one process runner, one path, one policy and one event bus. That is what lets them combine in a single command.
  • Fail closed. When vpnw can’t give what was asked for (a sandbox, a working path, a policy that loads), it stops before the program starts, with its own exit code.
  • License. The engine is not public yet. The license will be chosen before the first public release. The plan is a proprietary engine, with an open-source branch under consideration. Until then the prototype is marked “all rights reserved”, and the live demo is the way to see it run.
Part Lines What it does
cmd/vpnw 540 The command line: run, trace, guard, learn, doctor and version
process 907 The sandbox: namespaces, the helper, capabilities, the seccomp filter, the proxy variables
broker 817 The proxy front end outside the sandbox: HTTP CONNECT, plain HTTP and SOCKS5. It decides, connects, relays and counts
policy 606 Rules, the five-step decision, deny_private and its ranges
path 405 Direct and proxy paths, and the health check before a program starts
config 979 The strict TOML reader and the checks on every file
events 509 The event model, text and JSON Lines output, and reading traces back
learn 332 A draft policy from a trace
plan 130 What the broker would do with a request, shared with the browser demo
wasm 225 The browser build’s interface to the engine
version 9 The version string

Lines of Go, tests not counted. The tests add 2,439 lines.

The Sealed Sandbox

Inside the sandbox, a network namespace: the program, unchanged, told to use a proxy by the usual settings; two ports on its own loopback, the only listeners it can reach; and any other way out blocked, either because there is no route or because a seccomp filter refuses it. The two ports lead over a Unix socket to vpnw outside the sandbox, which checks the destination, decides with the policy, connects through the chosen path and records every step.

How a Sealed Run Starts

  1. vpnw checks the path. If a proxy was chosen and doesn’t answer, it stops with exit code 123 before anything starts.
  2. It starts the helper in a new user namespace and a new network namespace. The user namespace is what lets an ordinary user do this without root.
  3. The helper brings up loopback and checks that loopback is the only interface. If anything else is there, it refuses to go on (exit code 122).
  4. It listens on 127.0.0.1:3128 for HTTP proxy requests and on 127.0.0.1:1080 for SOCKS5, on the sandbox’s own loopback. Each connection to them is carried over a Unix socket to vpnw’s broker outside.
  5. It clears the capabilities the program could inherit and, unless --allow-unix-sockets was given, sets no_new_privs and installs the seccomp filter. All of this happens on the thread that will start the program.
  6. It starts the program with HTTP_PROXY, HTTPS_PROXY and ALL_PROXY pointing at the two ports, NO_PROXY removed, and NODE_USE_ENV_PROXY set so that Node.js honors the others.

Two Walls

The network namespace. The sandbox has no interface except loopback and no route anywhere. A direct connection, a UDP packet, a raw socket or a DNS query from the program or any of its children has nothing to travel on.

The seccomp filter. Unix sockets live in the file system, not in a network namespace, so the first wall alone would leave local services within reach. The filter makes socket(AF_UNIX, ...) fail with “operation not permitted”. It also refuses io_uring, which can create sockets without calling socket(), and any system call made through another architecture’s numbering, so there is no second system call table to go through. socketpair still works, so programs can talk to their own children. Like every seccomp filter, it passes to the program’s children and can’t be removed. --allow-unix-sockets leaves it out for programs that need a local socket, and the run’s record says so.

What It Needs From the Machine

Sealed runs need a Linux kernel that allows unprivileged user namespaces. Most distributions do. Ubuntu 23.10 and later restrict them through AppArmor, and vpnw doctor reports that; the Beta ships the AppArmor profile that lifts it for vpnw. vpnw is meant to run as an ordinary user. Run as root, the program in the sandbox keeps root’s power over the machine’s files, which the Alpha doesn’t limit.

Decisions and Paths

Five steps: deny rules on the name; deny rules on every address the name resolved to; deny_private on every address; allow rules; and the default. The first step that decides wins.

The Order of a Decision

Every request goes through the same five steps, and the first step that decides wins: deny rules on the name, deny rules on every address the name resolved to, deny_private on every address, allow rules, and finally the default. The default is set in the policy. When a policy has an allow list and no default, the default is deny.

A denied name is never looked up, and under a default of deny neither is a name that no allow rule matches, because a DNS query can carry data out too. Before a decision, vpnw looks a name up only when an address could change the answer: when the name is allowed and deny_private or an address deny rule still has to be checked, or when the default is allow. Once a name is allowed, vpnw looks it up to connect, if it hasn’t already, unless the path resolves names at its exit. When a name resolves to several addresses, every one of them has to pass.

Rules

Rule Matches Doesn’t match
github.com github.com api.github.com, evilgithub.com
*.github.com api.github.com, a.b.github.com github.com, badgithub.com
140.82.112.6 That address typed as a number. As a deny rule, also any name that resolves to it, on paths that resolve names locally As an allow rule, a name that happens to resolve to it
10.0.0.0/8 Any address in the range typed as a number. As a deny rule, also names that resolve into it, on paths that resolve names locally As an allow rule, names
api.github.com:443 That name on port 443 only Other ports

deny_private refuses loopback, the private ranges, carrier-grade NAT, the link-local range where cloud metadata services live, and the other special-purpose ranges of IPv4 and IPv6: 25 ranges in all. IPv4 addresses carried inside IPv6 (IPv4-mapped, NAT64 and 6to4) are judged by the IPv4 address they carry. A host name made only of digits, such as 2130706433, is refused.

Paths

Path How it connects Where names are resolved
Direct From this machine On this machine
SOCKS5 proxy Through the proxy On the command line as in curl: on this machine with socks5://, at the exit with socks5h://. In a file, at the exit unless dns = "local"
HTTP proxy Through the proxy, with HTTP CONNECT At the exit, unless dns = "local"

Proxy passwords are supported and never shown in messages or events. On a path that resolves names at its exit, vpnw can’t see where a name points, so it refuses a policy that would need to: deny_private with a default of allow is rejected on such a path, before the program starts. A path is checked before the program starts, and vpnw never falls back to a direct connection.

The Record and Learn

Every run is recorded as events, schema version 1. There are twelve event types: run.start, process.start, dns.query, dns.result, connection.attempt, policy.allow, policy.deny, connection.open, connection.close, connection.error, process.exit and run.end. They are printed as text while the program runs, or as JSON Lines with --json, and --out FILE writes them to a file as well. The last trace is kept for learn unless --no-save is given.

Events keep names, addresses, ports, byte counts, times and decisions. They never keep the data sent, URL paths, headers, environment variables or proxy passwords. Every connection ends as opened, denied or failed, and the summary at the end of a run adds up.

learn reads a trace, the last one or any file, and writes a draft policy: default deny, deny_private on, and one allow rule per destination the program reached, with a comment saying how often and on which ports. It lists destinations that were tried but never reached, so a person can decide whether they need another path, and it marks what a reviewer should look at: denied destinations, raw addresses, and connections that sent much more than they received. --ports writes host-and-port rules, and --wildcards folds three or more subdomains of one domain into a wildcard. The draft starts with “Read it before you use it”, because learn allows what the program did, leaks included.

Tests

Every test runs on Linux against the real engine. The integration tests start the real binary and seal real programs. All suites pass.

Suite What it checks Result
Unit tests Rules and decisions, deny_private ranges, configuration errors with line numbers, SOCKS5 and HTTP CONNECT paths, the broker, events, learn, the proxy variables, and the seccomp filter, run through a small interpreter 47 test functions, 24 subtests; all passed
Integration tests The real binary, sealed: routing through the broker, guard’s denial and exit code, the program’s own exit code, standard input, a dead path refused before start, socketpair, --allow-unix-sockets, and a program run as an ordinary user holding no capabilities 10 test functions, the bypass matrix among them; all passed
Bypass matrix 16 ways out of the sandbox. Each row lists the errors it accepts and fails on anything else; most rows aimed at this machine also run a control outside the sandbox 14 blocked, 2 skipped: this machine has no IPv6
Race detector The whole suite under Go’s race detector Clean
Fuzzing Four targets, one minute each: the configuration reader, the rule parser, policy decisions and the broker’s front end 6.95 million inputs, no crash
Plan against broker The request plan the browser demo uses, compared with the running broker 119 cases, all the same
Browser engine The TinyGo build in the demo, compared with the standard Go build 471 calls, identical answers
Planted bugs 24 deliberate bugs in the policy, the broker, the paths, the sandbox, the record and learn, one at a time 24 of 24 caught
Coverage Unit and integration tests together 77.4% of statements

Coverage by part: policy 93.2%, learn 94.8%, plan 88.2%, broker 84.4%, config 79.8%, path 72.2%, events 70.8%, process 64.4%, the command line 59.8%.

The Bypass Matrix

A sealed program tries each way out below. Each row lists the errors it accepts, usually one, and fails on anything else, success included. Rows aimed at a listener on this machine also check that nothing arrived, and most of them run the same attempt once outside the sandbox as a control, to show the attempt itself works.

Way out Inside the sandbox Outside (control) Result
IPv4 TCP to this machine’s loopback (127.0.0.1) Nothing listening in the sandbox Reached Blocked
IPv4 TCP to this machine’s own address No route Reached Blocked
IPv4 TCP to a public address (1.1.1.1:443) No route Reached Blocked
IPv4 TCP to a private address (10.0.0.1:80) No route Not needed Blocked
IPv4 TCP to cloud metadata (169.254.169.254:80) No route Not needed Blocked
UDP to this machine’s own address No route Reached Blocked
UDP to a public DNS resolver (8.8.8.8:53) No route Not needed Blocked
DNS lookup through the system resolver Name lookup failed Not needed Blocked
Raw IP socket (ICMP echo to 1.1.1.1) No route Not needed Blocked
A child process connecting on its own No route Not needed Blocked
Abstract Unix socket Not permitted Reached Blocked
Unix socket in the file system, like the Docker socket Not permitted Reached Blocked
io_uring, which can create sockets without socket() Not permitted Reached Blocked
A system call through the x32 table Not permitted Not needed Blocked
IPv6 TCP to loopback (::1) Not run Not run Skipped: no IPv6 on this machine
IPv6 TCP to a public address Not run Not run Skipped: no IPv6 on this machine

Planted Bugs

To check the tests themselves, 24 bugs were put into the engine on purpose, one at a time, and the test suite was run against each. A bug counts as caught only if a test fails.

  1. *.example.com also matches badexample.com
  2. An exact rule for github.com also matches evilgithub.com
  3. deny_private forgets the link-local range, where cloud metadata lives
  4. deny_private is not checked on the addresses a name resolved to
  5. With a default of allow, names are no longer looked up for deny_private
  6. Only the first address of a name is checked
  7. A private IPv4 address wrapped in NAT64 form is let through
  8. Numeric host names such as 2130706433 are accepted as names
  9. A path that resolves names at its exit quietly weakens deny_private
  10. The broker connects even when the policy says deny
  11. Proxy credentials are forwarded to the destination server
  12. The SOCKS5 port stops asking for the per-run token
  13. A configuration file of an unknown version is accepted
  14. The sandbox starts even with a network interface other than loopback
  15. The program keeps a capability the sandbox helper needed
  16. The Unix-socket filter is never installed
  17. The filter checks the wrong address family and lets Unix sockets through
  18. 32-bit system calls walk around the filter
  19. A NO_PROXY setting survives and tells programs to go around vpnw
  20. A proxy password appears in events and messages
  21. learn allows destinations that were denied during the trace
  22. The run summary stops counting connections that opened
  23. guard exits with 0 after a denial, so scripts and CI never notice
  24. A dead proxy is not caught before the program starts

All 24 were caught.

Defects Found and Fixed

Building and testing the Alpha turned up these defects. All are fixed, and each is covered by a test:

Defect Found by Effect before the fix
1 A sealed program could still connect to a Unix socket in the file system, such as Docker’s The bypass matrix, which listed it as a known gap until it was closed A local service could have made connections on the program’s behalf
2 In builds with cgo, the helper couldn’t clear the capabilities of an ordinary user’s program The test that runs a program as an ordinary user vpnw stopped with exit code 124 instead of running the program
3 One jump in the new seccomp filter pointed at the wrong instruction Code review, before the filter ever ran. It is now run through an interpreter in the tests The filter would have refused an unrelated system call, write on x86-64, and broken every program
4 The run summary counted failed connections twice Review; a test now checks that opened, denied and failed add up Wrong totals in the record
5 With two files on the command line, the policy took the other file’s name Building the demo A misleading policy name in the record
6 Some bypass rows passed for the wrong reason, such as a missing tool or no IPv6 Review of the matrix. Each row now lists the errors it accepts, with controls and explicit skips Rows that proved nothing counted as blocked

What the Tests Do Not Cover Yet

  • Real agents. Only a stand-in agent has run under vpnw.
  • Other machines. One kernel (Linux 6.18), one distribution, x86-64 only. The arm64 build compiles but hasn’t run, and the AppArmor restriction on Ubuntu hasn’t been tried.
  • IPv6. The test machine has no IPv6, so the two IPv6 rows were skipped, not passed.
  • macOS. The build compiles but hasn’t run on a Mac. By design it can only route and trace there.
  • Files. vpnw limits the network only. A sealed program can read what its user can read, and it can write files that another program may run later, outside the sandbox.
  • Silent walls. A program that tries to go around vpnw fails, but the attempt doesn’t show in the record.
  • Time. Fuzzing ran for one minute per target, and the longest run under vpnw lasted seconds.
  • Independent review. Every test was written by the people who wrote the code.

Speed and Size

All figures from one virtual machine with two CPUs, Linux 6.18 on x86-64, Go 1.24.7. The servers were local, so the figures show what vpnw adds, not what a real network costs.

Measure Without vpnw vpnw, sealed What vpnw adds
Start-up and exit of /bin/true, median of 60 runs 1.65 ms 10.97 ms 9.32 ms
The same with the env backend (proxy settings only) 1.65 ms 4.58 ms 2.93 ms
Each new connection, 1,000 in a row, best of 3 0.563 ms 1.668 ms 1.105 ms
The same with the env backend 0.563 ms 1.348 ms 0.785 ms
One 100 MB download, best of 3 1,251 MB/s 789 MB/s 63% of the bare speed
64 clients in parallel, 3,008 requests of 16 KB 2,000 requests/s 755 requests/s 38% of the bare rate; all 3,008 intact
Memory while a program runs (resident) vpnw 4.1 MB, helper 4.2 MB

The cost that matters for an agent is the one per connection: about a millisecond, next to the tens or hundreds of milliseconds a request to a real server takes. The parallel figure is the weakest. Every connection crosses two extra hops, from the sandbox to the helper and from the helper to the broker, and the Alpha does nothing clever about it yet.

Build Size Compressed (gzip -9)
Linux x86-64 3,612,856 bytes 1,515,249 bytes
Linux arm64 3,473,592 bytes 1,391,935 bytes
macOS, Intel and Apple silicon Compiles; not run yet
Browser engine (TinyGo, WebAssembly) 827,566 bytes 302,498 bytes
Browser engine (standard Go, WebAssembly) 4,293,029 bytes 1,171,863 bytes

Features and Limits

Feature What the Alpha supports Limits
Platforms Linux with unprivileged user namespaces, for sealed runs macOS compiles but hasn’t run; there, only run and trace with --backend env, and guard refuses to run. No Windows
Programs Programs that honor HTTP_PROXY, HTTPS_PROXY or ALL_PROXY. Run under vpnw so far: curl and Python’s urllib. Expected to work the same way, and tested in the Beta’s agent recipes: wget, git, pip, requests, httpx, Go’s net/http, Node.js 22.21 and later or 24.5 and later A program that ignores them can’t connect when sealed. Git over SSH doesn’t use them
Protocols TCP, through HTTP CONNECT, plain HTTP and SOCKS5 UDP and QUIC are refused. No incoming connections
Paths Direct; SOCKS5 or HTTP proxies, with names resolved locally or at the exit One path per run. WireGuard, and proxies reached over TLS (https://), are Beta work
Policy Names, wildcards, addresses, ranges, ports; deny_private; a default of allow or deny No rules on URL paths or content: vpnw decides on destinations only
Record Twelve event types, text or JSON Lines, schema version 1 The schema is frozen only at v1.0
learn A draft from the last trace or any trace file It allows what the program did, leaks included
Unix sockets Refused in sealed runs; socketpair works --allow-unix-sockets is all or nothing
Files Not limited The Beta’s file-system layer
Privileges No root needed Ubuntu 23.10 and later need an AppArmor profile, or sudo. Running as root is not recommended

The Demo

The demo is six runs of the Alpha on Linux, recorded on September 29, 2026 with the Linux demo kit. A stand-in coding agent gets a task file with a hidden instruction to send a deploy token to an attacker, and the six steps show trace, learn, guard, a route through the office, a script that ignores proxy settings and a request for cloud credentials. The agent, the servers, the office exit, a local service and the attacker are small Python programs in a private network namespace, at addresses that exist only there.

The live demo replays those runs in the browser, line for line, and under the replay it runs the Alpha’s own Go code (the policy engine, the request plan and learn), compiled to WebAssembly with TinyGo. Two checks keep the browser honest: the request plan is compared with the running broker in 119 cases, and the TinyGo build gives the same answers as the standard Go build on 471 calls.

Each part of the demo has its own case study: the coding agent, the office route, the sealed sandbox and cloud metadata. The same demo also runs offline with the Linux demo kit, on a machine that allows unprivileged user namespaces. So far it has run on the one test machine, as root and as an ordinary user. The kit is available on request.

Where It Stands

Maturity by Part

Part Maturity Evidence Gap to close
Sealed sandbox, Linux Working; tested on one machine 14 of 14 ways out blocked; 6 planted bugs in the sandbox caught IPv6, more kernels and distributions, arm64, the AppArmor profile, walls that report
Policy engine Working, tested 93.2% coverage; two fuzz targets; 9 planted bugs caught Tools that make reviewing a policy easier
Broker Working 84.4% coverage; fuzzed; checked against the plan in 119 cases Speed with many parallel connections
Paths Direct and proxy Path tests; a dead path is refused before start WireGuard; switching to another exit when one is down
Record Working, schema version 1 Round-trip tests; no passwords in events A frozen format; records that can’t be edited without it showing
learn Working 94.8% coverage Easier review of drafts
macOS Route and trace only Compiles guard on macOS
Files Not started None The file-system layer
Packaging A zip with the demo The x86-64 kit ran as root and as an ordinary user on the test machine Packages for Linux and macOS; a first run of the arm64 kit
Use by real agents None None The Beta’s four weeks

Readiness Level

On the European Commission’s technology readiness scale, which runs from TRL 1 to TRL 9, the Alpha sits at TRL 4, “technology validated in lab”. The engine works, and it has been tested in a lab: one machine, stand-in servers, a stand-in agent. The Beta aims at TRL 5, “technology validated in relevant environment”: real agents doing real work on the machines and CI runners where agents actually run.

How Far From a First Release

An estimate, and only an estimate: the Alpha is perhaps a quarter of the way to a first production release. The hardest design questions have working answers. The core is small, reasonably fast, and well tested on one machine. Most of the remaining work is the unglamorous part: other systems, WireGuard, limits on files, packages, an outside review and pilots. The Beta should take about 14 weeks with two to three people, and a first release about 6 to 9 months after that.

Against the Alternatives

VPN Works Alpha Anthropic’s sandbox runtime vopono
What it is One program’s network: a path, a policy and a record A sandbox for agents’ commands, built for Claude Code: files and network Runs applications through VPN tunnels, each in a temporary network namespace
Where traffic goes Direct, or a proxy you choose; WireGuard in the Beta Straight out through its own proxies. Upstream proxies are not yet supported in its new configuration format A VPN: WireGuard, OpenVPN, Cloudflare Warp or an enterprise VPN client
Network policy Names, wildcards, addresses, ranges, ports and deny_private, in a fixed order Domain allow and deny lists None documented
Record of connections Every connection with its decision, as text and JSON Lines, and learn drafts policies from it Violation logs on macOS; on Linux, tracing by hand None documented
Files Not limited yet Read and write limits, with protected files None documented
Unix sockets Refused, or all allowed Refused by default. On Linux all or nothing, as in vpnw; on macOS, a list of allowed sockets None documented
Platforms Linux for sealed runs; macOS compiles, route and trace only Linux (bubblewrap and seccomp) and macOS (Seatbelt) Linux
License Not chosen Apache 2.0, a research preview GPL-3.0 or later

Bitdefender’s VPN for AI agents takes another route: a free beta for Macs with Apple silicon that sends each prompt’s traffic through its own tunnel on Bitdefender’s servers, with a broker for MCP connections.

The fair reading: Anthropic’s sandbox runtime is further along, limits files as well as the network, and uses the same kind of Linux sandbox as vpnw. vopono is mature at putting one application on a VPN, but it doesn’t decide or record anything. vpnw’s case rests on combining a path of your choice, a policy with a floor and a record that drafts the next policy, for any program that honors proxy settings, on networks a team already has. So far that is shown on one Linux machine. Sources: the sandbox runtime README and the vopono README.

Technical Risks the Alpha Revealed

  • Programs that ignore proxy settings. Sealed, they fail instead of leaking, which is safe but not useful. Some agents’ tools will need recipes, and some may not work under guard at all.
  • Files are shared. The sandbox covers the network. A sealed program can still read secrets its user can read and write files that run later outside the sandbox. The file-system layer is Beta work, and until then that is the widest gap.
  • Silent walls. The kernel refuses a bypass attempt before vpnw hears of it, so the record shows a quiet run. The Beta tries to make the walls report.
  • Ubuntu’s AppArmor rule. Ubuntu 23.10 and later restrict unprivileged user namespaces, and many agents run on Ubuntu. Without the profile the Beta will ship, sealed runs there need sudo.
  • macOS. There are no network namespaces, and the system sandbox other agent tools use there is a different kind of wall. guard on macOS has to prove itself against the same bypass matrix.
  • The first dependency. A user-space WireGuard path would bring the first third-party code into an engine that has none today.
  • Many connections at once. Two extra hops per connection cost little one at a time, but they add up for a browser agent with dozens of connections open.

Every figure on this page was measured on the Alpha code on September 29, 2026 unless it is marked as an estimate. The scripts that reproduce them are kept with the Alpha’s source.