Skip to content

[go-fan] Go Module Review: tetratelabs/wazeroΒ #2829

@github-actions

Description

@github-actions

🐹 Go Fan Report: tetratelabs/wazero

Module Overview

wazero is a zero-dependency, pure-Go WebAssembly runtime β€” no cgo, no external libraries. It runs WASM modules in-process, making it ideal for sandboxed plugins and security guards. This project uses it (v1.11.0, latest) to execute DIFC (Decentralized Information Flow Control) guards: WASM modules that label MCP tool calls and responses with secrecy/integrity tags.

Current Usage in gh-aw-mcpg

  • Files: 2 files (internal/guard/wasm.go, internal/guard/wasm_test.go)
  • Instantiation sites: 3 (proxy/proxy.go, server/guard_init.go Γ—2)
  • Key APIs Used:
    • NewRuntimeConfigCompiler().WithCloseOnContextDone(true) β€” JIT mode with context-driven cancellation
    • wasi_snapshot_preview1.Instantiate() β€” full WASI support
    • runtime.NewHostModuleBuilder("env") β€” exposes call_backend and host_log host functions
    • NewModuleConfig().WithStartFunctions().WithStdin(...) β€” disables auto-start, isolates stdin
    • runtime.InstantiateWithConfig() β€” one-step compile + instantiate
    • module.ExportedFunction() β€” runtime function lookup
    • m.Memory().Read()/.Write()/.Grow() β€” linear memory I/O
    • WASM allocator integration via exported alloc/dealloc (preferred path)

The usage is sophisticated and correct — trap poisoning, adaptive output buffers (4MB→16MB in 3 retries), context.WithoutCancel for cleanup, and mutex serialization are all well-implemented.

Research Findings

Best Practices Already In Use βœ…

  • WithCloseOnContextDone(true) β€” timeout/cancellation propagates into WASM execution
  • WithStartFunctions() (no args) β€” correctly suppresses _start auto-call for library-style guards
  • context.WithoutCancel(ctx) for deferred wasmDealloc β€” cleanup runs even after request cancel
  • Trap detection via strings.Contains(err, "wasm error:") with permanent failed flag

Notable Feature: Compilation Cache

wazero has a compilation cache API (wazero.NewCompilationCache()) that caches JIT-compiled native artifacts and can be shared safely across multiple Runtime instances. Currently the project creates a fresh Runtime per guard and re-JITs every WASM binary on each instantiation. For large guards (the Rust-compiled github-guard WASM can be several MB), this adds measurable startup latency.

Improvement Opportunities

πŸƒ Quick Wins

  1. Consistent Close() error handling β€” module.Close(ctx) errors are logged and swallowed while runtime.Close(ctx) errors are returned. These should be treated consistently (e.g., errors.Join both, or log both and return the runtime error).

  2. Buffer retry strategy comment — The adaptive 4MB→16MB retry logic with the -2 error-code convention is non-obvious. A // Strategy: comment block at the top of callWasmFunction explaining the protocol would help future contributors.

✨ Feature Opportunities

  1. Process-level compilation cache β€” Add a package-level wazero.NewCompilationCache() shared across all WasmGuard instances:

    // In internal/guard/wasm.go
    var globalWasmCompilationCache = wazero.NewCompilationCache()
    
    // In NewWasmGuardWithOptions:
    runtimeConfig := wazero.NewRuntimeConfigCompiler().
        WithCloseOnContextDone(true).
        WithCompilationCache(globalWasmCompilationCache)

    wazero's cache is goroutine-safe and shared across runtimes. Impact: eliminates redundant JIT compilation when multiple guards load the same WASM binary (e.g., tests, restarts).

  2. Test-suite compilation cache via TestMain β€” The test suite instantiates WasmGuard many times. Each call re-compiles the WASM binary. A TestMain with a shared cache would cut test setup time significantly:

    func TestMain(m *testing.M) {
        testWasmCache = wazero.NewCompilationCache()
        defer testWasmCache.Close(context.Background())
        os.Exit(m.Run())
    }
  3. Disk-backed compilation cache for production β€” wazero.NewCompilationCacheContext(ctx, dir) persists compiled native code to disk. For deployments where startup time matters (e.g., containers that restart frequently), this would eliminate JIT compilation overhead entirely after the first run.

πŸ“ Best Practice Alignment

  1. WasmGuardOptions missing CompilationCache field β€” The options struct supports Stdout/Stderr customization but has no way to inject a custom CompilationCache. Adding this would make the cache injectable for both production sharing and test isolation:
    type WasmGuardOptions struct {
        Stdout            io.Writer
        Stderr            io.Writer
        CompilationCache  wazero.CompilationCache // optional shared cache
    }

πŸ”§ General Improvements

  1. ExportedFunction("label_agent") called twice β€” In LabelAgent, the function is checked for nil at the top (module.ExportedFunction("label_agent") == nil) and then looked up again inside callWasmFunction β†’ module.ExportedFunction(funcName). The first check is redundant since callWasmFunction handles the nil case. Minor cleanup opportunity.

Recommendations

Priority Action Effort
High Add process-level wazero.NewCompilationCache() shared across WasmGuard instances Low
Medium Add CompilationCache field to WasmGuardOptions Low
Medium Add TestMain with shared cache in wasm_test.go Low
Low Fix Close() error handling asymmetry Trivial
Low Add strategy comment in callWasmFunction Trivial
Future Evaluate disk-backed cache for production deployments Medium

Next Steps


Generated by Go Fan 🐹
Module summary saved to: /tmp/gh-aw/agent/wazero.md (note: specs/mods/ directory does not yet exist in repo)
Round-robin cache updated: next review will skip tetratelabs/wazero for 7 days

Note

πŸ”’ Integrity filter blocked 16 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by Go Fan Β· β—·

  • expires on Apr 6, 2026, 7:46 AM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions