How-To Guides#
Task-oriented recipes for running and isolating untrusted code with Rustbox. Each section leads with a direct answer, then a copy-pasteable Rust example using the official rustbox-sdk crate. Every example targets the live API at https://api.rustbox.sh and the v2 result schema.
Setup#
Add the SDK and an async runtime, then construct a client once and reuse it. The client reads your API key and defaults to the production endpoint.
cargo add rustbox-sdk tokio --features tokio/macros,tokio/rt-multi-thread
use rustbox_sdk::{Rustbox, SubmitRequest};
#[tokio::main]
async fn main() -> Result<(), rustbox_sdk::RustboxError> {
// RUSTBOX_API_KEY is your `rb_live_...` key from the dashboard.
let client = Rustbox::new(&std::env::var("RUSTBOX_API_KEY").unwrap())?;
let res = client
.run(&SubmitRequest {
language: "python".into(),
code: "print('hello')".into(),
..Default::default()
})
.await?;
println!("{} {}", res["result"]["verdict"], res["output"]["stdout"]); // "AC" "hello\n"
Ok(())
}
Every recipe below assumes a client built as in this Setup section.
How do you run untrusted code in Rustbox from Rust?#
Install the rustbox-sdk crate, construct a Rustbox client with your API key, and call run() with a SubmitRequest. The client submits synchronously, polls if needed, and returns the full JSON result. Read the outcome from result.verdict. A value of AC means a clean exit.
let res = client
.run(&SubmitRequest {
language: "javascript".into(),
code: "console.log(40 + 2)".into(),
..Default::default()
})
.await?;
if res["result"]["verdict"] == "AC" {
print!("{}", res["output"]["stdout"].as_str().unwrap_or_default()); // 42
}
How do you enforce cgroups v2 memory limits in Rustbox?#
You do not set a memory flag per request. Rustbox enforces a hard cgroups v2 memory.max per profile, 256 MB on the Judge profile, bound to your API key. Exceeding it triggers the kernel OOM killer and an MLE verdict. Confirm enforcement by reading the evidence.limits.cgroup block on any result.
// Allocate 512 MB inside a 256 MB Judge sandbox -> the cgroup OOM killer fires.
let res = client
.run(&SubmitRequest {
language: "python".into(),
code: "x = bytearray(512 * 1024 * 1024) # exceeds the 256 MB limit".into(),
..Default::default()
})
.await?;
let cg = &res["evidence"]["limits"]["cgroup"];
println!("verdict: {}", res["result"]["verdict"]); // "MLE"
println!("cause: {}", res["result"]["cause"]); // "oom_kill"
println!("limit: {} bytes", cg["memory_limit_bytes"]); // 268435456 (256 MB)
println!("peak: {} bytes", cg["memory_peak_bytes"]);
println!("oom_kills: {}", cg["oom_kill_events"]); // >= 1
The MLE verdict is backed by the cgroup OOM notification, not an exit-code guess. See the Isolation Model for how cgroups v2 sits in the 8-layer stack.
How do you pass stdin to a sandboxed program in Rustbox?#
Set the stdin field on SubmitRequest to the input string (up to 256 KB). The sandbox pipes it to the program's standard input before execution. Captured output comes back in output.stdout, and output.integrity reports whether it was complete or truncated.
let res = client
.run(&SubmitRequest {
language: "python".into(),
code: "a, b = map(int, input().split())\nprint(a + b)".into(),
stdin: "5 10".into(),
..Default::default()
})
.await?;
println!("{}", res["output"]["stdout"]); // "15\n"
How do you detect a Time Limit Exceeded (TLE) verdict in Rust?#
Submit code that exceeds the profile wall-time limit (7 s on Judge for Python). The supervisor sends SIGKILL and returns result.verdict = "TLE" with result.cause = "wall_timeout". Check the verdict string; output.integrity will report crash_mid_write because the process was killed mid-run.
let res = client
.run(&SubmitRequest {
language: "python".into(),
code: "while True:\n pass".into(),
..Default::default()
})
.await?;
match res["result"]["verdict"].as_str() {
Some("TLE") => println!("timed out: {}", res["result"]["cause"]), // "wall_timeout"
Some(v) => println!("finished with verdict {v}"),
None => println!("still running"),
}
How do you run code asynchronously and poll for the result?#
Call submit() with wait = false to get a 202 response carrying an id and status: "pending". Then poll get_result(id) until status is completed or error. This frees you from holding a connection open and suits batch or webhook-driven pipelines.
use rustbox_sdk::SubmitOptions;
let pending = client
.submit(
&SubmitRequest {
language: "go".into(),
code: "package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"hi\") }".into(),
..Default::default()
},
false, // wait = false -> async
SubmitOptions::default(),
)
.await?;
let id = pending["id"].as_str().unwrap().to_string();
loop {
let res = client.get_result(&id).await?;
match res["status"].as_str() {
Some("completed") | Some("error") => {
println!("{}", res["result"]["verdict"]);
break;
}
_ => tokio::time::sleep(std::time::Duration::from_millis(100)).await,
}
}
How do you handle rate limits and transient errors in the Rust SDK?#
The SDK auto-retries 5xx and network failures with exponential backoff, tuned with with_max_retries. A 429 surfaces as RustboxError::RateLimit so you can back off; 401/403 surface as RustboxError::Auth. Match on the error enum to branch your handling.
use rustbox_sdk::RustboxError;
let client = Rustbox::new(&api_key)?.with_max_retries(3); // SDK retries 5xx/network
match client.run(&req).await {
Ok(res) => println!("{}", res["result"]["verdict"]),
Err(RustboxError::RateLimit) => eprintln!("429 - slow down and retry later"),
Err(RustboxError::Auth(code)) => eprintln!("auth failed (HTTP {code}) - check API key"),
Err(RustboxError::Server(_)) => eprintln!("5xx - SDK already retried, give up"),
Err(RustboxError::Timeout) => eprintln!("request exceeded the client timeout"),
Err(e) => eprintln!("other error: {e}"),
}
How do you choose between the Judge and Agent profiles?#
Your profile is normally bound to your API key. The Judge profile (256 MB, 7 s wall time, no network) is the open-beta default for evaluation runs. The Agent profile (1 GB, 30 s, filtered egress) suits LLM tool calls and is on the waitlist. The SDK exposes an optional profile override for entitled keys.
use rustbox_sdk::{Profile, SubmitRequest};
let res = client
.run(&SubmitRequest {
language: "python".into(),
code: "print('agent run')".into(),
profile: Some(Profile::Agent), // requires an Agent-entitled (non-trial) key
..Default::default()
})
.await?;
println!("ran on profile: {}", res["profile"]); // "judge" or "agent"
See Profiles & Limits for the full per-language limit table.
How do you verify isolation was actually applied to a run?#
Every completed result includes an evidence.isolation block listing controls_applied and controls_missing. Check that the controls you require, for example pid_namespace, network_namespace, memory_limit, and no_new_privileges, appear in controls_applied and that controls_missing is empty.
let res = client.run(&req).await?;
let iso = &res["evidence"]["isolation"];
let applied = iso["controls_applied"].as_array().cloned().unwrap_or_default();
let required = ["pid_namespace", "network_namespace", "memory_limit", "no_new_privileges"];
let all_present = required
.iter()
.all(|c| applied.iter().any(|a| a == c));
println!("isolation mode: {}", iso["mode"]); // "strict"
println!("all required controls applied: {all_present}"); // true
println!("controls missing: {}", iso["controls_missing"]); // []
How do you list the languages a Rustbox instance supports?#
Call get_languages(), which returns a Vec<String> of the canonical language identifiers available on the connected instance. Use it to validate input before submitting, or to drive a language picker. The open beta supports 8 languages: C, C++, Go, Java, JavaScript, Python, Rust, and TypeScript.
let languages = client.get_languages().await?; // Vec<String>
println!("{} languages: {:?}", languages.len(), languages);
let target = "rust";
if !languages.iter().any(|l| l == target) {
eprintln!("{target} is not available on this instance");
}
Next steps#
- Quickstart - from API key to first execution
- Rust SDK - full client reference
- GET /api/result - the complete v2 response schema and verdict table
- Rustbox vs Docker, Firecracker, gVisor & Wasm - isolation vectors compared
- Isolation Model - the 8 kernel-level layers behind every run