Sandboxing untrusted code
The rest of the security model (require, per-task scoping) assumes you wrote the code. This page is the opposite: running code you don't trust — an LLM's output, a user's plugin, a public playground. Two tools: one from inside your program, one from outside.
1. The sandbox block — isolate a part of your program
Wrap the piece that handles untrusted input. Inside, all capabilities are stripped — no net/file/db/secret/exec, only pure computation + print. A require inside is a no-op (it can't re-grant to escape).
let payload be fetch("https://api.external.com/data") -- untrusted input
sandbox
let result be transform(payload) -- if `transform` is exploited, it can touch NOTHING
It's also an expression:
let clean be sandbox validate(untrusted_input) -- isolated, returns a value
Use it when part of your code must process something dangerous but must not reach the outside.
-- Doc example: the `sandbox` block — isolate a piece of code with NO capabilities.
-- As an expression it computes and returns; net/file/db/secret/exec are stripped inside.
intent: "doc example: sandbox block"
task risky_pure(n)
give n * n
-- sandbox as an EXPRESSION: isolated, returns the value (run shows it)
print("sandbox result: " + text(sandbox risky_pure(7))) -- sandbox result: 49
test "sandbox runs pure computation and returns the value"
assert_eq(sandbox risky_pure(7), 49)
assert_eq(sandbox (40 + 2), 42)
2. The host ceiling — --sandbox / --cap-set
When you run a whole program you didn't write, the person running synsema sets a ceiling the code can't exceed, no matter what it declares:
synsema run --sandbox program.syn # stdout + time only
synsema run --cap-set "stdout,db=:memory:" program.syn # a tailored ceiling
synsema test --cap-set "stdout,time,random,secret" tests.syn
--sandbox= the minimum useful ceiling (stdout+time).--cap-set "<list>"= you decide exactly how far (nameorname=scope).- The rule:
caps ⊆ require ∩ ceiling— the code never rises above it. Auto-grants (llmtoo) are filtered, and it applies to spawned agents andparallel_mapworkers. - Scope
file/dbor you give too much:file=scratch_*,db=:memory:.
The three layers together
| Layer | Who restricts | For |
|---|---|---|
require cap("scope") | the code (declares what it needs) | code you trust |
sandbox block | the code (isolates a part of itself) | code you trust |
--sandbox / --cap-set | the host (from outside) | code you DON'T trust |
They compose — the most restrictive wins.
Pattern: a safe runner (playground / agent)
To execute snippets a user or an LLM sends, spawn them under a ceiling in an ephemeral dir:
task run_snippet(code)
require exec("synsema")
require file.write("_run/*")
write_file("_run/s.syn", code)
let r be run("synsema", ["run", "--cap-set", "stdout,time", "_run/s.syn"], 5, {"cwd": "_run"})
give r["stdout"] + r["stderr"]
Add a short timeout (above: 5s) and an isolated cwd; for a public deploy, wrap it in an OS container as a second layer (defense in depth). This is exactly how this site's playground and its MCP run_synsema tool work.
Why it matters for agents: an agent that writes and runs Synsema can execute its own code under a ceiling it can't escape — so "run the code the LLM generated" is safe by construction. That's what makes Synsema fit for agent-native systems.