Skip to content

feat: WebAssembly plugin support with end-to-end host invocation#65

Open
monshri wants to merge 23 commits into
contextforge-org:devfrom
monshri:feat/plugin-wasm-e2e
Open

feat: WebAssembly plugin support with end-to-end host invocation#65
monshri wants to merge 23 commits into
contextforge-org:devfrom
monshri:feat/plugin-wasm-e2e

Conversation

@monshri

@monshri monshri commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

WASM Plugin End-to-End Support

Resolves #21 & #24

Summary

This PR introduces a complete WebAssembly (WASM) plugin sandbox system for the CPEX framework. It enables running untrusted or third-party plugins inside isolated Wasmtime sandboxes while maintaining full compatibility with the existing native plugin pipeline.

Scope: 2 new crates, ~20,400 lines added across 73 files, 46+ integration/E2E tests, full benchmarking suite, and comprehensive documentation.


Motivation

The CPEX plugin framework currently runs all plugins natively with full host access. This creates a security boundary problem for third-party or community plugins that should not have unrestricted filesystem, network, or memory access. WASM sandboxing provides:

  • Isolation — plugins run in a memory-safe sandbox with no implicit host access
  • Resource control — per-invocation CPU (fuel), memory, and wall-clock limits
  • Capability gating — fine-grained network, filesystem, and env var allowlists
  • Transparent integration — WASM plugins use the same HookHandler<H> trait and PluginManager.invoke() API as native plugins

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     PluginManager                            │
│           (invoke works identically for both)                │
├──────────────────────┬──────────────────────────────────────┤
│   NativePluginFactory│      WasmPluginFactory               │
│   (existing)         │      (new — this PR)                 │
│                      │  ┌────────────────────────────────┐  │
│                      │  │  SharedEngine (wasmtime)       │  │
│                      │  │  ┌──────────┐ ┌──────────┐    │  │
│                      │  │  │ Store A  │ │ Store B  │... │  │
│                      │  │  │ (plugin) │ │ (plugin) │    │  │
│                      │  │  └──────────┘ └──────────┘    │  │
│                      │  └────────────────────────────────┘  │
└──────────────────────┴──────────────────────────────────────┘

Key design decisions:

  1. Transparent integration — WASM plugins implement HookHandler<H> and register via PluginFactory, so PluginManager.invoke() works identically for native and WASM plugins
  2. Feature-gated cpex-core — tokio/runtime deps made optional so cpex-core compiles to wasm32-wasip2 (guest SDK depends on it with default-features = false)
  3. One plugin per binary — WIT single-export constraint; Cargo feature flags select which plugin compiles
  4. Structured + custom payloads — WIT types for built-in payloads (CMF, Identity, Delegation); JSON-serialized HookPayload::Custom variant for user-defined types
  5. Shared engine, isolated stores — one wasmtime Engine + epoch ticker per factory; each plugin gets its own Store with isolated memory, fuel, and state
  6. Per-invocation resource reset — fuel and epoch deadline reset before each handle_hook call; WASM linear memory persists across calls (enables OnceLock state patterns)
  7. Credential exclusion by design#[serde(skip)] on token fields ensures raw credentials never cross the sandbox boundary

New Crates

crates/cpex-wasm-host/ — Host Runtime

The host-side runtime that loads, sandboxes, and invokes WASM plugin binaries. Integrates with cpex-core's PluginManager via the PluginFactory trait.

Module Purpose
src/factory.rs WasmPluginFactory + WasmBridgeHandler — bridges PluginManager to sandboxes
src/sandbox_manager.rs SharedEngine, SandboxManager, network policy, host-logging WIT import
src/conversions.rs Bidirectional native <-> WIT type conversions for all payload/extension types
src/policy_loader.rs SandboxPolicy struct, WASI context builder from YAML config
src/payload_registry.rs Type-erased serialization registry for custom payload types

Dependencies: wasmtime 45.0 (component-model + async), wasmtime-wasi, wasmtime-wasi-http, cpex-core, tokio, serde, tracing

crates/cpex-wasm-plugin/ — Guest SDK

The plugin-author SDK. Compiled as cdylib to wasm32-wasip2 target.

Module Purpose
src/lib.rs WIT bindings, register_wasm_plugin! macro, cpex_log! macro, __block_on sync executor
src/conversions.rs WIT -> native type conversions for the guest side
src/plugins/ 13 demo/test plugin implementations

Dependencies: wit-bindgen 0.57, cpex-core (default-features = false), serde, async-trait, chrono, zeroize


WIT Interface Contract

Package: cpex:plugin (656 lines in crates/cpex-wasm-host/wit/world.wit)

