requires: wasm
The wasm HTTP filter loads a WebAssembly module into the proxy and calls it on every request and response passing through the filter chain. Use it when you need custom logic in-process — header and body mutation, bespoke auth, callouts — without forking or rebuilding Arion.
Before you begin
- Arion must be built with the
wasmCargo feature (cargo build -p arion-proxy --features wasm). A binary without it refuses to start when the config contains awasmfilter. - You need a compiled plugin — a
.wasmmodule built with the Rust SDK for thewasm32-unknown-unknowntarget.
Enable it
Add the filter to http_filters in the HTTP connection manager, before the router:
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
config:
name: auth_filter
root_id: auth_filter
vm_config:
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /etc/arion/auth_filter.wasm
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
config.name and root_id identify the plugin. runtime is accepted for Envoy compatibility but its value is not used — Arion always runs modules on its embedded Wasmtime engine. vm_config.vm_id and allow_precompiled are optional. code takes a local data source: filename (a .wasm file), inline_bytes (base64 module bytes), or inline_string (WAT text).
Variations
Pass configuration to the plugin
configuration carries an arbitrary string (typically JSON) that the plugin reads at startup with get_plugin_config(). It must be wrapped as a google.protobuf.StringValue:
config:
name: auth_filter
root_id: auth_filter
configuration:
"@type": type.googleapis.com/google.protobuf.StringValue
value: '{"header": "x-tenant", "required": true}'
vm_config:
runtime: envoy.wasm.runtime.v8
code:
local:
filename: /etc/arion/auth_filter.wasmDisable on a route
There is no per-route WASM configuration, but any filter can be switched off for a route with a FilterConfig wrapper keyed by the filter's chain name:
typed_per_filter_config:
envoy.filters.http.wasm:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
disabled: trueInstance lifecycle
The module is compiled once at configuration load. Instances are created lazily: the first request that needs one instantiates it and runs the plugin's on_plugin_start hook; when the transaction completes, the instance is reset and parked in a lock-free pool (up to 1024 per filter) for the next request. Each in-flight request holds its own instance, so plugin state is request-local unless you use shared state. Which hooks run is discovered from the module's exports: a plugin that only exports on_request_headers costs nothing on the response path.
Verify
With the SDK's dummy_filter example module loaded, an unauthenticated request is answered by the plugin itself:
curl -i http://127.0.0.1:8080/HTTP/1.1 401 Unauthorized
...
401 Unauthorized: missing Authorization headerLimits
- Body hooks fully buffer the request or response body before the plugin sees it; there is no streaming body API.
codemust be a local data source. Remote code and theenvironment_variablesource are rejected.- Envoy's
fail_open,failure_policy,reload_config,capability_restriction_config, and VMenvironment_variablesfields are not supported; a config using them refuses to start (see Envoy compatibility). - Per-route control is disable-only — no per-route plugin configuration.
- A plugin runtime error fails the request with a 500; there is no fail-open mode.
Field-by-field details are in the wasm filter reference; to write the module itself, continue with the Rust SDK.