Add sandboxed_shell
This commit is contained in:
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -2300,7 +2300,7 @@ dependencies = [
|
||||
"cap-primitives",
|
||||
"cap-std",
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2328,7 +2328,7 @@ dependencies = [
|
||||
"ipnet",
|
||||
"maybe-owned",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -4402,7 +4402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5064,7 +5064,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6709,7 +6709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
|
||||
dependencies = [
|
||||
"io-lifetimes",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10734,7 +10734,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11656,7 +11656,7 @@ dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"once_cell",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -11927,6 +11927,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"shlex",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -13426,7 +13427,7 @@ dependencies = [
|
||||
"fd-lock",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"winx",
|
||||
]
|
||||
|
||||
@@ -13566,7 +13567,7 @@ dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -15912,7 +15913,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16377,7 +16378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
|
||||
dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -16,6 +16,7 @@ doctest = false
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
collections.workspace = true
|
||||
shlex.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
mlua.workspace = true
|
||||
|
||||
104
crates/scripting_tool/src/sandboxed_shell.rs
Normal file
104
crates/scripting_tool/src/sandboxed_shell.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
/// Models will commonly generate POSIX shell one-liner commands which
|
||||
/// they run via io.popen() in Lua. Instead of giving those shell command
|
||||
/// strings to the operating system - which is a security risk, and
|
||||
/// which can eaisly fail on Windows, since Windows doesn't do POSIX - we
|
||||
/// parse the shell command ourselves and translate it into a sequence of
|
||||
/// commands in our normal sandbox. Essentially, this is an extremely
|
||||
/// minimalstic shell which Lua popen() commands can execute in.
|
||||
use mlua::{Error, Result};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct ShellCmd {
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
stdout_redirect: Option<String>,
|
||||
stderr_redirect: Option<String>,
|
||||
}
|
||||
|
||||
impl ShellCmd {
|
||||
/// Parse a shell string, which we assume the model will generate in POSIX format.
|
||||
/// Note that since we are turning this into our own representation, this should
|
||||
/// work seamlessly on Windows too, even though Windows has a different shell syntax.
|
||||
///
|
||||
/// If there are multiple commands piped into one another, this returns them all.
|
||||
pub fn parse_shell_str(string: &str) -> Result<Vec<Self>> {
|
||||
// For now, we don't support any of these shell features. We can add support them
|
||||
// in the future, though.
|
||||
if string.contains('$')
|
||||
|| string.contains('`')
|
||||
|| string.contains('(')
|
||||
|| string.contains(')')
|
||||
|| string.contains(';')
|
||||
|| string.contains('&')
|
||||
|| string.contains('{')
|
||||
|| string.contains('}')
|
||||
{
|
||||
return Err(Error::RuntimeError(
|
||||
"Complex shell features (pipes, subshells, variables, etc.) are not available in this shell."
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Use shlex to split the command line into tokens
|
||||
let tokens = shlex::split(string)
|
||||
.ok_or_else(|| Error::RuntimeError("Failed to parse shell command".to_string()))?;
|
||||
|
||||
// The first token is the command
|
||||
let mut tokens_iter = tokens.into_iter();
|
||||
let Some(command) = tokens_iter.next() else {
|
||||
return Err(Error::RuntimeError("Missing popen command".to_string()));
|
||||
};
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut stdout_redirect = None;
|
||||
let mut stderr_redirect = None;
|
||||
|
||||
// Process the remaining tokens
|
||||
while let Some(token) = tokens_iter.next() {
|
||||
match token.as_str() {
|
||||
">" | "1>" => {
|
||||
// stdout redirection
|
||||
let target = tokens_iter.next().ok_or_else(|| {
|
||||
Error::RuntimeError("Missing redirection target".to_string())
|
||||
})?;
|
||||
stdout_redirect = Some(target);
|
||||
}
|
||||
"2>" => {
|
||||
// stderr redirection
|
||||
let target = tokens_iter.next().ok_or_else(|| {
|
||||
Error::RuntimeError("Missing redirection target".to_string())
|
||||
})?;
|
||||
stderr_redirect = Some(target);
|
||||
}
|
||||
"&>" | ">&" => {
|
||||
// both stdout and stderr redirection
|
||||
let target = tokens_iter.next().ok_or_else(|| {
|
||||
Error::RuntimeError("Missing redirection target".to_string())
|
||||
})?;
|
||||
stdout_redirect = Some(target.clone());
|
||||
stderr_redirect = Some(target);
|
||||
}
|
||||
_ if token.starts_with(">")
|
||||
|| token.starts_with("2>")
|
||||
|| token.starts_with("&>") =>
|
||||
{
|
||||
// Handle cases like ">file" without a space
|
||||
return Err(Error::RuntimeError(
|
||||
"Redirections must have a space between operator and target".to_string(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
// Regular argument
|
||||
args.push(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ShellCmd {
|
||||
command,
|
||||
args,
|
||||
stdout_redirect,
|
||||
stderr_redirect,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod sandboxed_shell;
|
||||
mod session;
|
||||
|
||||
use project::Project;
|
||||
|
||||
Reference in New Issue
Block a user