WASM Plugin System¶
NovaEdge supports custom middleware through WebAssembly (WASM) plugins. Plugins run in a sandboxed environment with no filesystem or network access, ensuring safe execution of untrusted code.
Overview¶
WASM plugins can intercept HTTP requests and responses at configurable phases:
- request — Called before routing/backend selection. Can modify request headers or short-circuit with a response.
- response — Called after the backend responds. Can modify response headers.
- both — Called in both phases.
Architecture¶
graph LR
Client --> A[Request Phase Plugins]
A --> B[Router / Backend]
B --> C[Response Phase Plugins]
C --> Client
style A fill:#e1f5ff
style B fill:#e8f5e9
style C fill:#fff4e6
Built-in Plugins¶
NovaEdge ships with 13 ready-to-use WASM plugins covering security, traffic management, observability, and protocol transformation.
Security Plugins¶
| Plugin | Phase | Description |
|---|---|---|
| Custom Auth | request | API key and HMAC signature authentication |
| Schema Validation | request | Request header and content-type validation |
| GraphQL Protection | request | Depth limiting, alias control, introspection blocking |
| PII Masking | response | Detect and redact PII patterns in response headers |
Traffic Management Plugins¶
| Plugin | Phase | Description |
|---|---|---|
| A/B Testing | both | Experiment bucketing with deterministic assignment |
| Canary Analysis | both | Traffic splitting with success/failure tracking |
| Multi-tenant Routing | request | Tenant extraction and isolation enforcement |
| Cache Invalidation | both | Surrogate-key based cache purging |
Observability Plugins¶
| Plugin | Phase | Description |
|---|---|---|
| Request ID | both | Generate and propagate unique request identifiers |
| GeoIP Enrichment | request | Add geographic headers from IP address |
| Business Metrics | both | Extract business-relevant data for metric collection |
Transformation Plugins¶
| Plugin | Phase | Description |
|---|---|---|
| Body Rewrite | response | String replacement rules for response content |
| Protocol Transform | both | SOAP/XML to REST/JSON conversion hints |
Plugin Reference¶
GeoIP Header Enrichment¶
Reads client IP address and enriches requests with geographic information based on configurable IP-to-location mappings.
Phase: request
Configuration:
| Key | Default | Description |
|---|---|---|
country_header |
X-Geo-Country |
Header name for country code |
city_header |
X-Geo-City |
Header name for city |
region_header |
X-Geo-Region |
Header name for region |
ip_header |
X-Real-IP |
Header to read client IP from |
default_country |
(empty) | Fallback country code |
mapping_N |
— | IP prefix to location: prefix=CC,City,Region |
Example:
apiVersion: novaedge.io/v1alpha1
kind: ProxyPolicy
metadata:
name: geoip-enrichment
spec:
type: WASMPlugin
targetRef:
kind: ProxyRoute
name: api-route
wasmPlugin:
source: novaedge-geoip-plugin
phase: request
config:
ip_header: "X-Forwarded-For"
default_country: "US"
mapping_0: "10.0=US,NewYork,NY"
mapping_1: "192.168=DE,Berlin,BE"
Request ID Propagation¶
Generates unique request identifiers if not present and propagates them through request and response headers.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
header_name |
X-Request-ID |
Header name for the request ID |
prefix |
req |
Prefix for generated IDs |
propagate |
true |
Copy request ID to response headers |
Example:
wasmPlugin:
source: novaedge-requestid-plugin
phase: both
config:
header_name: "X-Request-ID"
prefix: "edge"
propagate: "true"
Response Body Rewrite¶
Sets rewrite rule headers for the host middleware to apply string replacements on response bodies.
Phase: response
Configuration:
| Key | Default | Description |
|---|---|---|
match_N |
— | String to match (N=0,1,2,...) |
replace_N |
— | Replacement string for match_N |
content_types |
(all) | Comma-separated content types to process |
max_rules |
32 |
Maximum number of rewrite rules |
Example:
wasmPlugin:
source: novaedge-bodyrewrite-plugin
phase: response
config:
match_0: "http://internal.example.com"
replace_0: "https://api.example.com"
match_1: "staging"
replace_1: "production"
content_types: "text/html,application/json"
Custom Authentication¶
Validates API keys or HMAC signatures on incoming requests. Supports header-based and query-parameter-based key extraction.
Phase: request
Configuration:
| Key | Default | Description |
|---|---|---|
mode |
apikey |
Authentication mode: apikey or hmac |
api_keys |
— | Comma-separated list of valid API keys |
key_header |
X-API-Key |
Header containing the API key |
key_query_param |
api_key |
Query parameter containing the API key |
hmac_header |
X-HMAC-Signature |
Header containing the HMAC signature |
hmac_secret |
— | Shared secret for HMAC verification |
reject_status |
401 |
HTTP status code for rejected requests |
reject_body |
{"error":"unauthorized"} |
Response body for rejected requests |
realm |
novaedge |
Authentication realm for WWW-Authenticate |
Example:
wasmPlugin:
source: novaedge-customauth-plugin
phase: request
priority: 10
config:
mode: "apikey"
api_keys: "sk-abc123,sk-def456,sk-ghi789"
key_header: "Authorization"
reject_status: "403"
Schema Validation¶
Validates incoming requests against configurable rules including required headers, header value patterns, content type enforcement, and method restrictions.
Phase: request
Configuration:
| Key | Default | Description |
|---|---|---|
required_headers |
— | Comma-separated list of required headers |
header_pattern_N |
— | header_name:wildcard_pattern (N=0,1,...) |
content_type |
— | Required Content-Type (prefix match) |
max_content_length |
— | Maximum Content-Length in bytes |
allowed_methods |
— | Comma-separated allowed HTTP methods |
reject_status |
400 |
HTTP status for rejected requests |
max_patterns |
32 |
Max number of header pattern entries |
Example:
wasmPlugin:
source: novaedge-schemavalidation-plugin
phase: request
config:
required_headers: "Authorization,Content-Type"
content_type: "application/json"
allowed_methods: "GET,POST,PUT,DELETE"
max_content_length: "1048576"
header_pattern_0: "Authorization:Bearer *"
PII Data Masking¶
Scans response headers for personally identifiable information (email addresses, phone numbers, SSNs, credit card numbers) and masks them.
Phase: response
Configuration:
| Key | Default | Description |
|---|---|---|
mask_email |
true |
Mask email address patterns |
mask_phone |
true |
Mask phone number patterns |
mask_ssn |
true |
Mask Social Security Number patterns |
mask_creditcard |
true |
Mask credit card number patterns |
mask_char |
* |
Character used for masking |
Example:
wasmPlugin:
source: novaedge-piimask-plugin
phase: response
config:
mask_email: "true"
mask_phone: "true"
mask_ssn: "true"
mask_creditcard: "true"
GraphQL Protection¶
Protects GraphQL endpoints from abuse by enforcing query depth limits, alias restrictions, introspection blocking, and field-level access control.
Phase: request
Configuration:
| Key | Default | Description |
|---|---|---|
max_depth |
10 |
Maximum nesting depth allowed |
max_aliases |
5 |
Maximum number of aliases per query |
blocked_fields |
— | Comma-separated list of blocked field names |
introspection |
false |
Allow introspection queries (true/false) |
graphql_path |
/graphql |
Path that identifies GraphQL endpoints |
Example:
wasmPlugin:
source: novaedge-graphqlprotect-plugin
phase: request
config:
max_depth: "5"
max_aliases: "3"
blocked_fields: "__schema,__type,_debug"
introspection: "false"
A/B Testing¶
Assigns users to experiment variants deterministically based on request fingerprinting (User-Agent + IP hash), with cookie-based sticky sessions.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
experiment_name |
default |
Name of the experiment |
variants |
control,variant_a |
Comma-separated variant names |
cookie_name |
ab_bucket |
Cookie name for sticky assignment |
header_name |
X-AB-Variant |
Header for variant assignment |
traffic_split |
50 |
Percentage of traffic for first variant |
Example:
wasmPlugin:
source: novaedge-abtesting-plugin
phase: both
config:
experiment_name: "checkout-redesign"
variants: "control,new_checkout"
traffic_split: "80"
cookie_name: "ab_checkout"
Business Metrics¶
Extracts business-relevant data from requests and responses, setting metric label headers for the Prometheus metrics collector to consume.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
metric_prefix |
business |
Prefix for metric names |
extract_headers |
— | Comma-separated headers to extract as labels |
path_pattern |
— | Path prefix to match for metric collection |
Example:
wasmPlugin:
source: novaedge-businessmetrics-plugin
phase: both
config:
metric_prefix: "api"
extract_headers: "X-Customer-ID,X-Plan-Tier"
path_pattern: "/api/v1"
Canary Analysis¶
Tags requests with canary version headers based on traffic split configuration, and tracks success/failure on responses for canary analysis.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
canary_header |
X-Canary-Version |
Header for version assignment |
canary_version |
(required) | Version identifier for canary (e.g., v2) |
stable_version |
v1 |
Version identifier for stable |
traffic_percent |
10 |
Percentage of traffic to canary |
error_threshold |
5 |
Error percentage threshold for alerting |
Example:
wasmPlugin:
source: novaedge-canaryanalysis-plugin
phase: both
config:
canary_version: "v2.1.0"
stable_version: "v2.0.0"
traffic_percent: "10"
error_threshold: "5"
Multi-tenant Routing¶
Extracts tenant identifiers from headers, path prefixes, or subdomains and enforces tenant isolation through request header enrichment and access control.
Phase: request
Configuration:
| Key | Default | Description |
|---|---|---|
tenant_header |
X-Tenant-ID |
Header for tenant identification |
tenant_source |
header |
Source: header, path, or subdomain |
path_prefix_strip |
true |
Strip tenant prefix from path |
allowed_tenants |
— | Comma-separated list of allowed tenants |
default_tenant |
— | Fallback tenant if none found |
Example:
wasmPlugin:
source: novaedge-multitenant-plugin
phase: request
config:
tenant_source: "subdomain"
allowed_tenants: "acme,globex,initech"
Cache Invalidation¶
Handles PURGE requests by validating source IPs and extracting surrogate keys. On responses, propagates surrogate keys from backends for the cache layer.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
purge_methods |
PURGE |
Comma-separated methods that trigger purge |
surrogate_header |
Surrogate-Key |
Header containing surrogate keys |
cache_tags_header |
Cache-Tag |
Header for cache tags |
allowed_purge_ips |
127.0.0.1 |
Comma-separated IPs allowed to purge |
Example:
wasmPlugin:
source: novaedge-cacheinvalidation-plugin
phase: both
config:
allowed_purge_ips: "10.0.0.1,10.0.0.2"
surrogate_header: "Surrogate-Key"
Protocol Transformation¶
Detects SOAP/XML requests and sets transformation hint headers for the host middleware to convert between SOAP/XML and REST/JSON formats.
Phase: both
Configuration:
| Key | Default | Description |
|---|---|---|
mode |
soap-to-rest |
Transformation mode: soap-to-rest or xml-to-json |
soap_action_header |
SOAPAction |
Header containing the SOAP action |
target_content_type |
application/json |
Target content type after transformation |
strip_namespace |
true |
Remove XML namespaces during transformation |
Example:
wasmPlugin:
source: novaedge-protocoltransform-plugin
phase: both
config:
mode: "soap-to-rest"
strip_namespace: "true"
Guest ABI¶
WASM plugins must export the following functions:
| Export | Signature | Description |
|---|---|---|
on_request_headers |
() -> () |
Called during request phase |
on_response_headers |
() -> () |
Called during response phase |
malloc |
(size: i32) -> ptr: i32 |
Allocate guest memory |
free |
(ptr: i32) -> () |
Free guest memory |
Host Functions¶
The host exposes these functions under the novaedge module:
| Function | Signature | Description |
|---|---|---|
get_request_header |
(name_ptr, name_len, val_ptr, val_cap) -> val_len |
Read a request header |
set_request_header |
(name_ptr, name_len, val_ptr, val_len) |
Set a request header |
get_response_header |
(name_ptr, name_len, val_ptr, val_cap) -> val_len |
Read a response header |
set_response_header |
(name_ptr, name_len, val_ptr, val_len) |
Set a response header |
get_method |
(buf_ptr, buf_cap) -> method_len |
Get HTTP method |
get_path |
(buf_ptr, buf_cap) -> path_len |
Get request path |
get_config_value |
(key_ptr, key_len, val_ptr, val_cap) -> val_len |
Read plugin config value |
log_message |
(level, msg_ptr, msg_len) |
Log a message (0=debug, 1=info, 2=warn, 3=error) |
send_response |
(status_code, body_ptr, body_len) |
Short-circuit with a response |
Writing a Custom Plugin¶
TinyGo Template¶
//go:build tinygo.wasm
package main
import "unsafe"
//go:wasmimport novaedge get_request_header
func getRequestHeader(namePtr, nameLen, valPtr, valCap uint32) uint32
//go:wasmimport novaedge set_request_header
func setRequestHeader(namePtr, nameLen, valPtr, valLen uint32)
//go:wasmimport novaedge get_config_value
func getConfigValue(keyPtr, keyLen, valPtr, valCap uint32) uint32
//go:wasmimport novaedge log_message
func logMessage(level, msgPtr, msgLen uint32)
//go:wasmimport novaedge send_response
func sendResponse(statusCode, bodyPtr, bodyLen uint32)
//export on_request_headers
func onRequestHeaders() {
// Your request-phase logic here
name := "X-Custom-Header"
value := "hello"
np, nl := ptrLen(name)
vp, vl := ptrLen(value)
setRequestHeader(np, nl, vp, vl)
}
//export on_response_headers
func onResponseHeaders() {
// Your response-phase logic here
}
//export malloc
func wasmMalloc(size uint32) uint32 {
buf := make([]byte, size)
return uint32(uintptr(unsafe.Pointer(&buf[0])))
}
//export free
func wasmFree(_ uint32) {}
func ptrLen(s string) (uint32, uint32) {
if len(s) == 0 {
return 0, 0
}
b := []byte(s)
return uint32(uintptr(unsafe.Pointer(&b[0]))), uint32(len(b))
}
func main() {}
Building¶
# Install TinyGo (https://tinygo.org/getting-started/install/)
# Build a single plugin
tinygo build -o plugin.wasm -target=wasi -scheduler=none -gc=conservative -opt=z -no-debug ./
# Build all built-in plugins
cd plugins/wasm && make all
Deploying a Plugin¶
1. Store the WASM binary in a ConfigMap¶
2. Create a ProxyPolicy¶
apiVersion: novaedge.io/v1alpha1
kind: ProxyPolicy
metadata:
name: my-wasm-plugin
spec:
type: WASMPlugin
targetRef:
kind: ProxyRoute
name: api-route
wasmPlugin:
source: my-wasm-plugin # ConfigMap name
phase: request
priority: 50
failClosed: true
config:
key1: value1
key2: value2
3. Reference in a route pipeline¶
apiVersion: novaedge.io/v1alpha1
kind: ProxyRoute
metadata:
name: api-route
spec:
hostnames: ["api.example.com"]
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-backend
pipeline:
middleware:
- type: wasm
name: my-wasm-plugin
priority: 50
Sandbox Constraints¶
- Memory: Limited to configurable max pages (default 256 pages = 16 MB)
- No filesystem access: WASM modules cannot read/write files
- No network access: WASM modules cannot make outbound connections
- No system calls: Only NovaEdge host functions are available
- Timeout: Plugin execution is bounded (default 5 seconds)
- Instance pooling: 4 instances per plugin for concurrent request handling
Plugin Configuration¶
Plugin configuration is passed as a map[string]string via the config field in the ProxyPolicy. Plugins read configuration values using the get_config_value host function. This design keeps plugins stateless and configuration-driven.
Resource Limits¶
| Setting | Default | Description |
|---|---|---|
maxMemoryPages |
256 (16 MB) | Maximum WASM linear memory |
executionTimeout |
5s | Per-invocation execution timeout |
failClosed |
false | Return 503 on plugin errors (vs. fail-open) |
Metrics¶
WASM plugin metrics are exposed via Prometheus:
| Metric | Type | Description |
|---|---|---|
novaedge_wasm_plugin_execution_duration_seconds |
Histogram | Plugin execution duration |
novaedge_wasm_plugin_execution_total |
Counter | Total executions by plugin/phase/status |
novaedge_wasm_plugin_errors_total |
Counter | Total errors by plugin/phase |
novaedge_wasm_plugins_loaded |
Gauge | Number of loaded plugins |
novaedge_wasm_plugin_instance_pool_size |
Gauge | Instance pool size per plugin |
novaedge_wasm_plugin_timeouts_total |
Counter | Total execution timeouts |