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.

VPN Works as an operating system: programs on top, plugins as apps, the plugin interface as system calls, the vpnw kernel, paths as drivers, and Linux or macOS underneath

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.