You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
wazero is a zero-dependency, pure-Go WebAssembly runtime. It supports WASI preview1, JIT compilation, custom host functions, and context-cancellation-aware WASM execution. It's used in this project to run DIFC security guard policies as sandboxed WASM binaries at the MCP gateway boundary.
Version in use: v1.11.0
Current Usage in gh-aw-mcpg
The entire wazero usage lives in internal/guard/wasm.go and its test.
Preferred allocator path (alloc/dealloc exports) avoids corrupting the WASM heap
stdin is explicitly isolated to prevent guards from reading the MCP protocol stream
Research Findings
Recent Updates
wazero v1.11.x continues a mature, stable API. The api.Memory interface has offered typed read/write helpers (ReadUint32Le, WriteUint32Le, etc.) since v1.0. The current usage occasionally manually decodes integers that these helpers could handle directly.
Best Practices (from wazero maintainers)
Use ReadUint32Le / WriteUint32Le for integer memory I/O instead of manual bit-shifting
Close CompilationCache on process shutdown to release JIT resources
Export alloc/dealloc from guards for safe buffer management (project already prefers this path)
Improvement Opportunities
🏃 Quick Wins
1. Use api.Memory.ReadUint32Le() instead of manual bit-shifting
In tryCallWasmFunction, the required buffer size is decoded from WASM memory twice (allocator path and fallback path) using manual bit operations:
// Current — manual LE decode in two placesrequiredSize:=uint32(sizeBytes[0]) |uint32(sizeBytes[1])<<8|uint32(sizeBytes[2])<<16|uint32(sizeBytes[3])<<24
wazero's api.Memory already provides a typed helper:
This eliminates the intermediate sizeBytes slice read and the manual arithmetic, and is self-documenting. Applies to the duplicated pattern at both the allocator and fallback code paths.
2. Route WASM warn/error logs to the operational file logger
hostLog currently dispatches all log levels (debug/info/warn/error) only to the debug logger (logWasm):
case logLevelWarn:
logWasm.Printf("%sWARN: %s", prefix, msg)
case logLevelError:
logWasm.Printf("%sERROR: %s", prefix, msg)
WASM guard errors and warnings are important operational signals that should also appear in mcp-gateway.log and gateway.md. The fix is to additionally call the operational logger for warn/error:
3. Close the global compilation cache on graceful shutdown
globalCompilationCache is initialized at package startup but never closed in production. Only wasm_test.go's TestMain properly closes it. During server shutdown, Registry.Close() closes individual guard runtimes, but the shared cache is leaked.
A minimal fix is to expose a package-level closer and call it during shutdown:
// In wasm.gofuncCloseGlobalCompilationCache(ctx context.Context) error {
returnglobalCompilationCache.Close(ctx)
}
// In server/unified.go shutdown path, after guardRegistry.Close():iferr:=guard.CloseGlobalCompilationCache(ctx); err!=nil {
logger.LogWarn("shutdown", "Failed to close WASM compilation cache: %v", err)
}
This ensures JIT-compiled code is properly released on graceful shutdown, which matters especially for long-running gateway processes.
4. Consider interpreter fallback for restricted environments
The code unconditionally uses NewRuntimeConfigCompiler() (JIT). In hardened container environments with mmap/mprotect restrictions, JIT initialization may fail at startup. A graceful fallback to NewRuntimeConfigInterpreter() would improve robustness in such deployments. A WasmGuardOptions.UseInterpreter bool field could opt into the interpreter for testing or restricted environments.
📐 Best Practice Alignment
5. Fallback memory layout risks heap corruption
When guards don't export alloc/dealloc, the code grows WASM memory and places host-managed buffers at the very end of linear memory. If the WASM module's heap allocator has already grown into that region, this will silently overwrite guard-owned memory.
The safest fix is to requirealloc/dealloc exports, returning a clear error for guards that don't provide them. The existing error message for missing guard functions provides a good model:
ifallocFn==nil {
returnnil, fmt.Errorf("WASM module must export alloc and dealloc for safe memory management. "+"See examples/guards/sample-guard/README.md for details")
}
Alternatively, document this limitation prominently in the guard authoring guide.
This will silently break if wazero changes its error message format. Consider opening an upstream issue requesting a typed api.TrapError or similar that can be detected with errors.As. For now, adding a comment noting the wazero version this was verified against would help future maintainers.
Recommendations (Prioritized)
[Medium] Expose CloseGlobalCompilationCache and wire it into graceful shutdown — prevents resource leaks in production (Lpcox/initial implementation #3 above)
[Low] Route WASM warn/error log levels to the operational file logger for better observability (Lpcox/initial implementation #2 above)
🐹 Go Fan Report: wazero
Module Overview
wazero is a zero-dependency, pure-Go WebAssembly runtime. It supports WASI preview1, JIT compilation, custom host functions, and context-cancellation-aware WASM execution. It's used in this project to run DIFC security guard policies as sandboxed WASM binaries at the MCP gateway boundary.
Version in use:
v1.11.0Current Usage in gh-aw-mcpg
The entire wazero usage lives in
internal/guard/wasm.goand its test.wasm.go,wasm_test.go)wazero,wazero/api,wazero/imports/wasi_snapshot_preview1,wazero/syswazero.NewRuntimeConfigCompiler()+WithCloseOnContextDone(true)— JIT runtime with context supportwazero.NewCompilationCache()— process-level compilation cache shared across all guard instanceswasi_snapshot_preview1.Instantiate()— WASI layer for guest modulesruntime.NewHostModuleBuilder("env")— custom host module exposingcall_backendandhost_logto WASMmodule.ExportedFunction()/fn.Call()— invoking guard functions (label_agent,label_resource,label_response)module.Memory().Read()/.Write()/.Grow()— data exchange across the host/WASM boundarysys.ExitError— distinguishing clean WASI exits from execution trapscontext.WithoutCancel()— non-cancelable context for dealloc cleanupThe code is generally well-written and uses wazero idiomatically. Notable strengths:
alloc/deallocexports) avoids corrupting the WASM heapResearch Findings
Recent Updates
wazero v1.11.x continues a mature, stable API. The
api.Memoryinterface has offered typed read/write helpers (ReadUint32Le,WriteUint32Le, etc.) since v1.0. The current usage occasionally manually decodes integers that these helpers could handle directly.Best Practices (from wazero maintainers)
ReadUint32Le/WriteUint32Lefor integer memory I/O instead of manual bit-shiftingCompilationCacheon process shutdown to release JIT resourcesalloc/deallocfrom guards for safe buffer management (project already prefers this path)Improvement Opportunities
🏃 Quick Wins
1. Use
api.Memory.ReadUint32Le()instead of manual bit-shiftingIn
tryCallWasmFunction, the required buffer size is decoded from WASM memory twice (allocator path and fallback path) using manual bit operations:wazero's
api.Memoryalready provides a typed helper:This eliminates the intermediate
sizeBytesslice read and the manual arithmetic, and is self-documenting. Applies to the duplicated pattern at both the allocator and fallback code paths.2. Route WASM warn/error logs to the operational file logger
hostLogcurrently dispatches all log levels (debug/info/warn/error) only to the debug logger (logWasm):WASM guard errors and warnings are important operational signals that should also appear in
mcp-gateway.logandgateway.md. The fix is to additionally call the operational logger for warn/error:✨ Feature Opportunities
3. Close the global compilation cache on graceful shutdown
globalCompilationCacheis initialized at package startup but never closed in production. Onlywasm_test.go'sTestMainproperly closes it. During server shutdown,Registry.Close()closes individual guard runtimes, but the shared cache is leaked.A minimal fix is to expose a package-level closer and call it during shutdown:
This ensures JIT-compiled code is properly released on graceful shutdown, which matters especially for long-running gateway processes.
4. Consider interpreter fallback for restricted environments
The code unconditionally uses
NewRuntimeConfigCompiler()(JIT). In hardened container environments withmmap/mprotectrestrictions, JIT initialization may fail at startup. A graceful fallback toNewRuntimeConfigInterpreter()would improve robustness in such deployments. AWasmGuardOptions.UseInterpreter boolfield could opt into the interpreter for testing or restricted environments.📐 Best Practice Alignment
5. Fallback memory layout risks heap corruption
When guards don't export
alloc/dealloc, the code grows WASM memory and places host-managed buffers at the very end of linear memory. If the WASM module's heap allocator has already grown into that region, this will silently overwrite guard-owned memory.The safest fix is to require
alloc/deallocexports, returning a clear error for guards that don't provide them. The existing error message for missing guard functions provides a good model:Alternatively, document this limitation prominently in the guard authoring guide.
6.
isWasmTrapstring matching is fragileThe fallback in
isWasmTrapuses:This will silently break if wazero changes its error message format. Consider opening an upstream issue requesting a typed
api.TrapErroror similar that can be detected witherrors.As. For now, adding a comment noting the wazero version this was verified against would help future maintainers.Recommendations (Prioritized)
CloseGlobalCompilationCacheand wire it into graceful shutdown — prevents resource leaks in production (Lpcox/initial implementation #3 above)mem.ReadUint32Le()— cleaner code (Configure as a Go CLI tool #1 above)alloc/deallocexports or document heap-overlap risk in guard authoring guide (Updated Dockerfile #5 above)Next Steps
CloseGlobalCompilationCache(ctx)tointernal/guard/wasm.goand call it inserver/unified.goshutdownhostLogto route warn/error tologger.LogWarn/logger.LogErroruint32LE decoding withmem.ReadUint32Le()alloc/deallocexports being required for safe memory managementGenerated by Go Fan 🐹
Module summary saved to:
specs/mods/wazero.md(session artifact)Run: §24983047267
Note
🔒 Integrity filter blocked 8 items
The following items were blocked because they don't meet the GitHub integrity level.
get_commit: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_commit: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_commit: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_latest_release: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_latest_release: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_latest_release: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".get_file_contents: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".search_code: has lower integrity than agent requires. The agent cannot read data with integrity below "unapproved".To allow these resources, lower
min-integrityin your GitHub frontmatter: