mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
Reject shell globs in safe command parsing
This commit is contained in:
@@ -152,10 +152,10 @@ fn parse_plain_command_from_node(cmd: tree_sitter::Node, src: &str) -> Option<Ve
|
||||
if word_node.kind() != "word" {
|
||||
return None;
|
||||
}
|
||||
words.push(word_node.utf8_text(src.as_bytes()).ok()?.to_owned());
|
||||
words.push(parse_unquoted_literal(word_node, src)?);
|
||||
}
|
||||
"word" | "number" => {
|
||||
words.push(child.utf8_text(src.as_bytes()).ok()?.to_owned());
|
||||
words.push(parse_unquoted_literal(child, src)?);
|
||||
}
|
||||
"string" => {
|
||||
let parsed = parse_double_quoted_string(child, src)?;
|
||||
@@ -172,8 +172,7 @@ fn parse_plain_command_from_node(cmd: tree_sitter::Node, src: &str) -> Option<Ve
|
||||
for part in child.named_children(&mut concat_cursor) {
|
||||
match part.kind() {
|
||||
"word" | "number" => {
|
||||
concatenated
|
||||
.push_str(part.utf8_text(src.as_bytes()).ok()?.to_owned().as_str());
|
||||
concatenated.push_str(&parse_unquoted_literal(part, src)?);
|
||||
}
|
||||
"string" => {
|
||||
let parsed = parse_double_quoted_string(part, src)?;
|
||||
@@ -197,6 +196,18 @@ fn parse_plain_command_from_node(cmd: tree_sitter::Node, src: &str) -> Option<Ve
|
||||
Some(words)
|
||||
}
|
||||
|
||||
fn parse_unquoted_literal(node: Node, src: &str) -> Option<String> {
|
||||
let text = node.utf8_text(src.as_bytes()).ok()?;
|
||||
if text.starts_with('~') || text.chars().any(is_unquoted_shell_expansion_char) {
|
||||
return None;
|
||||
}
|
||||
Some(text.to_string())
|
||||
}
|
||||
|
||||
fn is_unquoted_shell_expansion_char(c: char) -> bool {
|
||||
matches!(c, '*' | '?' | '[' | ']' | '{' | '}')
|
||||
}
|
||||
|
||||
fn parse_heredoc_command_words(cmd: Node<'_>, src: &str) -> Option<Vec<String>> {
|
||||
if cmd.kind() != "command" {
|
||||
return None;
|
||||
@@ -500,6 +511,26 @@ mod tests {
|
||||
assert!(parse_seq("rg -g\"$(echo '*.py')\" pattern").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unquoted_shell_expansion_words() {
|
||||
assert!(parse_seq("base64 -i input.txt -* cc06_out").is_none());
|
||||
assert!(parse_seq("ls *.rs").is_none());
|
||||
assert!(parse_seq("echo -{o,}").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_quoted_shell_expansion_literals() {
|
||||
assert_eq!(
|
||||
parse_seq(r#"echo "-*" '*.rs' "-{o,}""#).unwrap(),
|
||||
vec![vec![
|
||||
"echo".to_string(),
|
||||
"-*".to_string(),
|
||||
"*.rs".to_string(),
|
||||
"-{o,}".to_string(),
|
||||
]]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_shell_lc_single_command_prefix_supports_heredoc() {
|
||||
let command = vec![
|
||||
|
||||
@@ -738,6 +738,18 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bash_lc_glob_expansion_option_injection_is_not_safe() {
|
||||
assert!(
|
||||
!is_known_safe_command(&vec_str(&[
|
||||
"bash",
|
||||
"-lc",
|
||||
"base64 -i input.txt -* cc06_out",
|
||||
])),
|
||||
"Unquoted shell expansion can rewrite argv into base64 -o at runtime"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_powershell_words_use_windows_safelist() {
|
||||
let command = vec_str(&["Get-Content", "Cargo.toml"]);
|
||||
|
||||
Reference in New Issue
Block a user