P6: shell.exec — agents execute code in their real hardened sandbox
- SandboxManager (tc-runtime): one container per agent, provisioned
lazily on first use, reused for the manager's lifetime, replaced
transparently if dead, destroyed on shutdown
- shell.exec tool: sh -lc inside the agent's sandbox; stdout/stderr/
exit_code return to the model as the step output. No external effects
declared — the sandbox boundary (uid 10001, no caps, seccomp
allowlist, read-only rootfs, zero egress) is the §15 control here,
not an approval gate
- RuntimeConfig.sandboxes (+ with_sandboxes builder); [sandbox] config
{image, enabled}; the server connects the Docker driver at boot and
tolerates an absent engine (shell.exec reports it per-call)
- Tests with the REAL DockerDriver: a scripted run executes two
commands — output proves uid 10001 from inside, and /home/agent state
written by the first call is read by the second (same sandbox); a
deployment without a sandbox runtime records honest error steps and
the run still completes
151 Rust tests + 27 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
84c51168be
commit
7392ce1d08
@@ -17,6 +17,7 @@ tc-db = { path = "../../tc-db" }
|
||||
tc-files = { path = "../../tc-files" }
|
||||
tc-llm = { path = "../../tc-llm" }
|
||||
tc-runtime = { path = "../../tc-runtime" }
|
||||
tc-sandbox = { path = "../../tc-sandbox" }
|
||||
tc-scheduler = { path = "../../tc-scheduler" }
|
||||
tc-domain = { path = "../../tc-domain" }
|
||||
time = { workspace = true }
|
||||
|
||||
@@ -83,6 +83,22 @@ async fn run() -> Result<(), String> {
|
||||
.map_err(|e| format!("s3 storage: {e}"))?,
|
||||
),
|
||||
};
|
||||
// Environment tools need a container engine; absence is tolerated
|
||||
// (shell.exec reports it per-call) so the API still serves.
|
||||
let sandboxes = if config.sandbox.enabled {
|
||||
match tc_sandbox::DockerDriver::connect() {
|
||||
Ok(driver) => Some(std::sync::Arc::new(tc_runtime::SandboxManager::new(
|
||||
std::sync::Arc::new(driver),
|
||||
&config.sandbox.image,
|
||||
))),
|
||||
Err(error) => {
|
||||
eprintln!("teamclaw-server: sandbox engine unavailable: {error}");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let runtime = Runtime::with_blob_store(
|
||||
pool.clone(),
|
||||
provider,
|
||||
@@ -91,6 +107,7 @@ async fn run() -> Result<(), String> {
|
||||
max_tokens: 4096,
|
||||
broker_socket: Some(PathBuf::from(&config.broker.socket_path)),
|
||||
slack_base_url: config.slack.base_url.clone(),
|
||||
sandboxes,
|
||||
},
|
||||
blob,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user