Synoema

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

BackendWithout --untrustedWith --untrusted
sno runInterpreter — direct syscallsRejected — use sno wasm
sno jitCranelift JIT — native codeError — JIT rejected for untrusted code
sno wasmWASM + host importsSame — 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

CapabilityBuiltins coveredEffect 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

  1. The registry receives the source tarball.
  2. It scans lib.sno (and imported modules) for word-boundary matches against the known set of network, filesystem, and spawn builtins.
  3. If undeclared builtins are found, the upload is rejected with HTTP 422 and a detailed error body.
  4. The scan result — PASS, WARN, or FAIL — 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

BadgeMeaning
PASSDeclared capabilities exactly match detected usage
WARNDeclared capabilities are a superset of detected usage (over-declared)
FAILUpload 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 .wasm file produced by sno wasm runs 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 caseMean artifact sizeRuntime
Home automation rules82 Bwasm3 on Raspberry Pi
Industrial safety rules107 B maxwasm3 on STM32
Wearable rules74–102 Bwasm3 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.