A WebAssembly Component that exercises all major capabilities of the Enclave OS (Mini) WASM runtime. Designed as a reference implementation and integration test for validating new releases.
Six exported functions cover every host import available to WASM apps running inside an SGX enclave:
| # | Function | WASI Interface | What it tests |
|---|---|---|---|
| 1 | hello |
(none) | Smoke test — pure guest code, no host imports |
| 2 | get-random |
wasi:random |
Hardware RNG (RDRAND inside SGX) |
| 3 | get-time |
wasi:clocks/wall-clock |
Wall clock via OCALL |
| 4 | kv-store |
wasi:filesystem |
Write to sealed KV store |
| 5 | kv-read |
wasi:filesystem |
Read from sealed KV store |
| 6 | fetch-headlines |
privasys:enclave-os/https |
HTTPS egress (TLS inside SGX) |
| Tool | Version | Install |
|---|---|---|
| Rust | stable 1.82+ | rustup update stable |
| WASI target | — | rustup target add wasm32-wasip2 |
| cargo-component | latest | cargo install cargo-component |
cargo component build --releaseOutput: target/wasm32-wasip1/release/wasm_example.wasm
For faster load times you can AOT-compile to .cwasm using Wasmtime:
wasmtime compile target/wasm32-wasip1/release/wasm_example.wasm -o wasm_example.cwasmWith the enclave running (see Deployment):
python tests/test_wasm_functions.py wasm_example.cwasm================================================================
Enclave OS (Mini) - WASM Integration Test Suite
================================================================
Connecting to 127.0.0.1:8443 ...
Connected: TLSv1.3, TLS_AES_256_GCM_SHA384
Loading wasm_example.cwasm ...
Loaded in 0.12s
------------------------------------------------------------
# Test Result Details
------------------------------------------------------------
1 Hello World ✔ Hello, World!
2 Random Number ✔ 45
3 Wall Clock ✔ 1772364814.000000000
4 KV Store (write) ✔ greeting = 'Hello from WASM in SGX!'
5 KV Store (read) ✔ Hello from WASM in SGX!
6 HTTPS Egress ✔ 9 headlines — 1. Guides d'achat
------------------------------------------------------------
✔ Result: ALL 6 TESTS PASSED
Enclave OS (Mini) embeds a Wasmtime runtime inside the SGX enclave. WASM apps are:
- Compiled externally (this project) into a Component Model
.wasm - Pre-compiled to
.cwasm(AOT) with Wasmtime — no Cranelift inside SGX - Loaded dynamically at runtime via
wasm_loadover the RA-TLS wire protocol — the enclave starts empty, no apps are compiled in - Attested automatically — the SHA-256 of the WASM bytecode is embedded
in the config Merkle tree and in every RA-TLS certificate
(OID
1.3.6.1.4.1.65230.2.3) - Called over RA-TLS via JSON
wasm_callenvelopes - Executed statelessly — each call gets a fresh instance with a 10 million instruction fuel budget
- Managed at runtime — apps can be
listedandunloadedwithout restarting the enclave
All communication happens over a single RA-TLS connection using length-delimited JSON frames (4-byte big-endian length prefix).
Call parameters are typed JSON values:
{"type": "string", "value": "hello"}
{"type": "u32", "value": 42}The enclave starts empty — no WASM apps are compiled in. Apps are loaded, called, and managed entirely at runtime over the RA-TLS wire protocol.
┌───────────────┐ RA-TLS ┌───────────────────────────┐
│ Client │◄──────────────►│ SGX Enclave │
│ (ra-tls-cli) │ │ │
│ │ wasm_load ──► │ 1. SHA-256 code hash │
│ │ │ 2. Deserialize (AOT) │
│ │ │ 3. Introspect exports │
│ │ ◄── loaded │ 4. Register app │
│ │ │ 5. Embed hash in cert │
│ │ wasm_call ──► │ │
│ │ ◄── result │ Fresh instance per call │
│ │ │ (stateless, fuel-limited)│
└───────────────┘ └───────────────────────────┘
Send the pre-compiled .cwasm bytecode to the enclave. The enclave:
- Hashes the bytecode (SHA-256) — this becomes the app's identity
- Deserializes the AOT artifact (no Cranelift compilation inside SGX)
- Introspects the component to discover all exported functions
- Generates an AES-256 encryption key via RDRAND for the app's KV store (or accepts a caller-supplied key — see BYOK)
- Registers the app under the given name
- Re-derives the RA-TLS certificate with the new code hash in the
config Merkle tree (OID
1.3.6.1.4.1.65230.2.3)
Request:
{
"wasm_load": {
"name": "test-app",
"bytes": [0, 97, 115, 109, ...],
"hostname": "app.enclave.example.com"
}
}| Field | Required | Description |
|---|---|---|
name |
yes | App identifier — used in subsequent wasm_call requests |
bytes |
yes | Raw .cwasm bytecode as a JSON integer array |
hostname |
no | SNI hostname for a dedicated per-app TLS certificate (defaults to name) |
encryption_key |
no | Hex-encoded 32-byte AES-256 key for KV store (BYOK) |
Response:
{
"status": "loaded",
"app": {
"name": "test-app",
"hostname": "app.enclave.example.com",
"code_hash": "a1b2c3d4...64hex",
"key_source": "generated",
"exports": [
{"name": "hello", "param_count": 0, "result_count": 1},
{"name": "kv-store", "param_count": 2, "result_count": 1}
]
}
}The code_hash is the SHA-256 of the loaded bytecode. Remote clients can
verify this value in the RA-TLS certificate to confirm exactly what code
is running inside the enclave.
Invoke an exported function on a loaded app. Each call creates a fresh WASM instance (stateless execution) with a fuel budget of 10 million instructions.
Request:
{
"wasm_call": {
"app": "test-app",
"function": "hello",
"params": []
}
}Response:
{
"status": "ok",
"returns": [{"type": "string", "value": "Hello, World!"}]
}With parameters:
{
"wasm_call": {
"app": "test-app",
"function": "kv-store",
"params": [
{"type": "string", "value": "greeting"},
{"type": "string", "value": "Hello from WASM in SGX!"}
]
}
}Query all currently loaded apps with metadata.
Request:
{"wasm_list": {}}Response:
{
"status": "apps",
"apps": [
{
"name": "test-app",
"hostname": "app.enclave.example.com",
"code_hash": "a1b2c3d4...64hex",
"key_source": "generated",
"exports": [
{"name": "hello", "param_count": 0, "result_count": 1},
{"name": "get-random", "param_count": 0, "result_count": 1},
{"name": "get-time", "param_count": 0, "result_count": 1},
{"name": "kv-store", "param_count": 2, "result_count": 1},
{"name": "kv-read", "param_count": 1, "result_count": 1},
{"name": "fetch-headlines", "param_count": 0, "result_count": 1}
]
}
]
}Remove an app from the enclave. The in-memory encryption key is destroyed, making any KV data written with a generated key permanently unrecoverable.
Request:
{"wasm_unload": {"name": "test-app"}}Response:
{"status": "unloaded", "name": "test-app"}By default, the enclave generates a random AES-256 encryption key per app via RDRAND. The key lives only in enclave memory — if the app is unloaded, the key and any data encrypted with it are gone forever.
For persistent data across app reloads, supply an encryption_key during
wasm_load:
{
"wasm_load": {
"name": "my-app",
"bytes": [0, 97, 115, 109, ...],
"encryption_key": "a1b2c3d4e5f6...64hex"
}
}The key must be exactly 32 bytes (64 hex characters). The enclave will use
this key for all KV store operations for this app. The key_source field
in the response will report "byok" instead of "generated".
import json, socket, ssl, struct
def frame(data):
return struct.pack(">I", len(data)) + data
# Connect via RA-TLS
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE # or verify against RA-TLS CA
sock = ctx.wrap_socket(
socket.create_connection(("127.0.0.1", 8443)),
server_hostname="127.0.0.1",
)
# Load the WASM app
with open("wasm_example.cwasm", "rb") as f:
wasm_bytes = list(f.read())
load_req = json.dumps({
"wasm_load": {"name": "my-app", "bytes": wasm_bytes}
}).encode()
sock.sendall(frame(json.dumps({"Data": list(load_req)}).encode()))
# Read response (simplified — production code should handle partial reads)
buf = sock.recv(65536)
length = struct.unpack(">I", buf[:4])[0]
resp = json.loads(buf[4:4+length])
inner = json.loads(bytes(resp["Data"]))
print("Loaded:", json.dumps(inner, indent=2))
# Call a function
call_req = json.dumps({
"wasm_call": {"app": "my-app", "function": "hello", "params": []}
}).encode()
sock.sendall(frame(json.dumps({"Data": list(call_req)}).encode()))
buf = sock.recv(65536)
length = struct.unpack(">I", buf[:4])[0]
resp = json.loads(buf[4:4+length])
result = json.loads(bytes(resp["Data"]))
print("Result:", json.dumps(result, indent=2))
# {"status": "ok", "returns": [{"type": "string", "value": "Hello, World!"}]}
sock.close(){"wasm_call": {"app": "test-app", "function": "hello", "params": []}}Returns: "Hello, World!"
No host imports. Validates the end-to-end path: RA-TLS → wire protocol → WASM instantiation → guest code → response.
{"wasm_call": {"app": "test-app", "function": "get-random", "params": []}}Returns: integer in [1, 100]
Uses wasi:random/random.get-random-bytes(4) → maps to RDRAND inside SGX.
Different value on each call.
{"wasm_call": {"app": "test-app", "function": "get-time", "params": []}}Returns: "<seconds>.<nanoseconds>" (e.g. "1772364814.000000000")
Uses wasi:clocks/wall-clock.now() → OCALL to host for current UNIX time.
{
"wasm_call": {
"app": "test-app",
"function": "kv-store",
"params": [
{"type": "string", "value": "greeting"},
{"type": "string", "value": "Hello from WASM in SGX!"}
]
}
}Returns: "stored: greeting"
Opens a "file" via wasi:filesystem, writes the value, and calls
sync-data() to flush encrypted data to the host-side sealed KV store.
{
"wasm_call": {
"app": "test-app",
"function": "kv-read",
"params": [{"type": "string", "value": "greeting"}]
}
}Returns: "Hello from WASM in SGX!" (the value stored by kv-store)
Data persists across calls and enclave restarts (same MRENCLAVE required).
Each app is namespace-isolated: app:<name>/fs:<path>.
{"wasm_call": {"app": "test-app", "function": "fetch-headlines", "params": []}}Returns: numbered list of Le Monde headlines (up to 10)
Uses privasys:enclave-os/https.fetch() to make an HTTPS GET request.
TLS 1.3 terminates inside the enclave using rustls + Mozilla root CAs.
The host only sees encrypted TCP bytes.
Use ra-tls-clients to connect, verify attestation, and interact with the enclave.
cd ra-tls-clients/go
go run . --host <server> --port 443The CLI verifies the RA-TLS certificate (DCAP quote + ReportData binding) then drops into an interactive session where you can send JSON commands.
from ratls_client import RaTlsClient
client = RaTlsClient("server.example.com", 443, ca_cert="path/to/ca.crt")
client.connect()
# Load the WASM app
with open("wasm_example.cwasm", "rb") as f:
wasm_bytes = list(f.read())
client.send({"wasm_load": {"name": "test-app", "bytes": wasm_bytes}})
result = client.recv()
# Call a function
client.send({"wasm_call": {"app": "test-app", "function": "hello", "params": []}})
result = client.recv()
print(result) # {"status": "ok", "returns": [{"type": "string", "value": "Hello, World!"}]}Available clients: Go, Python, Rust, TypeScript, C# (.NET) — see ra-tls-clients for details.
See the Enclave OS (Mini) README
for full build instructions. The WASM runtime is enabled by building with the
wasm-enclave composition crate:
cd enclave-os-mini
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_WASM=ON -DWASM_ENCLAVE_DIR=/path/to/wasm-app-example/enclave
make -j$(nproc)This produces enclave-os-host and enclave-os-enclave.signed.so in build/bin/.
cd build/bin
./enclave-os-host -p 8443The enclave listens on 0.0.0.0:8443 for RA-TLS connections. WASM apps are
loaded dynamically — no apps need to be present at build time.
The enclave terminates TLS internally — a front-end load balancer must operate at Layer 4 (TCP passthrough). See the Layer 4 Proxy Guide for Caddy (with caddy-l4) and HAProxy configurations.
For cloud-specific setup instructions, see:
wasm-example/
├── Cargo.toml # cdylib crate, depends on wit-bindgen-rt
├── README.md # This file
├── src/
│ ├── lib.rs # 6 exported functions (~210 lines)
│ └── bindings.rs # Auto-generated by wit-bindgen (do not edit)
├── tests/
│ └── test_wasm_functions.py # Integration test suite (all 6 functions)
├── install/
│ └── ovh-sgx.md # OVH bare metal SGX deployment guide
└── wit/
├── world.wit # Component world (imports + exports)
└── deps/
├── clocks/ # wasi:clocks@0.2.0
├── enclave-os/ # privasys:enclave-os@0.1.0 (HTTPS)
├── filesystem/ # wasi:filesystem@0.2.0
├── io/ # wasi:io@0.2.0
└── random/ # wasi:random@0.2.0
WIT interfaces are copied from the
Enclave OS WASM SDK.
The full SDK includes additional interfaces (cli, sockets, crypto,
keystore) not used by this example.
| Function | Expected output | Validation |
|---|---|---|
hello |
"Hello, World!" |
Exact string match |
get-random |
Integer [1, 100] |
Range check, varies per call |
get-time |
"<unix_ts>.000000000" |
Valid recent UNIX timestamp |
kv-store("k","v") |
"stored: k" |
Exact match |
kv-read("k") |
"v" |
Returns previously stored value |
kv-read("missing") |
"error: key not found: missing" |
Error message |
fetch-headlines |
Numbered list (1-10 items) | At least 2 headlines |
- Stateless execution: Each call creates a fresh WASM instance. KV data
persists via
sync-data()→ sealed KV store on the host. - Namespace isolation: Each app's files and keys are prefixed with
app:<name>/— apps cannot access each other's data. - HTTPS only: The egress interface only supports
https://URLs —http://requests are rejected. The host never sees plaintext. - Attestation: The SHA-256 hash of the loaded WASM bytecode is embedded in the RA-TLS certificate, allowing remote clients to verify exactly what code is running.
This project is licensed under the GNU Affero General Public License v3.0.