Security & Sandbox Model
How Synoema isolates LLM-generated code from prompt to deployment
1. The --untrusted Flag
When running code you did not write — LLM output, community packages, or any external source — use the --untrusted flag. It enforces the WASM execution path and rejects native run or JIT backends.
sno run --untrusted generated.sno # enforces WASM path
sno jit --untrusted generated.sno # error: use sno wasm for untrusted code
The flag exists because the verification gap in LLM-generated code is real: a model can produce syntactically valid, type-correct code that still performs unexpected I/O or system calls. WASM isolation closes that gap by construction — the module cannot access anything the host does not explicitly provide.
What the flag does
| Backend | Without --untrusted | With --untrusted |
|---|---|---|
sno run | Interpreter — direct syscalls | Rejected — use sno wasm |
sno jit | Cranelift JIT — native code | Error — JIT rejected for untrusted code |
sno wasm | WASM + host imports | Same — WASM is the required path |
Compile and run untrusted code safely
# Step 1: compile to WASM
sno wasm generated.sno -o generated.wasm
# Step 2: run in a WASM runtime (wasmtime, wasm3, or a host embedding)
wasmtime generated.wasm
The resulting .wasm file runs in a linear memory sandbox with no ambient authority. It can only call host functions that you explicitly import — nothing else is reachable.
2. Capabilities System
Packages declare the system resources they require under [package.capabilities] in sno.toml. Without a declaration, the corresponding builtins are unavailable at runtime. This makes capability requirements explicit, auditable, and machine-readable.
Declaring capabilities in sno.toml
[package]
name = "my-server"
version = "1.0.0"
[package.capabilities]
network = true # tcp_connect, tls_connect, http_get, http_post, tcp_listen ...
fs = true # file_read, file_write, file_read_bytes, fd_popen
spawn = true # spawn, scope (OS threads)
Capability categories
| Capability | Builtins covered | Effect if absent |
|---|---|---|
network = true |
tcp_connect, tls_connect, http_get, http_post, tcp_listen, tls_listen, async_tcp_connect, async_tcp_read, async_tcp_write, async_tcp_close, async_tcp_listen, async_tcp_accept |
Package cannot open network connections or listen on ports |
fs = true |
file_read, file_write, file_read_bytes, fd_popen |
Package cannot read or write files or shell out to subprocesses |
spawn = true |
spawn, scope |
Package cannot create OS threads |
Minimal package — no capabilities
-- A pure-computation IoT rule: no network, no filesystem, no threads.
-- sno.toml has no [package.capabilities] section.
requires sensor_val > 0
ensure result >= 0
rule_normalize sensor_val =
sensor_val / 1024
Pure computation packages — typical for IoT rules, math libraries, and data transformers — declare nothing. They are safe to run on bare MCUs, in WASM sandboxes, or in constrained environments where no ambient authority should be granted.
3. L2 Security Scanner
When you publish a package with sno publish, the registry runs an automated source scan before the upload is accepted. The scanner verifies that your actual builtin usage matches what you declared in [package.capabilities].
How it works
- The registry receives the source tarball.
- It scans
lib.sno(and imported modules) for word-boundary matches against the known set of network, filesystem, and spawn builtins. - If undeclared builtins are found, the upload is rejected with HTTP 422 and a detailed error body.
- The scan result —
PASS,WARN, orFAIL— is stored in package metadata and displayed as a security badge on the package page.
Example: upload rejected (HTTP 422)
$ sno publish
Uploading my-filter@1.0.0 ...
Error: HTTP 422 Unprocessable Entity
{
"error": "capability_mismatch",
"message": "Undeclared builtins found in lib.sno",
"undeclared": ["file_read", "fd_popen"],
"declared": [],
"fix": "Add `fs = true` under [package.capabilities] in sno.toml, or remove the calls."
}
Fix: either declare the capability you need, or remove the call. There is no way to bypass the scanner — it runs server-side on every upload.
Security badge
| Badge | Meaning |
|---|---|
PASS | Declared capabilities exactly match detected usage |
WARN | Declared capabilities are a superset of detected usage (over-declared) |
FAIL | Upload was rejected — usage exceeds declaration |
The badge is visible on every package page at /packages so users can see the scan result before installing.
4. WASM Isolation Model
Synoema’s WASM backend compiles .sno files to standard WebAssembly modules. The isolation properties come from the WASM specification itself, not from any runtime-specific feature.
Guarantees
- Linear memory sandbox. The module can only access its own linear memory. It cannot read or write host process memory, the filesystem, or any other module’s memory.
- No ambient syscalls. WASM has no syscall instruction. All I/O — including printing, network access, GPIO — requires explicit host imports that the embedding runtime provides.
- Deterministic execution. Given the same inputs and host imports, the module produces the same outputs. Useful for reproducible IoT rule verification.
- Portable artifact. A
.wasmfile produced bysno wasmruns identically on wasmtime, wasm3, the browser, or any WASM runtime — no recompilation needed.
Compile to WASM
sno wasm factorial.sno # → factorial.wasm
sno wasm rule_fan_control.sno # → rule_fan_control.wasm (IoT Tier 0)
IoT use case: pure computation rules
For IoT verticals — home automation, industrial safety, wearables — Synoema generates WASM artifacts that run on bare MCUs via wasm3. Because the module has no ambient authority, there is no attack surface beyond what the host explicitly imports (GPIO reads, sensor inputs). This is the same isolation model used for LLM-generated code — a property that holds by construction, not by policy.
-- Example: industrial safety rule
-- No capabilities declared — pure computation, fully sandboxed.
requires pressure > 0
ensures result == true || result == false
rule_overpressure pressure threshold =
pressure > threshold
WASM artifact sizes (from measured IoT verticals)
| Use case | Mean artifact size | Runtime |
|---|---|---|
| Home automation rules | 82 B | wasm3 on Raspberry Pi |
| Industrial safety rules | 107 B max | wasm3 on STM32 |
| Wearable rules | 74–102 B | wasm3 on nRF5340 |
Sizes measured from the real compile verification run. See IoT Platform for deployment details.
5. What Gets Blocked vs. Allowed
A concrete example showing the difference between trusted and untrusted execution paths.
Code that accesses the filesystem
-- reader.sno
main =
contents = file_read "/etc/passwd"
print contents
Trusted path (interpreter) — runs with host access
sno run reader.sno
# root:x:0:0:root:/root:/bin/bash
# ...
Untrusted path — blocked at the runtime boundary
sno run --untrusted reader.sno
# error: --untrusted rejects interpreter backend; compile with sno wasm
sno wasm reader.sno
# compiles — but file_read is not available as a host import in the default runtime
# the WASM module traps at the import boundary if file_read is not provided by the host
The WASM module can only call file_read if the embedding host explicitly provides it. A minimal host (bare MCU, restricted sandbox) simply does not export it — the call traps, safely, at the module boundary rather than reaching the OS.
CLI Reference →
Full list of sno commands including --untrusted, sno wasm, and sno publish.
IoT Platform →
Three-tier device support — bare MCU, edge Linux, and embedded WASM3 runtimes.
Packages →
Package registry, sno.toml format, capability declarations, and publishing.