Add sandboxed_shell

This commit is contained in:
Richard Feldman
2025-03-07 12:07:53 -05:00
parent 6ed6e8bc26
commit 38136cb0c0
4 changed files with 118 additions and 11 deletions

23
Cargo.lock generated
View File

@@ -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]]

View File

@@ -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

View 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,
})
}
}

View File

@@ -1,3 +1,4 @@
mod sandboxed_shell;
mod session;
use project::Project;