Running untrusted code
This page runs code you do not trust in a Workspace with no network access, a small resource limit, no Volume, and a time limit, then reads back a bounded result. The complete program is examples/go/untrusted-code.
Prerequisites
- Go 1.27 or later, and a checkout of the Kibotos repository.
- The ctrl address, and an enrollment key for this program from
kibo enrollment create --operator. See Identity and enrollment.
Steps
1. Create a restricted Workspace
Set the limits when you create the Workspace. The expiration time makes ctrl delete it even if your program never comes back:
request := &ctrlv1.CreateWorkspaceRequest{
Metadata: &ctrlv1.CommandMetadata{CommandId: commandID},
Spec: &ctrlv1.WorkspaceCreateSpec{
Template: &ctrlv1.TemplateSelector{TemplateId: templateID},
Resources: &ctrlv1.ResourceRequest{CpuMillis: 500, MemoryBytes: 256 << 20, EphemeralDiskBytes: 1 << 30},
Volume: &ctrlv1.WorkspaceVolumeBinding{Mode: ctrlv1.WorkspaceVolumeMode_WORKSPACE_VOLUME_MODE_NONE},
NetworkPolicy: &ctrlv1.NetworkPolicy{Mode: ctrlv1.NetworkPolicyMode_NETWORK_POLICY_MODE_DENY_ALL},
},
Expiration: &ctrlv1.WorkspaceExpirationPolicy{
Timeout: durationpb.New(10 * time.Minute),
Action: ctrlv1.WorkspaceExpirationAction_WORKSPACE_EXPIRATION_ACTION_DELETE,
},
}2. Run the code with a time limit
Upload the code, then start it with TimeoutMillis. The process is killed when the limit passes:
stream, err := session.StartProcess(ctx, &ctrlv1.StartRequest{
Argv: []string{"/tmp/job.sh"},
TimeoutMillis: 30_000,
Mode: &ctrlv1.StartRequest_Pipe{Pipe: &ctrlv1.ProcessPipeMode{}},
})3. Check how the process ended
The exit event says whether the code finished or ran out of time. A killed process has no meaningful exit code, so check the reason first:
switch exited.GetTerminationReason() {
case ctrlv1.ProcessTerminationReason_PROCESS_TERMINATION_REASON_EXITED:
if code := exited.GetExitCode(); code != 0 {
return fmt.Errorf("the code exited with %d", code)
}
case ctrlv1.ProcessTerminationReason_PROCESS_TERMINATION_REASON_TIMED_OUT:
return errors.New("the code ran past its time limit and was killed")
default:
return fmt.Errorf("the code ended with %s, signal %d", exited.GetTerminationReason(), exited.GetSignal())
}4. Read a bounded result
Treat the output and the result files as untrusted too. Keep a limited amount of each, and delete the Workspace when you are done:
result, err := io.ReadAll(io.LimitReader(file, maxOutput))5. Run the program
The built-in script tries to reach the network and writes a result file. Pass -code to run your own script:
go run ./examples/go/untrusted-code -endpoint https://ctrl.example.com:18092 \
-identity "$HOME/.local/state/kibotos-untrusted-code" -enrollment-key-file untrusted-code.keycreated Workspace ws-06gvz1gwahz9f738p47k0d7zpg
output:
wget: bad address 'example.com'
network: unreachable
result: computed 42
deleted Workspace ws-06gvz1gwahz9f738p47k0d7zpgWith the E2B SDK
The SDK requests public internet access by default, so turn it off explicitly. A command that passes its time limit throws an error:
import { Sandbox } from 'e2b';
const sandbox = await Sandbox.create('kibotos-minimal', {
allowInternetAccess: false,
timeoutMs: 10 * 60_000,
lifecycle: { onTimeout: 'kill', autoResume: false },
});
try {
await sandbox.files.write('/tmp/job.sh', 'echo computed 42 > /tmp/result.txt\n');
await sandbox.commands.run('sh /tmp/job.sh', { timeoutMs: 30_000 });
console.log(await sandbox.files.read('/tmp/result.txt'));
} finally {
await sandbox.kill();
}The gateway sets the resources of every sandbox, so the SDK cannot lower them for one sandbox. See Resources.
Notes
- Isolation comes from the MicroVM. The code can run as root inside it, and that does not reach the host or other Workspaces. See Isolation.
- Give each user or job its own Workspace. A Workspace is one trust boundary, and stopping it keeps its files.
- A Workspace with no network still answers data-plane calls from your program.
Next steps
- Network policy: allow only the destinations the code needs.
- Resources and health: limits and how to read resource use.
- Commands and terminals: process options, exit codes, and lost output.
Coding Agent with a persistent project
A coding Agent needs its project to survive when a session ends. This page keeps the project on a Volume of its own and gives each session a new Workspace bound to that Volume. The complete program is examples/go/coding-agent.
Batch creation and reclamation
This page creates many Workspaces so that a restart creates no duplicates, finds them through a label, and deletes them. The complete program is examples/go/fleet.