Plugins
Plugins are the apps of VPN Works. The kernel, vpnw, seals, decides, carries and records. Everything else comes as a plugin: a small WebAssembly module that vpnw runs in a sandbox, with no files, no network and no environment, reaching the kernel only through the calls its manifest asks for.
That holds for the plugins that ship with vpnw too. The console is a plugin. Policy drafting is a plugin. They run in the same sandbox and use the same interface as a plugin you write yourself, with no extra calls.

Built In
Trace, an Observer. It’s the console: every connection prints as it happens, with what the program asked for, what it resolved to, whether it opened, and how much went each way.
vpnw trace -- ./my-agent
vpnw guard --policy agent.toml --set trace.level=decisions -- ./my-agent
Learn, an Advisor. It reads traces and drafts a policy that allows exactly what the program reached, and flags what a person should look at: raw IP addresses, uploads much bigger than the downloads, destinations on the private network.
vpnw learn --name my-agent -o my-agent.toml
Ledger, an Observer. It seals the run’s events into a file of its own as they happen, each linked to the ones before it by a hash chain, and copies its checkpoints into the trace. Ledger
vpnw guard --plugin ledger --policy agent.toml -- ./my-agent
ledger-verify, an Advisor. It checks a ledger and names the line anyone changed, or says the run was cut short.
vpnw verify ~/.local/state/vpnw/ledger/r-3f9a1c.ledger
rotate, a Tunnel. It hands the kernel the WireGuard configs in a folder, one after the other, kept as long as you say; the kernel runs every tunnel itself and moves to the next config when one is due or stops answering. Rotate in 0.8.0
vpnw guard --tunnel rotate --set rotate.every=10m --policy scraper.toml -- ./scraper
A Tunnel plugin decides where a program’s traffic goes, so choose one as you would a VPN provider. It has no network of its own, but its configs reach the network for it: the kernel looks up their endpoints and sends WireGuard handshakes to them.
Next
Plugins for gateway mode come with it, and Tunnel plugins for particular VPN providers can follow rotate. The order is on the roadmap.
Plugin Types
| Type | vpnw calls it | Can it change a decision? |
|---|---|---|
| Observer | for every event of a run, as it happens | no |
| Advisor | for every event of the traces it’s given, or every line of its files, then for its advice | no |
| Guard | for every connection the policy allowed | yes, it can refuse |
| Tunnel | when the kernel needs a WireGuard config: at the start, when the one in use is due or stops answering, and after one is refused or doesn’t come up | no; it chooses the way out, and the kernel checks each config and runs the tunnel |
| Exit, Identity | reserved for later releases; vpnw refuses them for now (vpnw exit itself is part of the kernel, and Observers and Guards run there as they do in a run) |
Plugins never carry traffic; that stays in the kernel. Each call into a plugin has a time budget: a Guard gets a quarter of a second to decide on a connection. A plugin that crashes or runs past its budget is stopped and the run carries on. A Guard that fails refuses everything after it.
Anything more a plugin may do, its manifest has to ask for, and there are four such permissions:
console: write to vpnw’s output.events.emit: add its own events to the run’s record.file.write, new in 0.5.0: append to one file that vpnw creates for it. The plugin can’t read the file, move around in it, or reach any other.folder.read, new in 0.8.0: list and read the files in one folder that vpnw opens for it, and nothing outside it.
Install and Run
vpnw plugin add ./geo-fence # checks the signature against keys you trust
vpnw plugin list
vpnw guard --policy agent.toml --plugin geo-fence -- ./my-agent
vpnw advise my-advisor --from last # run an Advisor on the last trace
vpnw plugin keygen, sign and trust handle the Ed25519 keys; --allow-unsigned installs a plugin without a signature, if you say so. The built-in plugins ship inside vpnw itself. Plugins install under ~/.local/share/vpnw/plugins/. Every command is in the reference.
Write One
In Go, a plugin is a few lines with the SDK and one build command. A Guard that refuses one domain and everything under it:
sdk.Register(sdk.Plugin{
Init: func(c *sdk.Config) error {
blocked = c.Settings["domain"]
return nil
},
OnDecide: func(r *sdk.Request) (bool, string) {
if blocked != "" && (r.Host == blocked || strings.HasSuffix(r.Host, "."+blocked)) {
return true, r.Host + " is under " + blocked
}
return false, ""
},
})
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o plugin.wasm .
Any language that compiles to WASI works too; the interface is documented call by call. The plugin guide
Written a plugin you’d like listed here? Write.