Unified · Split
| @@ -2300,7 +2300,7 @@ dependencies = [ · 2299 unmodified lines | ||
| 2300 | 2300 | "cap-primitives", |
| 2301 | 2301 | "cap-std", |
| 2302 | 2302 | "io-lifetimes", |
| 2303 | − "windows-sys 0.52.0", | |
| 2303 | + "windows-sys 0.59.0", | |
| 2304 | 2304 | ] |
| 2305 | 2305 | |
| 2306 | 2306 | [[package]] |
| @@ -2328,7 +2328,7 @@ dependencies = [ · 21 unmodified lines | ||
| 2328 | 2328 | "ipnet", |
| 2329 | 2329 | "maybe-owned", |
| 2330 | 2330 | "rustix", |
| 2331 | − "windows-sys 0.52.0", | |
| 2331 | + "windows-sys 0.59.0", | |
| 2332 | 2332 | "winx", |
| 2333 | 2333 | ] |
| 2334 | 2334 | |
| @@ -4402,7 +4402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 2067 unmodified lines | ||
| 4402 | 4402 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" |
| 4403 | 4403 | dependencies = [ |
| 4404 | 4404 | "libc", |
| 4405 | − "windows-sys 0.52.0", | |
| 4405 | + "windows-sys 0.59.0", | |
| 4406 | 4406 | ] |
| 4407 | 4407 | |
| 4408 | 4408 | [[package]] |
| @@ -5064,7 +5064,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" · 655 unmodified lines | ||
| 5064 | 5064 | dependencies = [ |
| 5065 | 5065 | "io-lifetimes", |
| 5066 | 5066 | "rustix", |
| 5067 | − "windows-sys 0.52.0", | |
| 5067 | + "windows-sys 0.59.0", | |
| 5068 | 5068 | ] |
| 5069 | 5069 | |
| 5070 | 5070 | [[package]] |
| @@ -6709,7 +6709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 1638 unmodified lines | ||
| 6709 | 6709 | checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" |
| 6710 | 6710 | dependencies = [ |
| 6711 | 6711 | "io-lifetimes", |
| 6712 | − "windows-sys 0.52.0", | |
| 6712 | + "windows-sys 0.59.0", | |
| 6713 | 6713 | ] |
| 6714 | 6714 | |
| 6715 | 6715 | [[package]] |
| @@ -10734,7 +10734,7 @@ dependencies = [ · 4018 unmodified lines | ||
| 10734 | 10734 | "once_cell", |
| 10735 | 10735 | "socket2", |
| 10736 | 10736 | "tracing", |
| 10737 | − "windows-sys 0.52.0", | |
| 10737 | + "windows-sys 0.59.0", | |
| 10738 | 10738 | ] |
| 10739 | 10739 | |
| 10740 | 10740 | [[package]] |
| @@ -11656,7 +11656,7 @@ dependencies = [ · 915 unmodified lines | ||
| 11656 | 11656 | "libc", |
| 11657 | 11657 | "linux-raw-sys", |
| 11658 | 11658 | "once_cell", |
| 11659 | − "windows-sys 0.52.0", | |
| 11659 | + "windows-sys 0.59.0", | |
| 11660 | 11660 | ] |
| 11661 | 11661 | |
| 11662 | 11662 | [[package]] |
| @@ -11927,6 +11927,7 @@ dependencies = [ · 264 unmodified lines | ||
| 11927 | 11927 | "serde", |
| 11928 | 11928 | "serde_json", |
| 11929 | 11929 | "settings", |
| 11930 | + "shlex", | |
| 11930 | 11931 | "util", |
| 11931 | 11932 | ] |
| 11932 | 11933 | |
| @@ -13426,7 +13427,7 @@ dependencies = [ · 1493 unmodified lines | ||
| 13426 | 13427 | "fd-lock", |
| 13427 | 13428 | "io-lifetimes", |
| 13428 | 13429 | "rustix", |
| 13429 | − "windows-sys 0.52.0", | |
| 13430 | + "windows-sys 0.59.0", | |
| 13430 | 13431 | "winx", |
| 13431 | 13432 | ] |
| 13432 | 13433 | |
| @@ -13566,7 +13567,7 @@ dependencies = [ · 133 unmodified lines | ||
| 13566 | 13567 | "getrandom 0.3.1", |
| 13567 | 13568 | "once_cell", |
| 13568 | 13569 | "rustix", |
| 13569 | − "windows-sys 0.52.0", | |
| 13570 | + "windows-sys 0.59.0", | |
| 13570 | 13571 | ] |
| 13571 | 13572 | |
| 13572 | 13573 | [[package]] |
| @@ -15912,7 +15913,7 @@ version = "0.1.9" · 2339 unmodified lines | ||
| 15912 | 15913 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 15913 | 15914 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" |
| 15914 | 15915 | dependencies = [ |
| 15915 | − "windows-sys 0.52.0", | |
| 15916 | + "windows-sys 0.59.0", | |
| 15916 | 15917 | ] |
| 15917 | 15918 | |
| 15918 | 15919 | [[package]] |
| @@ -16377,7 +16378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 458 unmodified lines | ||
| 16377 | 16378 | checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" |
| 16378 | 16379 | dependencies = [ |
| 16379 | 16380 | "bitflags 2.8.0", |
| 16380 | − "windows-sys 0.52.0", | |
| 16381 | + "windows-sys 0.59.0", | |
| 16381 | 16382 | ] |
| 16382 | 16383 | |
| 16383 | 16384 | [[package]] |
| @@ -16,6 +16,7 @@ doctest = false · 15 unmodified lines | ||
| 16 | 16 | anyhow.workspace = true |
| 17 | 17 | assistant_tool.workspace = true |
| 18 | 18 | collections.workspace = true |
| 19 | +shlex.workspace = true | |
| 19 | 20 | futures.workspace = true |
| 20 | 21 | gpui.workspace = true |
| 21 | 22 | mlua.workspace = true |
| @@ -0,0 +1,104 @@ | ||
| 1 | +/// Models will commonly generate POSIX shell one-liner commands which | |
| 2 | +/// they run via io.popen() in Lua. Instead of giving those shell command | |
| 3 | +/// strings to the operating system - which is a security risk, and | |
| 4 | +/// which can eaisly fail on Windows, since Windows doesn't do POSIX - we | |
| 5 | +/// parse the shell command ourselves and translate it into a sequence of | |
| 6 | +/// commands in our normal sandbox. Essentially, this is an extremely | |
| 7 | +/// minimalstic shell which Lua popen() commands can execute in. | |
| 8 | +use mlua::{Error, Result}; | |
| 9 | + | |
| 10 | +#[derive(Debug, Clone, PartialEq, Eq, Hash)] | |
| 11 | +struct ShellCmd { | |
| 12 | + command: String, | |
| 13 | + args: Vec<String>, | |
| 14 | + stdout_redirect: Option<String>, | |
| 15 | + stderr_redirect: Option<String>, | |
| 16 | +} | |
| 17 | + | |
| 18 | +impl ShellCmd { | |
| 19 | + /// Parse a shell string, which we assume the model will generate in POSIX format. | |
| 20 | + /// Note that since we are turning this into our own representation, this should | |
| 21 | + /// work seamlessly on Windows too, even though Windows has a different shell syntax. | |
| 22 | + /// | |
| 23 | + /// If there are multiple commands piped into one another, this returns them all. | |
| 24 | + pub fn parse_shell_str(string: &str) -> Result<Vec<Self>> { | |
| 25 | + // For now, we don't support any of these shell features. We can add support them | |
| 26 | + // in the future, though. | |
| 27 | + if string.contains('$') | |
| 28 | + || string.contains('`') | |
| 29 | + || string.contains('(') | |
| 30 | + || string.contains(')') | |
| 31 | + || string.contains(';') | |
| 32 | + || string.contains('&') | |
| 33 | + || string.contains('{') | |
| 34 | + || string.contains('}') | |
| 35 | + { | |
| 36 | + return Err(Error::RuntimeError( | |
| 37 | + "Complex shell features (pipes, subshells, variables, etc.) are not available in this shell." | |
| 38 | + .to_string(), | |
| 39 | + )); | |
| 40 | + } | |
| 41 | + | |
| 42 | + // Use shlex to split the command line into tokens | |
| 43 | + let tokens = shlex::split(string) | |
| 44 | + .ok_or_else(|| Error::RuntimeError("Failed to parse shell command".to_string()))?; | |
| 45 | + | |
| 46 | + // The first token is the command | |
| 47 | + let mut tokens_iter = tokens.into_iter(); | |
| 48 | + let Some(command) = tokens_iter.next() else { | |
| 49 | + return Err(Error::RuntimeError("Missing popen command".to_string())); | |
| 50 | + }; | |
| 51 | + | |
| 52 | + let mut args = Vec::new(); | |
| 53 | + let mut stdout_redirect = None; | |
| 54 | + let mut stderr_redirect = None; | |
| 55 | + | |
| 56 | + // Process the remaining tokens | |
| 57 | + while let Some(token) = tokens_iter.next() { | |
| 58 | + match token.as_str() { | |
| 59 | + ">" | "1>" => { | |
| 60 | + // stdout redirection | |
| 61 | + let target = tokens_iter.next().ok_or_else(|| { | |
| 62 | + Error::RuntimeError("Missing redirection target".to_string()) | |
| 63 | + })?; | |
| 64 | + stdout_redirect = Some(target); | |
| 65 | + } | |
| 66 | + "2>" => { | |
| 67 | + // stderr redirection | |
| 68 | + let target = tokens_iter.next().ok_or_else(|| { | |
| 69 | + Error::RuntimeError("Missing redirection target".to_string()) | |
| 70 | + })?; | |
| 71 | + stderr_redirect = Some(target); | |
| 72 | + } | |
| 73 | + "&>" | ">&" => { | |
| 74 | + // both stdout and stderr redirection | |
| 75 | + let target = tokens_iter.next().ok_or_else(|| { | |
| 76 | + Error::RuntimeError("Missing redirection target".to_string()) | |
| 77 | + })?; | |
| 78 | + stdout_redirect = Some(target.clone()); | |
| 79 | + stderr_redirect = Some(target); | |
| 80 | + } | |
| 81 | + _ if token.starts_with(">") | |
| 82 | + || token.starts_with("2>") | |
| 83 | + || token.starts_with("&>") => | |
| 84 | + { | |
| 85 | + // Handle cases like ">file" without a space | |
| 86 | + return Err(Error::RuntimeError( | |
| 87 | + "Redirections must have a space between operator and target".to_string(), | |
| 88 | + )); | |
| 89 | + } | |
| 90 | + _ => { | |
| 91 | + // Regular argument | |
| 92 | + args.push(token); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + } | |
| 96 | + | |
| 97 | + Ok(ShellCmd { | |
| 98 | + command, | |
| 99 | + args, | |
| 100 | + stdout_redirect, | |
| 101 | + stderr_redirect, | |
| 102 | + }) | |
| 103 | + } | |
| 104 | +} | |
| @@ -1,3 +1,4 @@ | ||
| 1 | +mod sandboxed_shell; | |
| 1 | 2 | mod session; |
| 2 | 3 | |
| 3 | 4 | use project::Project; |