Episode 15 goes beyond the built-in CRDs: when to safely use EnvoyFilter, building Proxy-WASM filters for custom logic, how to register them via extension providers and the Telemetry API, and examples of a header enricher and a telemetry exporter.

Istio already has many CRDs, but the real world always has special needs: adding custom headers, calling a custom authorization service, or exporting business metrics. Episode 15 covers two ways to extend the mesh: EnvoyFilter for direct patching and Proxy-WASM for custom filters running in a sandbox.
The main message of this episode: with great power comes great responsibility. Both tools must be used with discipline.
EnvoyFilter is a direct patch to Envoy configuration. Consider using it only when all of the following fail:
EnvoyFilter is prone to breaking during Envoy upgrades because filter names, config structures, and APIs change between versions. The safety rules:
istio-system namespace so it is visible as mesh configuration.match as specific as possible.istioctl proxy-config listener productpage-abc123
istioctl proxy-config cluster productpage-abc123istioctl proxy-config listener shows the real listener structure. Develop your EnvoyFilter from this structure, not from memory.
Native Envoy filters must be written in C++ and compiled into the Envoy binary itself — impractical. Proxy-WASM (WASM) lets filters be written in other languages (Rust, Go, C++, AssemblyScript), compiled to WebAssembly, and loaded by Envoy at runtime inside a safe sandbox. Istio supports it through the WasmPlugin API.
The simplest example: a Rust filter that adds a header to the request. After compiling it to filter.wasm, register it:
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: header-enricher
namespace: istio-system
spec:
selector:
matchLabels:
app: productpage
url: oci://registry.example.com/mesh/header-enricher:v1
phase: AUTHN
pluginConfig:
headerName: x-tenant
headerValue: tenant-aWasmPlugin registers a WASM module and applies it to workloads matching the selector. The url: oci:// loads the module from an OCI registry — the recommended pattern because it can be versioned like an image.
Besides WasmPlugin, extensions can be attached through the Telemetry API with custom metrics:
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
name: custom-metrics
namespace: istio-system
spec:
metrics:
- providers:
- name: prometheus
overrides:
- match:
metric: REQUEST_COUNT
tagOverrides:
tenant:
value: "istio_attributize(request.headers['x-tenant'])"tagOverrides adds a tenant dimension to the request count metric from a header. The result: you can measure per-tenant traffic right in Prometheus — a practical example of a telemetry exporter without writing a new filter.
When you have many WASM modules, register providers through MeshConfig extensionProviders so they can be used consistently:
spec:
meshConfig:
extensionProviders:
- name: tenant-ext
wasm:
url: oci://registry.example.com/mesh/tenant-ext:v1
imagePullPolicy: IfNotPresentThis provider can then be referenced from multiple WasmPlugins. Manage versions here, not in each plugin — it is easier to upgrade and audit.
Warning
WASM still must be tested against the Envoy version used by Istio. A module compiled for a different proxy-wasm ABI can fail to load silently. Check the istio-proxy logs on first load.
Episode 15 opened the door to extensibility: using EnvoyFilter with strict criteria and safeguards, building sandboxed and OCI-versioned Proxy-WASM filters, and integrating them through WasmPlugin, extension providers, and the Telemetry API.
Key takeaways:
proxy-config output, not from memory.oci:// URLs allow module versioning like images.In the next episode, episode 16, we will unite many clusters: multi-cluster and mesh federation — primary-remote and replicated control plane topologies, the east-west gateway, cross-cluster service export, and establishing trust domains between meshes.