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
Next
| Plugin | Type | What it does | Release |
|---|---|---|---|
| Ledger | Observer | seals each run’s record as a hash chain while the run goes | 0.5.0 |
| Ledger verify | Advisor | checks a sealed record and names the exact line that was changed | 0.5.0 |
| Config rotation | Tunnel | hands WireGuard configs to the kernel from a folder, and rotates them | 0.8.0 |
Ledger needs one new permission in the plugin interface: writing to a single file that vpnw opens for the plugin. Every plugin will be able to ask for it. The order and the rest of the plan are 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, then for its advice | no |
| Guard | for every connection the policy allowed | yes, it can refuse |
| Tunnel, Exit, Identity | reserved for later releases; vpnw refuses them for now |
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.
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.