diff --git a/crates/astra-sandbox/src/bash_ast.rs b/crates/astra-sandbox/src/bash_ast.rs
index dc197847a..0f82dde1f 100644
--- a/crates/astra-sandbox/src/bash_ast.rs
+++ b/crates/astra-sandbox/src/bash_ast.rs
@@ -379,15 +379,69 @@ fn resolve_transparent_launcher(
let executable = command_basename(word.literal().ok_or(())?);
index += 1;
match executable.as_str() {
- "command" | "builtin" | "exec" | "nohup" => {
- let Some(next) = skip_literal_options(words, index, &[])? else {
+ "command" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new("pVv", "", &[], &[], &[]),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "builtin" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new("", "", &["--help"], &[], &[]),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "exec" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new("cl", "a", &["--help"], &[], &[]),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "nohup" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new("", "", &["--help", "--version"], &[], &[]),
+ )?
+ else {
return Ok(None);
};
index = next;
}
"env" => {
- let Some(next) =
- skip_literal_options(words, index, &["-u", "--unset", "-C", "--chdir"])?
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "i0v",
+ "uCPa",
+ &[
+ "--ignore-environment",
+ "--null",
+ "--debug",
+ "--list-signal-handling",
+ "--help",
+ "--version",
+ ],
+ &["--unset", "--chdir", "--path", "--argv0"],
+ &["--block-signal", "--default-signal", "--ignore-signal"],
+ ),
+ )?
else {
return Ok(None);
};
@@ -400,24 +454,152 @@ fn resolve_transparent_launcher(
index += 1;
}
}
- "sudo" | "doas" | "pkexec" => {
- let Some(next) = skip_literal_options(
+ "sudo" => {
+ let Some(next) = skip_launcher_options(
words,
index,
- &[
- "-u",
- "--user",
- "-g",
- "--group",
- "-h",
- "--host",
- "-p",
- "--prompt",
- "-R",
- "--chroot",
- "-C",
- "--close-from",
- ],
+ LauncherOptionGrammar::new(
+ "ABbEHikKlNnPSseVv",
+ "CDghpRTUurt",
+ &[
+ "--askpass",
+ "--background",
+ "--bell",
+ "--edit",
+ "--set-home",
+ "--help",
+ "--login",
+ "--remove-timestamp",
+ "--reset-timestamp",
+ "--list",
+ "--non-interactive",
+ "--preserve-groups",
+ "--stdin",
+ "--shell",
+ "--version",
+ "--validate",
+ ],
+ &[
+ "--close-from",
+ "--chdir",
+ "--group",
+ "--host",
+ "--prompt",
+ "--chroot",
+ "--command-timeout",
+ "--other-user",
+ "--role",
+ "--type",
+ "--user",
+ ],
+ &["--preserve-env"],
+ ),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "doas" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new("Lns", "aCu", &[], &[], &[]),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "pkexec" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "",
+ "",
+ &["--disable-internal-agent", "--keep-cwd", "--version"],
+ &["--user"],
+ &[],
+ ),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "timeout" | "gtimeout" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "fpv",
+ "ks",
+ &[
+ "--foreground",
+ "--preserve-status",
+ "--verbose",
+ "--help",
+ "--version",
+ ],
+ &["--kill-after", "--signal"],
+ &[],
+ ),
+ )?
+ else {
+ return Ok(None);
+ };
+ let Some(next) = skip_launcher_operands(words, next, 1)? else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "nice" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "",
+ "n",
+ &["--help", "--version"],
+ &["--adjustment"],
+ &[],
+ )
+ .with_legacy_numeric_short_option(),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "ionice" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "thV",
+ "cnpPu",
+ &["--ignore", "--help", "--version"],
+ &["--class", "--classdata", "--pid", "--pgid", "--uid"],
+ &[],
+ ),
+ )?
+ else {
+ return Ok(None);
+ };
+ index = next;
+ }
+ "setsid" => {
+ let Some(next) = skip_launcher_options(
+ words,
+ index,
+ LauncherOptionGrammar::new(
+ "cfwhV",
+ "",
+ &["--ctty", "--fork", "--wait", "--help", "--version"],
+ &[],
+ &[],
+ ),
)?
else {
return Ok(None);
@@ -429,10 +611,44 @@ fn resolve_transparent_launcher(
}
}
-fn skip_literal_options(
+#[derive(Clone, Copy)]
+struct LauncherOptionGrammar {
+ short_flags: &'static str,
+ short_options_with_value: &'static str,
+ long_flags: &'static [&'static str],
+ long_options_with_value: &'static [&'static str],
+ long_options_with_optional_value: &'static [&'static str],
+ legacy_numeric_short_option: bool,
+}
+
+impl LauncherOptionGrammar {
+ const fn new(
+ short_flags: &'static str,
+ short_options_with_value: &'static str,
+ long_flags: &'static [&'static str],
+ long_options_with_value: &'static [&'static str],
+ long_options_with_optional_value: &'static [&'static str],
+ ) -> Self {
+ Self {
+ short_flags,
+ short_options_with_value,
+ long_flags,
+ long_options_with_value,
+ long_options_with_optional_value,
+ legacy_numeric_short_option: false,
+ }
+ }
+
+ const fn with_legacy_numeric_short_option(mut self) -> Self {
+ self.legacy_numeric_short_option = true;
+ self
+ }
+}
+
+fn skip_launcher_options(
words: &[CommandWord],
mut index: usize,
- options_with_value: &[&str],
+ grammar: LauncherOptionGrammar,
) -> Result