diff --git a/src/main.rs b/src/main.rs index 7b275ce..e2522a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1519,6 +1519,21 @@ mod tests { "outer pipe should receive stdout after restoration, got: {outer_output:?}" ); } + + #[test] + fn test_sanitize_completion_value_strips_shell_metacharacters() { + let value = "$(touch /tmp/pwn); hello|world && rm -rf /"; + assert_eq!( + sanitize_completion_value(value), + "touch /tmp/pwn helloworld rm -rf /" + ); + } + + #[test] + fn test_sanitize_completion_value_keeps_common_identifiers() { + let value = "LIN-123 team_alpha/user@example.com"; + assert_eq!(sanitize_completion_value(value), value); + } } fn sanitize_completion_field(value: &str) -> String { @@ -1533,6 +1548,18 @@ fn sanitize_completion_field(value: &str) -> String { .to_string() } +fn sanitize_completion_value(value: &str) -> String { + sanitize_completion_field(value) + .chars() + .filter(|ch| { + ch.is_ascii_alphanumeric() + || matches!(ch, ' ' | '.' | '_' | '-' | ':' | '/' | '@' | '+') + }) + .collect::() + .trim() + .to_string() +} + /// Handle the context command - detect current Linear issue from git branch async fn handle_context( output: &OutputOptions, @@ -1919,7 +1946,7 @@ async fn complete_teams(cache: Option<&cache::Cache>, prefix: &str) -> Result<() { println!( "{}\t{}", - sanitize_completion_field(key), + sanitize_completion_value(key), sanitize_completion_field(name) ); } @@ -1945,7 +1972,7 @@ async fn complete_projects(cache: Option<&cache::Cache>, prefix: &str) -> Result if prefix.is_empty() || name.to_lowercase().starts_with(&prefix_lower) { println!( "{}\t{}", - sanitize_completion_field(name), + sanitize_completion_value(name), sanitize_completion_field(state) ); } @@ -1991,7 +2018,7 @@ async fn complete_issues(prefix: &str) -> Result<()> { if prefix.is_empty() || id.to_uppercase().starts_with(&prefix_upper) { println!( "{}\t{}", - sanitize_completion_field(id), + sanitize_completion_value(id), sanitize_completion_field(title) ); } @@ -2081,7 +2108,7 @@ async fn complete_statuses( if prefix.is_empty() || name.to_lowercase().starts_with(&prefix_lower) { println!( "{}\t{}", - sanitize_completion_field(name), + sanitize_completion_value(name), sanitize_completion_field(type_) ); } @@ -2111,7 +2138,7 @@ async fn complete_users(cache: Option<&cache::Cache>, prefix: &str) -> Result<() { println!( "{}\t{}", - sanitize_completion_field(display), + sanitize_completion_value(display), sanitize_completion_field(name) ); } @@ -2137,7 +2164,7 @@ async fn complete_labels(cache: Option<&cache::Cache>, prefix: &str) -> Result<( if prefix.is_empty() || name.to_lowercase().starts_with(&prefix_lower) { println!( "{}\t{}", - sanitize_completion_field(name), + sanitize_completion_value(name), sanitize_completion_field(color) ); }