Why Secure AI Needs Compile-Time Sandboxing | Jo Secure Programming Language
Skip to content
Appearance
MenuReturn to top
Why Secure AI Needs Compile-Time Sandboxing <br>June 11, 2026 · The Jo Team<br>AI agents are becoming more capable at automating tasks by generating and executing code. But AI agents cannot be fully trusted. Earlier this year, OpenClaw deleted hundreds of emails from the inbox of Meta Superintelligence Labs' alignment director.<br>Until an agent's actions are strictly bounded, we cannot use it on critical infrastructure or with sensitive data. What exactly is the untrusted code allowed to do — and who checked?<br>The standard answer is runtime sandboxing: containers, seccomp filters, VMs. But they operate at the wrong level of abstraction. A container can stop a process from opening a socket; it cannot stop untrusted code from querying another user's rows through a database connection it legitimately holds.<br>Runtime vs. Compile-Time Sandboxing <br>Runtime sandboxing<br>Enforced in the infrastructure, after deployment
Compile-time sandboxing Jo<br>Enforced by the compiler, before code runs
✕ Blind to business logic<br>Can block syscalls and files, but cannot express rules like "read only this user's rows" or "only these 2 narrowed REST APIs"
✓ Aware of business logic<br>Rules like "read only this user's rows" or "only these 5 narrowed REST APIs" are typed capabilities the compiler enforces.
✕ Boundary buried in deployment stack<br>Authority is scattered across configs and runtime parameters — auditing means digging through infrastructure.
✓ Boundary is typed, versioned code<br>Authority is declared in typed interfaces — auditing means reviewing code in version control.
✕ Violations surface at runtime<br>Escapes are discovered at runtime, after the code is already deployed.
✓ Violations are compile errors<br>The compiler pinpoints them in source, with detailed errors — avoids unnecessary deployment on infrastructure.
A runtime sandbox speaks the operating system's language: processes, files, sockets. The rules worth enforcing need to be written in the application's language — which rows, which endpoints, which user's data — and a boundary can only enforce what it can express. Types are the only boundary that operates at the application level.<br>Compile-Time Sandboxing, Applied to AI <br>In a recent ACM Queue article, Safe Coding, Christoph Kern distills decades of Google's security engineering into a principle of rigorous modular reasoning:<br>... the safety of risky operations within an abstraction must rely solely on assumptions supported by the abstraction's APIs and type signatures. Conversely, the composition of safe abstractions with safe code (i.e., code free of risky operations, which constitutes the vast majority of a program) is automatically verified by the implementation language's type checker.
That is exactly what Jo's compile-time sandboxing is doing.<br>In Jo, risky operations — filesystem access, network calls, FFI, database queries — need explicit permissions, visible in function type signatures as capabilities.<br>In the following example, the AI-generated code is confined to the capabilities it is given:<br>jointerface OrdersApi<br>def query(lastDays: Int): List[Order]<br>end
param ordersApi: OrdersApi
// Untrusted code (AI-generated): can use only what it received<br>def aiMain(): Unit receives ordersApi, IO.stdout =<br>val orders = ordersApi.query(30)<br>printOrders: orders.select(o => o.state == "open")<br>If aiMain tries to reach the filesystem, the network, or an unscoped query, the program does not misbehave at runtime — it fails to compile.<br>The ordersApi capability is provided by the trusted harness — for example, as a user-scoped, read-only view over the real database. But the implementation is opaque to the AI code: all it ever sees is the OrdersApi interface.<br>Alternatives <br>Runtime Sandboxing + REST APIs <br>A popular middle ground is to sandbox the agent and let it reach the world only through REST APIs. This helps, but it inherits a design mismatch: REST APIs were designed for trusted callers and they expose a large capability surface . A typical API token grants everything the user can do through the web interface — read all orders, change the account, delete records — far more than any single agent task needs.<br>An agent is not a trusted caller. What it may call depends on the security context of the task at hand: one task should see only a small subset of the API surface; another needs an endpoint, but with its scope narrowed — this user's rows, read-only, the last 30 days. Existing REST APIs were not designed to answer such needs, so the restrictions end up in gateways and per-agent proxy policies — authority drifts back into infrastructure configuration, with all the audit problems above.<br>With capabilities, the same narrowing is a few lines of trusted code: wrap the API in a smaller interface.<br>MCPs <br>MCP gives LLMs a catalog of vetted tools that can restrict LLM capabilities to application-defined security...