Status: acceptance plan for the 1.7 Wasm line. These examples are not all
implemented in 1.7.0; they are required outcomes before the 1.7 line is
considered complete.
Fluxheim is not trying to embed Tcl, Lua, SPOE, or VCL interpreters. The goal is capability parity through a small typed Wasm ABI with explicit resource limits, redaction, and deterministic failure behavior.
Every family below must have:
- a documented operator-facing example;
- a real Wasm plugin fixture checked into the repository;
- a Fluxheim config fixture that attaches the plugin to a vhost or route;
- a live smoke script that starts Fluxheim, sends real HTTP traffic, and validates the observable result;
- unit tests for ABI encoding/decoding and rejection paths;
- release-gate coverage through
scripts/test_starter.py.
Capability target:
- conditional allow, deny, or continue decisions;
- bounded synthetic deny/error response;
- simple route classification from host, path, method, trusted client IP, TLS facts, and selected runtime facts;
- no direct upstream address control and no direct TLS verification control.
Representative example:
If the request path starts with /admin and the restored client IP is outside
the allowed CIDR set, return 403 with a small synthetic response. Otherwise
continue to the configured route.
Required live test:
- allowed client receives the origin response;
- denied client receives the plugin's 403 response;
- plugin timeout/trap follows the configured fail mode;
- the plugin cannot read request bodies, filesystem paths, admin tokens, or upstream TLS secrets.
Target release: v1.7.1.
Status: the checked-in examples/wasm/irules-access-policy.wat and
examples/wasm/irules-access-policy.toml pair packages the bounded
access-decision subset as a runnable v1.7.9 migration example. Fluxheim owns
host/path/method/trusted-client/TLS classification; the route-scoped plugin can
continue, allow, or deny and cannot inspect arbitrary request state. The live
listener smoke proves an unattached public request reaches origin, the attached
admin request receives the plugin's 403 before origin dispatch, and a trapping
plugin fails closed.
Capability target:
- request header mutation before upstream selection;
- response header mutation before the response is sent;
- bounded header count, name, and value lengths;
- explicit redaction rules for sensitive headers.
Representative example:
Add x-policy-tier based on the request path, remove x-powered-by from the
response, and attach a short response header that identifies the selected
policy branch.
Required live test:
- origin observes the plugin-added request header;
- client observes the plugin-added response header;
- oversized or forbidden header mutations are rejected;
- Authorization, Cookie, Set-Cookie, private keys, and admin credentials are not exposed to the plugin unless a future explicit capability allows it.
Target release: v1.7.2.
Status: implemented in v1.7.2 for the bounded preview ABI. The live listener
coverage is in
crates/fluxheim-server/src/native_http1_route_proxy_tests/wasm.rs and proves
x-policy-tier request mutation, x-fluxheim-policy-branch response mutation,
x-powered-by response removal, and fail-closed rejection of forbidden header
mutation IDs. The ABI remains symbolic and does not expose raw sensitive
headers or bodies.
The v1.7.9 operator example is checked in as
examples/wasm/openresty-header-policy.wat with a complete config fixture. The
shared policy-example smoke compiles that exact source and proves the origin
receives x-policy-tier: gold, the client receives
x-fluxheim-policy-branch: gold, upstream x-powered-by is removed, and an
unknown header mutation ID fails closed before origin dispatch.
Capability target:
- bounded typed decisions for pool choice, persistence-key choice, mirror/shadow enablement, and deny/pass/continue outcomes;
- no arbitrary network I/O from the plugin;
- all decisions constrained to configured routes, pools, and mirrors.
Representative example:
Choose a canary pool for requests carrying x-canary: 1, derive a persistence
key from a bounded request header, and mirror only safe GET requests to a
configured shadow route.
Required live test:
- two local origins prove the selected pool is honored;
- persistence key keeps repeated requests pinned as configured;
- mirror/shadow decisions never recurse and only target configured mirrors;
- a plugin trying to choose an unknown pool is rejected deterministically.
Target release: v1.7.3.
Status: first subset implemented in v1.7.3 for native HTTP/1
route-decision hooks. The current live test uses two local origins and a
configured canary route branch selected by a bounded x-canary: 1 signal.
The hook can continue, deny, or select a configured matching branch; arbitrary
pool names, persistence keys, and mirror/shadow target decisions remain staged
for later 1.7.x slices.
The v1.7.9 runnable mapping is checked in as
examples/wasm/haproxy-spoe-routing-policy.wat with a complete config fixture.
The shared live smoke compiles that source and proves configured canary route
selection, unavailable-branch fail-closed behavior, selected-route policy
enforcement, native load-balancer selection, managed-cookie persistence, and
safe configured mirror routing. Backend addresses, arbitrary persistence keys,
and mirror URLs remain outside the guest ABI.
Capability target:
- lookup/admission decision: bypass, pass, continue, or deny;
- bounded cache-key component output with low-cardinality enforcement;
- store-admission decision for TTL override, cache tags, safe response-header mutation, and rejection of unsafe responses;
- no raw cache-object access.
Representative example:
Bypass cache for preview=true, add a bounded device-class key component,
override TTL for image responses up to the configured maximum, and tag objects
with a small plugin-provided cache tag.
Required live test:
- preview requests bypass cache;
- normal requests produce MISS then HIT;
- device-class key component changes the cache key only within configured cardinality bounds;
- plugin TTL override is clamped to route/vhost cache limits;
- plugin cache tags are visible to purge/status tooling;
- unsafe Set-Cookie or private responses remain uncacheable.
Target line: starts in v1.7.4 with cache lookup/pass/bypass/deny decisions
and cache-store skip/deny decisions. v1.7.5 adds bounded symbolic cache-key
components with live low-cardinality variant tests plus fixed-ID TTL/tag/header
store metadata and symbolic response content-type inspection with live TTL
expiry and stored-header HIT coverage. The
checked-in examples/wasm/cache-lookup-policy.wat and
examples/wasm/cache-store-policy.wat fixtures plus the
examples/wasm/cache-policy.toml config template document this subset, and the
native HTTP/1 test suite compiles the example sources directly. Richer
store-admission mutation remains follow-up 1.7.x cache-policy work.
For v1.7.9, the example TOML is schema-validated and the shared live policy
smoke proves pass versus MISS/HIT behavior, bounded device-class variants,
image-only TTL/tag/stored-header mutation, TTL expiry, non-image isolation,
tag-based purge through Fluxheim's cache tooling, and fail-closed unknown or
duplicate mutation IDs. This is typed VCL-like capability parity; it does not
embed VCL or expose raw cache objects.
Before 1.7 is complete:
- all four example families must be runnable from
scripts/test_starter.py; - the stable or deep release gate must run the applicable Wasm smokes;
- docs must state that Fluxheim provides capability parity, not syntax compatibility with F5 iRules, Lua/OpenResty, HAProxy SPOE, or VCL;
- unsupported host calls, unknown ABI versions, unsupported plugin phases, and invalid config combinations must fail deterministically at config load or plugin-load time;
- every example must have a matching negative test that proves the sandbox does not expose filesystem, network, env, admin APIs, secrets, request bodies, or raw cache objects without explicit future capabilities.
v1.7.9 provides scripts/build_wasm_policy_examples.sh for deterministic
source-to-module builds and digest output, while scripts/smoke_wasm_all.sh
is the single orchestration entry used by both scripts/test_starter.py and
the opt-in stable/deep release gate. This avoids treating multiple script paths
as arguments to one launcher command and keeps human and CI coverage aligned.
v1.7.10 also exposes each family independently through wasm-irules,
wasm-openresty, wasm-haproxy-spoe, and wasm-vcl test-starter entries.
These focused entries invoke the same family checks used by the aggregate
smoke; they are not reduced documentation-only substitutes.
The aggregate smoke also runs the arbitrary guest-ID decoder property test. This keeps symbolic host-call and guest-outcome decoding totality inside the same deep-gate evidence as the runnable examples.
The standalone scripts/smoke_wasm_policy_examples_binary.sh proof also loads
the generated files through a private plugin root and exact SHA-256 pins,
starts the real Fluxheim binary from a file-based config, and sends HTTP
traffic through all four vhosts. The in-process listener tests remain the
faster detailed regressions; both layers are required by the complete smoke.