- 1169
out.push('\''); - 1170
out - 1171
} - 1172
- 1173
/// Decides how a sandbox wrapper applies to a brokered tool invocation. - 1174
/// - 1175
/// Two strategies: - 1176
/// - **`WorkerProcess` target** (Seatbelt/Landlock): the entire worker - 1177
/// process is wrapped, so the sandbox binary itself is sandboxed at exec. - 1178
/// - **`ToolCommand` target** (Docker): the worker runs on the host and only - 1179
/// the tool's own command is wrapped, so the model-controlled shell lands - 1180
/// inside the container. - 1181
/// - 1182
/// Returns the effective shell command string and mutates `request_args` - 1183
/// in place when the command-level wrapping path is taken. - 1184
fn resolve_worker_command( - 1185
sandbox: Option<&dyn crate::sandbox::Sandbox>, - 1186
tool: &str, - 1187
request_args: &mut serde_json::Value, - 1188
worker_command: &str, - 1189
) -> String { - 1190
match sandbox { - 1191
Some(sb) if sb.target() == SandboxTarget::WorkerProcess => sb.wrap(worker_command), - 1192
Some(sb) => { - 1193
if tool == "bash" - 1194
&& let Some(command) = request_args - 1195
.get("command") - 1196
.and_then(serde_json::Value::as_str) - 1197
{ - 1198
request_args["command"] = serde_json::Value::String(sb.wrap(command)); - 1199
} - 1200
worker_command.to_string() - 1201
} - 1202
None => worker_command.to_string(), - 1203
} - 1204
} - 1205
- 1206
#[cfg(test)] - 1207
mod tests { - 1208
#![allow(clippy::unwrap_used, clippy::expect_used)] - 1209
- 1210
use super::*; - 1211
use crate::sandbox::{Sandbox, SandboxMode, Seatbelt}; - 1212
use serde_json::json; - 1213
- 1214
#[test] - 1215
fn no_sandbox_passes_worker_command_through() { - 1216
let worker_cmd = "/path/to/vak __tool_worker"; - 1217
let mut args = json!({"command": "echo hi"}); - 1218
let effective = resolve_worker_command(None, "bash", &mut args, worker_cmd); - 1219
assert_eq!(effective, worker_cmd); - 1220
// args untouched - 1221
assert_eq!(args["command"], json!("echo hi")); - 1222
} - 1223
- 1224
#[test] - 1225
fn worker_process_target_wraps_the_worker_executable() { - 1226
let dir = std::env::temp_dir(); - 1227
let sb: Arc<dyn Sandbox> = Arc::new(Seatbelt::new(SandboxMode::ReadOnly, &dir)); - 1228
assert_eq!(sb.target(), SandboxTarget::WorkerProcess); - 1229
let worker_cmd = "/vak __tool_worker"; - 1230
let mut args = json!({"command": "echo hi"}); - 1231
let effective = resolve_worker_command(Some(&*sb), "bash", &mut args, worker_cmd); - 1232
// The worker executable itself — not the inner bash command — is - 1233
// wrapped, so the whole broker runs under sandbox-exec. - 1234
assert!( - 1235
effective.starts_with("sandbox-exec -p "), - 1236
"expected sandbox-exec wrapper, got: {effective}" - 1237
); - 1238
assert!(effective.contains("__tool_worker")); - 1239
// The inner bash command must NOT be wrapped — it travels through - 1240
// the worker protocol, not the shell wrapper. - 1241
assert!(!effective.contains("echo hi")); - 1242
} - 1243
- 1244
#[test] - 1245
fn tool_command_target_wraps_bash_command_in_args() { - 1246
// A Docker-style sandbox (ToolCommand target) must wrap the bash - 1247
// command inside the worker request args — the host worker stays - 1248
// a protocol adapter and only the model-controlled shell is sandboxed. - 1249
let sb: Arc<dyn Sandbox> = Arc::new(DockerStub { - 1250
target: SandboxTarget::ToolCommand, - 1251
wrap_fn: |cmd| format!("docker-run-wrapped({})", cmd), - 1252
}); - 1253
assert_eq!(sb.target(), SandboxTarget::ToolCommand); - 1254
- 1255
let worker_cmd = "/vak __tool_worker"; - 1256
- 1257
// bash tool: command gets wrapped in the args - 1258
let mut args = json!({"command": "echo from-bash"}); - 1259
let effective = resolve_worker_command(Some(&*sb), "bash", &mut args, worker_cmd); - 1260
assert_eq!( - 1261
effective, worker_cmd, - 1262
"worker command passes through unwrapped" - 1263
); - 1264
assert_eq!( - 1265
args["command"], - 1266
json!("docker-run-wrapped(echo from-bash)"), - 1267
"bash command must be wrapped in the request args" - 1268
); - 1269
- 1270
// non-bash tool: args untouched, worker command passes through - 1271
let mut args2 = json!({"path": "src/main.rs"}); - 1272
let effective2 = resolve_worker_command(Some(&*sb), "read", &mut args2, worker_cmd); - 1273
assert_eq!(effective2, worker_cmd); - 1274
assert_eq!(args2["path"], json!("src/main.rs")); - 1275
} - 1276
- 1277
#[test] - 1278
fn tool_command_target_does_not_wrap_bash_without_command_arg() { - 1279
let sb: Arc<dyn Sandbox> = Arc::new(DockerStub { - 1280
target: SandboxTarget::ToolCommand, - 1281
wrap_fn: |cmd| format!("wrapped({})", cmd), - 1282
}); - 1283
let worker_cmd = "/vak __tool_worker"; - 1284
let mut args = json!({}); - 1285
let effective = resolve_worker_command(Some(&*sb), "bash", &mut args, worker_cmd); - 1286
assert_eq!(effective, worker_cmd); - 1287
assert_eq!( - 1288
args["command"], - 1289
json!(Value::Null), - 1290
"no command key to wrap" - 1291
); - 1292
} - 1293
- 1294
/// Minimal Sandbox stub that lets us test ToolCommand-target wrapping - 1295
/// without a Docker daemon. - 1296
struct DockerStub { - 1297
target: SandboxTarget, - 1298
wrap_fn: fn(&str) -> String, - 1299
} - 1300
- 1301
impl Sandbox for DockerStub { - 1302
fn name(&self) -> &str { - 1303
"docker-stub" - 1304
} - 1305
fn wrap(&self, command: &str) -> String { - 1306
(self.wrap_fn)(command) - 1307
} - 1308
fn target(&self) -> SandboxTarget { - 1309
self.target - 1310
} - 1311
} - 1312
} - 1313
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.