History

Add sandboxed_shell

Richard Feldman committed 1 year ago

Commit 38136cb1 parent 6ed6e8b4 files · +118 −11Browse files

Unified · Split

4 files
  1. Cargo.lock +12 −11
  2. crates/scripting_tool/Cargo.toml +1 −0
  3. crates/scripting_tool/src/sandboxed_shell.rs +104 −0
  4. crates/scripting_tool/src/scripting_tool.rs +1 −0
Cargo.lock+12−11
@@ -2300,7 +2300,7 @@ dependencies = [ · 2299 unmodified lines
23002300 "cap-primitives",
23012301 "cap-std",
23022302 "io-lifetimes",
2303− "windows-sys 0.52.0",
2303+ "windows-sys 0.59.0",
23042304 ]
23052305
23062306 [[package]]
@@ -2328,7 +2328,7 @@ dependencies = [ · 21 unmodified lines
23282328 "ipnet",
23292329 "maybe-owned",
23302330 "rustix",
2331− "windows-sys 0.52.0",
2331+ "windows-sys 0.59.0",
23322332 "winx",
23332333 ]
23342334
@@ -4402,7 +4402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 2067 unmodified lines
44024402 checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
44034403 dependencies = [
44044404 "libc",
4405− "windows-sys 0.52.0",
4405+ "windows-sys 0.59.0",
44064406 ]
44074407
44084408 [[package]]
@@ -5064,7 +5064,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" · 655 unmodified lines
50645064 dependencies = [
50655065 "io-lifetimes",
50665066 "rustix",
5067− "windows-sys 0.52.0",
5067+ "windows-sys 0.59.0",
50685068 ]
50695069
50705070 [[package]]
@@ -6709,7 +6709,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 1638 unmodified lines
67096709 checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65"
67106710 dependencies = [
67116711 "io-lifetimes",
6712− "windows-sys 0.52.0",
6712+ "windows-sys 0.59.0",
67136713 ]
67146714
67156715 [[package]]
@@ -10734,7 +10734,7 @@ dependencies = [ · 4018 unmodified lines
1073410734 "once_cell",
1073510735 "socket2",
1073610736 "tracing",
10737− "windows-sys 0.52.0",
10737+ "windows-sys 0.59.0",
1073810738 ]
1073910739
1074010740 [[package]]
@@ -11656,7 +11656,7 @@ dependencies = [ · 915 unmodified lines
1165611656 "libc",
1165711657 "linux-raw-sys",
1165811658 "once_cell",
11659− "windows-sys 0.52.0",
11659+ "windows-sys 0.59.0",
1166011660 ]
1166111661
1166211662 [[package]]
@@ -11927,6 +11927,7 @@ dependencies = [ · 264 unmodified lines
1192711927 "serde",
1192811928 "serde_json",
1192911929 "settings",
11930+ "shlex",
1193011931 "util",
1193111932 ]
1193211933
@@ -13426,7 +13427,7 @@ dependencies = [ · 1493 unmodified lines
1342613427 "fd-lock",
1342713428 "io-lifetimes",
1342813429 "rustix",
13429− "windows-sys 0.52.0",
13430+ "windows-sys 0.59.0",
1343013431 "winx",
1343113432 ]
1343213433
@@ -13566,7 +13567,7 @@ dependencies = [ · 133 unmodified lines
1356613567 "getrandom 0.3.1",
1356713568 "once_cell",
1356813569 "rustix",
13569− "windows-sys 0.52.0",
13570+ "windows-sys 0.59.0",
1357013571 ]
1357113572
1357213573 [[package]]
@@ -15912,7 +15913,7 @@ version = "0.1.9" · 2339 unmodified lines
1591215913 source = "registry+https://github.com/rust-lang/crates.io-index"
1591315914 checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
1591415915 dependencies = [
15915− "windows-sys 0.52.0",
15916+ "windows-sys 0.59.0",
1591615917 ]
1591715918
1591815919 [[package]]
@@ -16377,7 +16378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 458 unmodified lines
1637716378 checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d"
1637816379 dependencies = [
1637916380 "bitflags 2.8.0",
16380− "windows-sys 0.52.0",
16381+ "windows-sys 0.59.0",
1638116382 ]
1638216383
1638316384 [[package]]
crates/scripting_tool/Cargo.toml+1−0
@@ -16,6 +16,7 @@ doctest = false · 15 unmodified lines
1616 anyhow.workspace = true
1717 assistant_tool.workspace = true
1818 collections.workspace = true
19+shlex.workspace = true
1920 futures.workspace = true
2021 gpui.workspace = true
2122 mlua.workspace = true
crates/scripting_tool/src/sandboxed_shell.rs+104−0
@@ -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+}
crates/scripting_tool/src/scripting_tool.rs+1−0
@@ -1,3 +1,4 @@
1+mod sandboxed_shell;
12 mod session;
23
34 use project::Project;