chore: sync current workspace changes
Desktop CI Build / Build macOS ARM64 DMG (push) Has been cancelled
Desktop CI Build / Build Windows x64 (push) Has been cancelled
Desktop CI Build / Build macOS ARM64 DMG (push) Has been cancelled
Desktop CI Build / Build Windows x64 (push) Has been cancelled
This commit is contained in:
+48
-14
@@ -9,16 +9,16 @@
|
||||
|
||||
#![cfg(unix)]
|
||||
|
||||
use boosapi::services::agent_workbench::bash_sandbox::{build_bwrap_args, SandboxedBashTool};
|
||||
use boosapi::services::agent_workbench::bash_sandbox::{
|
||||
build_bwrap_args, sandbox_available, SandboxedBashTool,
|
||||
};
|
||||
use boosapi::services::agent_workbench::permission::PermissionMode;
|
||||
use open_agent_sdk::types::{Tool, ToolUseContext};
|
||||
use serde_json::json;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn bwrap_available() -> bool {
|
||||
["/usr/bin/bwrap", "/bin/bwrap", "/usr/local/bin/bwrap"]
|
||||
.iter()
|
||||
.any(|p| Path::new(p).is_file())
|
||||
sandbox_available()
|
||||
}
|
||||
|
||||
fn tmp_session() -> PathBuf {
|
||||
@@ -45,6 +45,7 @@ fn build_args_pin_session_dir_and_isolation() {
|
||||
assert!(j.contains("--chdir /srv/sess/abc"));
|
||||
assert!(args.iter().any(|a| a == "--unshare-all"));
|
||||
assert!(!j.contains("/etc"));
|
||||
assert!(!j.contains("/opt"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -71,9 +72,18 @@ async fn default_mode_blocks_listing_root() {
|
||||
let tool = SandboxedBashTool::new(tmp_session(), PermissionMode::Default);
|
||||
let (out, _) = run(&tool, "ls /").await;
|
||||
// /etc, /root, /home must be absent from the sandbox root.
|
||||
assert!(!out.contains("etc"), "sandbox root must not expose /etc: {out}");
|
||||
assert!(!out.contains("root"), "sandbox root must not expose /root: {out}");
|
||||
assert!(!out.contains("home"), "sandbox root must not expose /home: {out}");
|
||||
assert!(
|
||||
!out.contains("etc"),
|
||||
"sandbox root must not expose /etc: {out}"
|
||||
);
|
||||
assert!(
|
||||
!out.contains("root"),
|
||||
"sandbox root must not expose /root: {out}"
|
||||
);
|
||||
assert!(
|
||||
!out.contains("home"),
|
||||
"sandbox root must not expose /home: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -97,7 +107,10 @@ async fn default_mode_blocks_network() {
|
||||
}
|
||||
let tool = SandboxedBashTool::new(tmp_session(), PermissionMode::Default);
|
||||
let (out, _) = run(&tool, "getent hosts example.com || echo NET-BLOCKED").await;
|
||||
assert!(out.contains("NET-BLOCKED"), "network should be unshared: {out}");
|
||||
assert!(
|
||||
out.contains("NET-BLOCKED"),
|
||||
"network should be unshared: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -116,10 +129,31 @@ async fn default_mode_allows_work_inside_sandbox() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_access_mode_is_unconfined() {
|
||||
// FullAccess is the explicitly-dangerous mode: it should NOT be sandboxed,
|
||||
// so it can read the host (this documents the intended escape hatch).
|
||||
let tool = SandboxedBashTool::new(tmp_session(), PermissionMode::FullAccess);
|
||||
let (out, _) = run(&tool, "test -e /etc && echo HOST-VISIBLE || echo no").await;
|
||||
assert!(out.contains("HOST-VISIBLE"), "full access should see host: {out}");
|
||||
async fn output_flood_is_bounded() {
|
||||
if !bwrap_available() {
|
||||
eprintln!("SKIP: sandbox runtime not available");
|
||||
return;
|
||||
}
|
||||
let tool = SandboxedBashTool::new(tmp_session(), PermissionMode::Default);
|
||||
let (out, _) = run(&tool, "yes x | head -c 250000").await;
|
||||
assert!(out.len() < 101_000, "captured output exceeded hard cap");
|
||||
assert!(out.contains("output truncated"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn full_access_mode_remains_confined() {
|
||||
if !bwrap_available() {
|
||||
eprintln!("SKIP: sandbox runtime not available");
|
||||
return;
|
||||
}
|
||||
let tool = SandboxedBashTool::new(tmp_session(), PermissionMode::FullAccess);
|
||||
let (out, _) = run(
|
||||
&tool,
|
||||
"test -e /etc/passwd && echo HOST-VISIBLE || echo BLOCKED",
|
||||
)
|
||||
.await;
|
||||
assert!(
|
||||
out.contains("BLOCKED"),
|
||||
"full access must remain confined: {out}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,3 +63,10 @@ fn ordinary_new_file_still_allowed() {
|
||||
let sb = tmp_sandbox();
|
||||
assert!(validate_path_within_cwd("notes/today.md", &sb).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute_glob_pattern_is_rejected() {
|
||||
let sb = tmp_sandbox();
|
||||
let input = json!({ "pattern": "/etc/**" });
|
||||
assert!(check_tool_paths("Glob", &input, &sb).is_err());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
#![cfg(unix)]
|
||||
|
||||
use boosapi::services::agent_workbench::bash_sandbox::SandboxedBashTool;
|
||||
use boosapi::services::agent_workbench::bash_sandbox::{sandbox_available, SandboxedBashTool};
|
||||
use boosapi::services::agent_workbench::permission::PermissionMode;
|
||||
use open_agent_sdk::types::{Tool, ToolUseContext};
|
||||
use serde_json::json;
|
||||
@@ -30,14 +30,11 @@ use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn bwrap_available() -> bool {
|
||||
["/usr/bin/bwrap", "/bin/bwrap", "/usr/local/bin/bwrap"]
|
||||
.iter()
|
||||
.any(|p| Path::new(p).is_file())
|
||||
sandbox_available()
|
||||
}
|
||||
|
||||
fn tmp_session() -> PathBuf {
|
||||
let base =
|
||||
std::env::temp_dir().join(format!("agent_toctou_{}", uuid::Uuid::new_v4().simple()));
|
||||
let base = std::env::temp_dir().join(format!("agent_toctou_{}", uuid::Uuid::new_v4().simple()));
|
||||
std::fs::create_dir_all(&base).unwrap();
|
||||
base.canonicalize().unwrap()
|
||||
}
|
||||
@@ -60,7 +57,11 @@ async fn backgrounded_process_does_not_outlive_bash_call() {
|
||||
let cmd = "( for i in $(seq 1 200); do echo x >> racer_marker; sleep 0.01; done ) & \
|
||||
echo backgrounded";
|
||||
let res = tool.call(json!({ "command": cmd }), &ctx).await.unwrap();
|
||||
assert!(!res.is_error, "spawn call should succeed: {}", res.get_text());
|
||||
assert!(
|
||||
!res.is_error,
|
||||
"spawn call should succeed: {}",
|
||||
res.get_text()
|
||||
);
|
||||
|
||||
let marker = sd.join("racer_marker");
|
||||
let count_now = std::fs::read_to_string(&marker)
|
||||
|
||||
Reference in New Issue
Block a user