export handle-hook: func(
    hook-name: string,
    payload: hook-payload,
    extensions: extensions,
    ctx: plugin-context
) -> hook-result;

Payload variant types:

  • cmf — structured field-by-field CMF MessagePayload
  • identity — structured IdentityPayload
  • delegation — structured DelegationPayload
  • custom — JSON bytes with type discriminator (user-defined payloads)

Imports: WASI P2 (io, clocks, http, filesystem, sockets, random, cli) + custom host-logging interface


Security Model (5-Layer Defense-in-Depth)

Layer Mechanism What it prevents
1 Pre-invocation capability filtering Plugin sees only extensions it's authorized to read
2 Immutable tier validation Arc pointer identity check — immutable slots can't be swapped
3 Monotonic label enforcement Security labels can only be added, never removed
4 Write authorization Mutations restricted to slots with declared write capability
5 Filtered slot preservation Hidden slots preserved unchanged during writeback

Sandbox enforcement (per-invocation):

  • Fuel budget (instruction limit)
  • Epoch-based execution timeout (wall-clock)
  • Memory caps (linear memory growth)
  • Network allowlist (per-host outbound HTTP)
  • Filesystem preopens (per-directory)
  • Environment variable allowlist

Changes to cpex-core

  • Feature gating: default = ["runtime"] with runtime feature guarding tokio/tokio-util/arc-swap/cpex-orchestration — enables compilation for wasm32-wasip2
  • Conditional module compilation: executor, factory, manager, registry, visitor gated behind #[cfg(feature = "runtime")]
  • New trait: WasmSerializablePayload (type discriminator + JSON serialize/deserialize) with impl_wasm_payload! macro
  • Payload implementations: MessagePayload, IdentityPayload, DelegationPayload all implement WasmSerializablePayload

Plugin Demos

Capability-Gated Plugins (CMF payload)

Plugin Role
identity_checker.rs PII access control + identity resolution from headers
header_injector.rs Adds labels + injects HTTP headers
audit_logger.rs Read-only logging (fire-and-forget)
token_attenuator.rs Mints scoped delegation tokens
noop.rs Benchmarking baseline

Custom Payload Plugins (ToolInvokePayload)

Plugin Role
tool_invoke_checker.rs Identity check on custom payload
pii_guard.rs PII clearance gate via context state
remote_authz.rs ACL authorization with persistent state (OnceLock)
audit_logger_custom.rs Logs invocations (fire-and-forget)
compute_bench.rs Real computation for benchmarking

Sandbox Test Fixtures

Plugin Tests
fs_test.rs Attempts filesystem read — expects sandbox denial
net_test.rs Attempts outbound HTTP — expects sandbox denial
env_test.rs Attempts env var read — expects sandbox denial

Tests

Test File Tests Coverage
test_security_enforcement.rs 15 5-layer security validation
test_custom_payload_pipeline.rs 8 Full E2E: 4 WASM plugins with custom ToolInvokePayload through PluginManager
test_sandbox_isolation.rs 2 Filesystem access denied by sandbox
test_sandbox_network.rs 2 Network access denied by sandbox
test_sandbox_env.rs 2 Env var access denied by sandbox
test_policy_loader.rs 8 YAML config parsing, context building, deny-all defaults
Plugin unit tests ~12 Per-plugin correctness (feature-gated)

Total: ~49 tests


Benchmarking

Criterion-based benchmarks in crates/cpex-wasm-host/benchmarking/:

Scenario Latency (Apple M4 Max)
Native noop (baseline) 87 ns
WASM noop (pure sandbox overhead) 5.1 us
WASM compute (real work) 10.5 us
Custom payload (JSON serde path) 5.3 us
Cold start (first load) 547 ms

Conclusion: For typical CPEX usage (2-4 plugin calls per LLM request at ~200ms), WASM overhead is 20-40us total — 0.01-0.02% of request latency.

Includes plot_results.py for generating comparison charts and a step-by-step README.md.


Configuration

YAML-based plugin configuration with sandbox policies:

plugins:
  - name: "my-plugin"
    wasm_path: "./wasm/my_plugin.wasm"
    capabilities:
      read: ["headers", "identity"]
      write: ["headers"]
    sandbox_policy:
      allowed_filesystem: ["/tmp/plugin-scratch"]
      allowed_network: ["api.example.com"]
      allowed_env: ["LOG_LEVEL"]
      resources:
        max_memory_bytes: 67108864
        max_fuel: 1000000000
        max_execution_time_ms: 5000
        max_instances: 10
        max_tables: 10

Build System

Workspace changes:

  • cpex-wasm-host added to workspace members
  • cpex-wasm-plugin excluded (compiled separately for wasm32-wasip2 target)

Makefile targets (both crates):

  • make build-all-plugins — build all WASM binaries
  • make build-test-plugins — build sandbox test fixtures
  • make build-bench-plugins — build benchmark plugins
  • make test — clean, rebuild, run all tests
  • make bench-all — full benchmark suite + chart generation
  • make run-demos — build and run example binaries
  • make clean-all — remove all artifacts

Documentation

Document Lines Content
docs/specs/cpex-wasm-spec.md 621 Full specification: architecture, WIT contract, security model, data flow, error classification
crates/cpex-wasm-host/README.md 545 Host runtime docs: usage, config, security, API reference, troubleshooting
crates/cpex-wasm-plugin/README.md 728 Guest SDK tutorial: step-by-step plugin creation, testing, state management
crates/cpex-wasm-host/benchmarking/README.md 236 Benchmarking guide

How to Test

# Prerequisites
rustup target add wasm32-wasip2

# Run all tests (builds plugins automatically)
cd crates/cpex-wasm-host && make test

# Run benchmarks
cd crates/cpex-wasm-host && make bench-all

# Run demos
cd crates/cpex-wasm-host && make run-demos

Files Changed (73 files)

Click to expand full file list

New — Host Runtime (crates/cpex-wasm-host/)

  • Cargo.toml
  • Makefile
  • src/lib.rs
  • src/factory.rs
  • src/sandbox_manager.rs
  • src/conversions.rs
  • src/policy_loader.rs
  • src/payload_registry.rs
  • wit/world.wit
  • wit/deps/cli.wit
  • wit/deps/clocks.wit
  • wit/deps/filesystem.wit
  • wit/deps/http.wit
  • wit/deps/io.wit
  • wit/deps/random.wit
  • wit/deps/sockets.wit
  • config/config.yaml
  • config/config_capabilities.yaml
  • config/config_plugin_demo.yaml
  • tests/test_security_enforcement.rs
  • tests/test_custom_payload_pipeline.rs
  • tests/test_sandbox_isolation.rs
  • tests/test_sandbox_network.rs
  • tests/test_sandbox_env.rs
  • tests/test_policy_loader.rs
  • benchmarking/invocation.rs
  • benchmarking/comprehensive.rs
  • benchmarking/plot_results.py
  • benchmarking/performance_comparison.png
  • benchmarking/README.md
  • examples/wasm_plugin_demo.rs
  • examples/wasm_capabilities_demo.rs
  • README.md

New — Guest SDK (crates/cpex-wasm-plugin/)

  • Cargo.toml
  • Makefile
  • src/lib.rs
  • src/conversions.rs
  • src/plugins/mod.rs
  • src/plugins/identity_checker.rs
  • src/plugins/header_injector.rs
  • src/plugins/audit_logger.rs
  • src/plugins/token_attenuator.rs
  • src/plugins/noop.rs
  • src/plugins/tool_invoke_checker.rs
  • src/plugins/pii_guard.rs
  • src/plugins/remote_authz.rs
  • src/plugins/audit_logger_custom.rs
  • src/plugins/compute_bench.rs
  • src/plugins/fs_test.rs
  • src/plugins/net_test.rs
  • src/plugins/env_test.rs
  • README.md

New — Documentation

  • docs/specs/cpex-wasm-spec.md

Modified — Core

  • Cargo.toml (workspace)
  • crates/cpex-core/Cargo.toml
  • crates/cpex-core/src/lib.rs
  • crates/cpex-core/src/hooks/mod.rs
  • crates/cpex-core/src/hooks/payload.rs
  • crates/cpex-core/src/cmf/message.rs
  • crates/cpex-core/src/identity/payload.rs
  • crates/cpex-core/src/delegation/payload.rs
  • crates/cpex-core/src/config.rs

Modified — Build/Config

  • .gitignore
---

Breaking Changes

None. The feature is entirely additive:

  • Existing native plugins continue working unchanged
  • cpex-core changes are behind a feature flag that defaults to on
  • New crate (cpex-wasm-host) is an optional workspace member

Future Work

  • Pre-compiled module caching (eliminate cold-start penalty for repeat loads)
  • Plugin hot-reload (watch filesystem for updated .wasm binaries)
  • Component-model resource types for streaming payloads
  • Plugin marketplace / registry integration
  • WASM-native async support (when WASI P3 stabilizes)

