Tracking CVEs Across the Wasm Runtime Supply Chain
Problem
The Wasm runtime ecosystem has matured rapidly: Wasmtime (Bytecode Alliance), WasmEdge (CNCF), wasmer, wazero, and the Wasm engines embedded in V8 (Node.js, Deno, Chrome), SpiderMonkey (Firefox), and JavaScriptCore (Safari) are all in active production use. Each has its own CVE history, its own release cadence, and its own advisory disclosure process.
Unlike application dependencies where vulnerability scanners (Trivy, Grype, Dependabot) can automatically detect vulnerable package versions, Wasm runtimes present a more complex tracking challenge:
Runtimes are embedded in unexpected places. Wasmtime is a Rust library that gets compiled into applications. WasmEdge is used as a containerd shim. Wazero is a Go library embedded in tools like opa (Open Policy Agent). An organisation may be running three different Wasm runtimes across their stack without realising it — because the runtime is a dependency of a dependency, not a direct package reference.
Runtime CVEs have wide blast radius. A vulnerability in a Wasm sandbox runtime is not equivalent to a CVE in an application library. If the runtime’s sandbox is broken (allowing guest code to escape), every Wasm module running on that runtime is affected. A CVE in Wasmtime’s sandbox could allow a malicious Wasm module (plugin, user-provided code) to escape containment — this is a qualitatively different risk from a library CVE.
CVE tracking infrastructure does not cover all runtimes equally. Wasmtime publishes security advisories to GHSA and via the Bytecode Alliance’s security advisory process. WasmEdge has had CVEs disclosed via GHSA. Wazero and wasmer are tracked in GHSA and OSV. But NVD enrichment for runtime CVEs can be slow, and scanner CPE matching for these components is inconsistent.
The supply chain depth makes automated detection hard. If your service uses opa and opa embeds wazero, your container image scanner will detect wazero as a transitive dependency — but only if the scanner handles Go module dependency trees, which varies by tool configuration.
Target systems: organisations embedding Wasm runtimes in production services; platform teams deploying WasmEdge as a containerd runtime; any stack using Wasmtime, wazero, or wasmer as a dependency; serverless platforms built on Wasm.
Threat Model
Adversary 1 — Sandbox escape via runtime CVE. A CVE in Wasmtime’s bounds checking logic allows a Wasm module to read or write outside its linear memory sandbox into the host process memory. An attacker who can supply a Wasm module (plugin system, user-provided functions) exploits the CVE to escape the sandbox and access host resources.
Adversary 2 — Denial of service via runtime resource exhaustion. A CVE in Wasmtime’s async execution (CVE-2024-class) allows a Wasm module to cause unbounded resource consumption in the runtime host. An attacker submits crafted Wasm that exhausts memory or CPU, causing DoS for all Wasm workloads on the affected host.
Adversary 3 — Undetected vulnerable runtime in transitive dependency. An organisation scans their container images but the scanner does not detect the wazero version embedded in OPA because Go module embedding is not fully analysed. A CVE is published for the embedded wazero version. The scanner shows clean. The organisation has no process to track Wasm runtime CVEs independently.
Adversary 4 — Malicious Wasm module exploiting patched-but-undeployed runtime. A sandbox escape CVE is published for Wasmtime. The vendor has released a patch. The organisation uses Wasmtime and is aware of the CVE. However, the Wasmtime library is embedded in three different services, each with different release cadences. One service is patched; two remain on the vulnerable version. An attacker exploits the unpatched services.
Configuration / Implementation
Step 1 — Inventory Wasm runtimes across your stack
#!/bin/bash
# scripts/wasm-runtime-inventory.sh
# Find Wasm runtimes deployed across the environment
echo "=== Wasm Runtime Inventory ==="
# 1. Check for standalone runtime binaries
echo ""
echo "--- Standalone Wasm runtimes ---"
for runtime in wasmtime wasmer wasm-pack wasm3 iwasm; do
path=$(which "$runtime" 2>/dev/null)
if [[ -n "$path" ]]; then
version=$("$runtime" --version 2>/dev/null | head -1)
echo " $runtime: $path ($version)"
fi
done
# 2. Check for WasmEdge as containerd shim
echo ""
echo "--- WasmEdge containerd shim ---"
if [[ -f /usr/local/bin/containerd-shim-wasmedge-v1 ]]; then
VERSION=$(containerd-shim-wasmedge-v1 --version 2>/dev/null | head -1)
echo " WasmEdge shim: $VERSION"
fi
# 3. Check Kubernetes node for Wasm runtimeclass
echo ""
echo "--- Kubernetes Wasm RuntimeClasses ---"
kubectl get runtimeclass -o json 2>/dev/null | \
jq -r '.items[] | select(.handler | test("wasm|wasmtime|wasmedge|spin")) |
"\(.metadata.name): handler=\(.handler)"' || echo " kubectl not available"
# 4. Check Go binaries for embedded wazero
echo ""
echo "--- Go binaries with embedded wazero ---"
find /usr/local/bin /usr/bin /opt -maxdepth 3 -executable -type f 2>/dev/null | \
while read -r binary; do
if strings "$binary" 2>/dev/null | grep -q "wazero\|wasm.*runtime\|wasmtime"; then
echo " $binary (contains wasm strings)"
fi
done
# 5. Check container images in use
echo ""
echo "--- Container images with wasm runtimes (from running pods) ---"
kubectl get pods -A -o json 2>/dev/null | \
jq -r '.items[].spec.containers[].image' | sort -u | \
while read -r image; do
if echo "$image" | grep -qi "wasm\|wasmtime\|wasmedge\|spin"; then
echo " $image"
fi
done
# 6. Check Rust binaries for embedded wasmtime
echo ""
echo "--- Rust binaries potentially embedding Wasmtime ---"
find /usr/local/bin /opt -maxdepth 3 -executable -type f 2>/dev/null | \
while read -r binary; do
if strings "$binary" 2>/dev/null | grep -q "wasmtime\|cranelift"; then
wasmtime_ver=$(strings "$binary" | grep -oP 'wasmtime [0-9]+\.[0-9]+\.[0-9]+' | head -1)
[[ -n "$wasmtime_ver" ]] && echo " $binary ($wasmtime_ver)"
fi
done
Step 2 — Subscribe to Wasm runtime security advisories
#!/bin/bash
# scripts/wasm-advisory-monitor.sh
# Monitor GitHub Security Advisories for Wasm runtime repositories
GITHUB_TOKEN="${GITHUB_TOKEN:?Set GITHUB_TOKEN}"
STATE_FILE="/var/lib/wasm-advisory-monitor/state.json"
mkdir -p "$(dirname "$STATE_FILE")"
# Wasm runtime GitHub repositories to monitor
WASM_REPOS=(
"bytecodealliance/wasmtime"
"WasmEdge/WasmEdge"
"wasmerio/wasmer"
"tetratelabs/wazero"
"bytecodealliance/wasm-micro-runtime" # WAMR
"bytecodealliance/lucet" # Deprecated but still in use
)
check_advisories() {
local repo="$1"
local owner="${repo%%/*}"
local name="${repo##*/}"
curl -s -X POST "https://api.github.com/graphql" \
-H "Authorization: bearer $GITHUB_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"query\": \"query {
repository(owner: \\\"$owner\\\", name: \\\"$name\\\") {
securityAdvisories: vulnerabilityAlerts(first: 10, states: [OPEN]) {
nodes {
securityAdvisory {
ghsaId
summary
severity
publishedAt
identifiers { type value }
cvss { score }
}
vulnerableVersionRange
firstPatchedVersion { identifier }
}
}
}
}\"
}" | jq --arg repo "$repo" '
.data.repository.securityAdvisories.nodes[] |
{
repo: $repo,
ghsa: .securityAdvisory.ghsaId,
severity: .securityAdvisory.severity,
cvss: .securityAdvisory.cvss.score,
summary: .securityAdvisory.summary,
cves: [.securityAdvisory.identifiers[] | select(.type=="CVE") | .value],
vulnerable_range: .vulnerableVersionRange,
fixed_in: .firstPatchedVersion.identifier
}'
}
echo "=== Wasm Runtime Security Advisory Check ==="
for repo in "${WASM_REPOS[@]}"; do
echo ""
echo "--- $repo ---"
advisories=$(check_advisories "$repo")
if [[ -z "$advisories" ]] || [[ "$advisories" == "null" ]]; then
echo " No open security advisories"
else
echo "$advisories" | jq -r '" [\(.severity)] \(.ghsa): \(.summary[0:80])\n Fixed in: \(.fixed_in // "no fix")\n CVEs: \(.cves | join(", "))"'
fi
done
Step 3 — Pin runtime versions with digest verification
# Dockerfile — pin wasmtime version with SHA256 digest
# Prevents supply chain substitution and ensures reproducible builds
FROM debian:12-slim
# Pin wasmtime version — update this when a new security release is published
# Check: https://github.com/bytecodealliance/wasmtime/releases
ARG WASMTIME_VERSION="26.0.1"
ARG WASMTIME_SHA256="abc123def456..." # Replace with actual SHA256 from release
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \
curl -fsSL "https://github.com/bytecodealliance/wasmtime/releases/download/v${WASMTIME_VERSION}/wasmtime-v${WASMTIME_VERSION}-x86_64-linux.tar.xz" \
-o /tmp/wasmtime.tar.xz && \
echo "${WASMTIME_SHA256} /tmp/wasmtime.tar.xz" | sha256sum --check && \
tar -xf /tmp/wasmtime.tar.xz -C /usr/local/bin/ --strip-components=1 \
"wasmtime-v${WASMTIME_VERSION}-x86_64-linux/wasmtime" && \
rm /tmp/wasmtime.tar.xz && \
apt-get remove -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
# Cargo.toml — pin wasmtime crate version
[dependencies]
# Pin exact version — update when security releases are published
# Subscribe to: https://github.com/bytecodealliance/wasmtime/security/advisories
wasmtime = "=26.0.1"
# If using wasmtime features, be specific
[dependencies.wasmtime]
version = "=26.0.1"
default-features = false
features = ["cranelift", "component-model"]
# Do NOT use: async (if not needed) — reduces attack surface
// go.mod — pin wazero version
module github.com/example/myservice
go 1.22
require (
// Pin exact version — update when CVEs are published
// Advisory feed: https://github.com/tetratelabs/wazero/security/advisories
github.com/tetratelabs/wazero v1.7.2
)
Step 4 — Renovate configuration for Wasm runtimes
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base"],
"packageRules": [
{
"description": "Wasm runtime updates — require security review; no auto-merge",
"matchPackageNames": [
"wasmtime",
"github.com/tetratelabs/wazero",
"wasmer",
"wasmedge"
],
"matchDatasources": ["cargo", "go", "npm", "pypi"],
"automerge": false,
"labels": ["security", "wasm-runtime", "requires-review"],
"reviewers": ["platform-team", "security-team"],
"prPriority": 15,
"prTitle": "chore(wasm): update {{depName}} {{currentVersion}} → {{newVersion}} (check CVEs)"
},
{
"description": "WasmEdge container image updates",
"matchPackageNames": ["wasmedge/wasmedge", "ghcr.io/wasmedge/wasmedge"],
"matchDatasources": ["docker"],
"automerge": false,
"labels": ["security", "wasm-runtime"],
"reviewers": ["platform-team"]
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "vulnerability"]
}
}
Step 5 — CVE-triggered runtime update automation
#!/bin/bash
# scripts/wasm-runtime-cve-response.sh
# When a CVE is published for a Wasm runtime, trigger the update process
CVE_ID="${1:?Usage: $0 <cve-id> <runtime-name> <patched-version>}"
RUNTIME="${2:?}"
PATCHED_VERSION="${3:?}"
echo "=== Wasm Runtime CVE Response: $CVE_ID ==="
echo "Runtime: $RUNTIME"
echo "Patched version: $PATCHED_VERSION"
# 1. Find all services embedding this runtime
echo ""
echo "Finding services with embedded $RUNTIME..."
case "$RUNTIME" in
wasmtime)
# Search Cargo.lock files in repositories
find . -name "Cargo.lock" | xargs grep -l "wasmtime" 2>/dev/null
;;
wazero)
find . -name "go.sum" | xargs grep -l "tetratelabs/wazero" 2>/dev/null
;;
wasmedge)
kubectl get pods -A -o json 2>/dev/null | \
jq -r '.items[] | select(.spec.runtimeClassName == "wasmedge") |
"\(.metadata.namespace)/\(.metadata.name)"'
;;
esac
# 2. Create emergency update PRs
echo ""
echo "Creating update PRs..."
case "$RUNTIME" in
wazero)
# Update go.mod in affected repositories
find . -name "go.mod" -exec grep -l "tetratelabs/wazero" {} \; | \
while read -r gomod; do
repo_dir=$(dirname "$gomod")
pushd "$repo_dir" > /dev/null
go get "github.com/tetratelabs/wazero@${PATCHED_VERSION}"
go mod tidy
git diff go.mod go.sum
# Create PR via gh cli
gh pr create \
--title "security: update wazero to $PATCHED_VERSION ($CVE_ID)" \
--body "Emergency security update for $CVE_ID in wazero. Patched version: $PATCHED_VERSION" \
--label "security,critical" 2>/dev/null || echo "PR creation requires gh auth"
popd > /dev/null
done
;;
wasmtime)
find . -name "Cargo.toml" -exec grep -l "wasmtime" {} \; | \
while read -r cargo_toml; do
sed -i "s/wasmtime = \"=[0-9.]*\"/wasmtime = \"=${PATCHED_VERSION}\"/" "$cargo_toml"
done
;;
esac
# 3. Alert platform team
if [[ -n "${SLACK_WEBHOOK:-}" ]]; then
curl -s -X POST "$SLACK_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"text\": \":warning: Wasm Runtime CVE Alert: $CVE_ID\n*Runtime:* $RUNTIME\n*Patched version:* $PATCHED_VERSION\nEmergency update PRs created for affected services.\"}"
fi
echo ""
echo "Response complete. Review and merge update PRs."
Expected Behaviour
| Scenario | Without Wasm CVE tracking | With Wasm CVE tracking |
|---|---|---|
| Wasmtime CVE published | Not detected by container scanner | GHSA monitor alerts; affected services identified; update PRs created |
| Wazero CVE in transitive Go dependency | Go module scanner may catch it | Explicit Renovate rule for wazero triggers; security review required |
| WasmEdge shim CVE on Kubernetes nodes | Node version not in image scanner scope | Node-level runtime inventory identifies version; kubelet update triggered |
| Runtime version pinned to old version in Dockerfile | Stays on vulnerable version | Renovate detects update; PR opened with CVE note in title |
| Multiple services use different runtime versions | Inconsistent patching | Inventory script identifies all versions; CVE response script updates all |
Trade-offs
| Aspect | Benefit | Cost | Mitigation |
|---|---|---|---|
| Exact version pinning in Cargo.toml | Full control; no surprise updates | Manual update required for every security release | Automate via Renovate; add CVE check to PR description |
| GHSA monitoring per repository | Catches runtime-specific advisories | Many GitHub API calls; rate limits | Use GraphQL batching; cache results; run 4×/day not continuously |
| Inventory script for embedded runtimes | Visibility into hidden runtime use | strings on binaries is imprecise; may miss obfuscated embeds |
Combine with SBOM generation at build time for definitive inventory |
| Emergency update automation | Fast response to runtime CVEs | Auto-updating runtimes can introduce breaking changes | Require test suite to pass before PR merges; keep a rollback plan |
Failure Modes
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Wasm runtime embedded in compiled binary not detected by inventory script | Runtime runs undetected; CVE tracking misses it | Post-CVE audit finds untracked service | Add SBOM generation to CI (cargo cyclonedx, go mod vendor analysis); check SBOM for wasm runtime packages |
| Renovate update breaks Wasm ABI compatibility | Wasm modules fail to load after runtime update | Wasm module load errors in application logs | Pin both runtime version and Wasm binary format; test Wasm modules against new runtime in CI |
| GHSA rate limit prevents advisory checks | Advisory monitor fails silently | Monitor alert on failed API call | Implement exponential backoff; use GitHub token with higher rate limits |
| WasmEdge shim update breaks containerd | Containers fail to start after shim update | Pod start failures; containerd logs show shim error | Test shim update on one node before rolling out; maintain rollback node image |
Related Articles
- Wasmtime Production Hardening — securing Wasmtime in production deployments, relevant when patching Wasmtime CVEs
- Wasm Runtime Security Disclosures — historical pattern of Wasm runtime security disclosures and what they affect
- CVE Program Resilience and NVD Alternatives — tracking Wasm runtime CVEs when NVD enrichment is incomplete
- CISA KEV Alerting Integration — when Wasm runtime CVEs enter the KEV catalog for active exploitation
- Wasm Component Supply Chain Security — supply chain security for Wasm components that run on the runtime