fix: scope delegated commands by cwd
This commit is contained in:
@@ -5,7 +5,9 @@ use agen::tool::{Tool, ToolDefinition, ToolError, ToolMeta, ToolOutput};
|
||||
use async_trait::async_trait;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use workdir::{CommandHandle, CommandOutputRequest, CommandRequest, WorkdirSessionHandle};
|
||||
use workdir::{
|
||||
CommandHandle, CommandOutputRequest, CommandRequest, WorkdirPath, WorkdirSessionHandle,
|
||||
};
|
||||
|
||||
const DEFAULT_TIMEOUT_SECS: u64 = 120;
|
||||
const MAX_TIMEOUT_SECS: u64 = 600;
|
||||
@@ -14,6 +16,10 @@ const INLINE_BYTE_BUDGET: usize = 12 * 1024;
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct BashParams {
|
||||
command: String,
|
||||
/// Optional logical working directory relative to the bound session cwd.
|
||||
/// Supplying it lets delegation guards prove the command is disjoint from child write scopes.
|
||||
#[serde(default)]
|
||||
cwd: Option<String>,
|
||||
#[serde(default)]
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
@@ -51,11 +57,18 @@ impl Tool for BashTool {
|
||||
.timeout
|
||||
.unwrap_or(DEFAULT_TIMEOUT_SECS)
|
||||
.clamp(1, MAX_TIMEOUT_SECS);
|
||||
let cwd = params
|
||||
.cwd
|
||||
.as_deref()
|
||||
.map(WorkdirPath::new)
|
||||
.transpose()
|
||||
.map_err(crate::ToolsError::from)?;
|
||||
let cmd_summary = truncate_for_summary(¶ms.command);
|
||||
let handle = self
|
||||
.session
|
||||
.start_command(CommandRequest {
|
||||
command: params.command,
|
||||
cwd,
|
||||
timeout_secs,
|
||||
output_limit: INLINE_BYTE_BUDGET,
|
||||
tool_call_id: Some(ctx.call_id),
|
||||
|
||||
@@ -390,6 +390,18 @@ async fn bash_inherits_workdir_cwd() {
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn bash_uses_explicit_logical_cwd() {
|
||||
let (dir, _spill, reg) = setup();
|
||||
std::fs::create_dir_all(dir.path().join("nested")).unwrap();
|
||||
let bash = reg.get("Bash");
|
||||
let out = call(&bash, json!({ "command": "pwd", "cwd": "nested" })).await;
|
||||
let body = out.content.unwrap();
|
||||
let actual = std::fs::canonicalize(body.trim()).unwrap();
|
||||
let expected = std::fs::canonicalize(dir.path().join("nested")).unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn bash_provider_output_does_not_expose_internal_paths() {
|
||||
let (_dir, spill, reg) = setup();
|
||||
|
||||
Reference in New Issue
Block a user