@monshri monshri self-assigned this Jun 9, 2026
@monshri
monshri marked this pull request as draft June 9, 2026 18:27
@monshri
monshri marked this pull request as ready for review June 9, 2026 18:56
@monshri
monshri force-pushed the feat/plugin-wasm-e2e branch from 51aeb57 to 7fde6ca Compare June 9, 2026 19:23
@araujof araujof assigned araujof and unassigned monshri Jun 10, 2026
@araujof araujof added this to CPEX Jun 10, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in CPEX Jun 10, 2026
@araujof araujof moved this from Backlog to In progress in CPEX Jun 10, 2026
@araujof araujof added this to the 0.2.0 milestone Jun 10, 2026
@araujof araujof changed the title WebAssembly plugin support with end-to-end host invocation feat: WebAssembly plugin support with end-to-end host invocation Jun 10, 2026
@monshri

monshri commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@araujof @terylt Few things to discuss on current implementation:

1. Raw Credentials - Completely Blocked

  • Intentional security boundary - tokens never cross WASM boundary
  • WASM plugins cannot access:
    • Inbound bearer tokens (JWTs, OAuth tokens)
    • Delegated/minted tokens for downstream services
    • Any raw credential material
  • Impact: Cannot implement identity resolvers, token exchangers, or forwarding proxies

2. SecurityExtension - Truncated

Only 4 of 8 fields are available in WASM:

Available:

  • labels - security labels
  • classification - data classification
  • subject - user identity (ID, roles, permissions, claims)
  • auth_method - authentication method used

Missing:

  • client (ClientExtension) - OAuth client identity unavailable
  • caller_workload (WorkloadIdentity) - can't do mTLS/SPIFFE-based policy
  • this_workload (WorkloadIdentity) - can't inspect outbound identity
  • objects/data policy maps - can't read data/object policies

Impact: WASM plugins cannot make workload-aware or client-aware authorization decisions

3. Extension Slots - Only 4 of 13 Available

Available in WASM:

  • request - request metadata (ID, timestamp, trace)
  • security - partial (see above)
  • http - request/response headers
  • meta - entity metadata, tags, properties

Missing from WASM:

  • agent - session/conversation/lineage context
  • mcp - MCP tool/resource/prompt metadata
  • completion - LLM token usage/stop reason
  • provenance - message origin/threading
  • llm - model identity/capabilities
  • framework - agentic framework context
  • delegation - delegation chain
  • raw_credentials - intentionally absent (security boundary)
  • custom - custom extension bag

To discuss

For 1, we can discuss if we want to keep it as-is or not
For 2 and 3, extensions could be extended through WIT definitions, however, we need to discuss what are the use cases we want to limit the wasm based plugins to?

@monshri
monshri marked this pull request as draft June 18, 2026 13:34
@araujof araujof modified the milestones: 0.2.0, 0.2.2 Jul 1, 2026
@monshri
monshri force-pushed the feat/plugin-wasm-e2e branch from 888ec5d to 51b3347 Compare July 2, 2026 23:38
monshri added 5 commits July 2, 2026 19:45
Introduces the WASM plugin sandbox system: cpex-wasm-plugin (guest-side
cdylib targeting wasm32-wasip2) and cpex-wasm-host (host-side runtime
using wasmtime with sandbox policy enforcement, resource limits, and
network filtering).

Signed-off-by: Shriti Priya <shritip@ibm.com>
… functionality in wasm compilation

Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
monshri added 2 commits July 2, 2026 19:48
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
@monshri
monshri force-pushed the feat/plugin-wasm-e2e branch from 51b3347 to 18988db Compare July 2, 2026 23:52
…ation

Fix WIT keyword conflict (%resource escape), add feature-gated plugin SDK
supporting multiple plugins from a single crate, and implement three WASM
demos: CMF pipeline, identity resolution, and capabilities isolation with
extension modification across the sandbox boundary.
@araujof araujof modified the milestones: 0.2.2, 0.2.3 Jul 15, 2026
monshri added 4 commits July 16, 2026 17:18
…ing WIT import, per-invocation resource limits, and full payload coverage tests

Signed-off-by: Shriti Priya <shritip@ibm.com>
…e test target, and suppress warnings

Signed-off-by: Shriti Priya <shritip@ibm.com>
@monshri
monshri marked this pull request as ready for review July 21, 2026 18:28
monshri added 10 commits July 21, 2026 14:35
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
Signed-off-by: Shriti Priya <shritip@ibm.com>
@araujof

araujof commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Thanks for this PR @monshri !

Can you please map the new crate into the cpex facade crate and the release workflow?

Also, including a doc pages and examples where you can demonstrate the sandbox in action would be cool. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

[EPIC]: Configurable and Portable WASM-Based Sandboxing for Plugins [FEATURE]: Add CPEX WASM isolation support

2 participants