From daa5fe79b027277dca1448578fbc504f67dd2c17 Mon Sep 17 00:00:00 2001 From: zhiwuyazhe_fjr Date: Fri, 7 Aug 2026 13:44:30 +0800 Subject: [PATCH] fix(rewrite): preserve quoted assignment data --- src/discover/registry.rs | 42 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/discover/registry.rs b/src/discover/registry.rs index 6469b178d8..d559c6d57b 100644 --- a/src/discover/registry.rs +++ b/src/discover/registry.rs @@ -63,8 +63,11 @@ static COMPILED: LazyLock> = LazyLock::new(|| { static ENV_PREFIX: LazyLock = LazyLock::new(|| { let double_quoted = r#""(?:[^"\\]|\\.)*""#; let single_quoted = r#"'(?:[^'\\]|\\.)*'"#; - let unquoted = r#"[^\s]*"#; - let env_value = format!("(?:{}|{}|{})", double_quoted, single_quoted, unquoted); + // Quotes must be handled by the complete quoted alternatives above. + // Otherwise regex backtracking can reinterpret a quoted assignment as a + // partial unquoted value and expose literal data as a command (#3262). + let unquoted = r#"[^\s'"]+"#; + let env_value = format!("(?:{}|{}|{})*", double_quoted, single_quoted, unquoted); let env_assign = format!(r#"[A-Z_][A-Z0-9_]*={}"#, env_value); Regex::new(&format!(r#"^(?:sudo\s+|env\s+|{}\s+)+"#, env_assign)).unwrap() }); @@ -2813,6 +2816,14 @@ mod tests { ); } + #[test] + fn test_rewrite_env_concatenated_quoted_and_unquoted_value() { + assert_eq!( + rewrite_command_no_prefixes("FOO='bar baz'qux git status", &[]), + Some("FOO='bar baz'qux rtk git status".into()) + ); + } + #[test] fn test_classify_env_quoted_value_stripped() { assert_eq!( @@ -4546,6 +4557,33 @@ mod tests { ); } + #[test] + fn test_rewrite_compound_preserves_quoted_assignment_data() { + let command = r#"D='# shellcheck disable=SC2034 # comment'; for f in hooks/*.sh; do sed -i '' -e "s|^TS_BACKUP=|${D}\nTS_BACKUP=|" "$f"; done"#; + + assert_eq!(rewrite_command_no_prefixes(command, &[]), None); + } + + #[test] + fn test_rewrite_compound_only_rewrites_commands_outside_assignment_quotes() { + let command = "D='# shellcheck | git status; $(whoami)' ; cargo test"; + + assert_eq!( + rewrite_command_no_prefixes(command, &[]), + Some("D='# shellcheck | git status; $(whoami)'; rtk cargo test".into()) + ); + } + + #[test] + fn test_rewrite_compound_preserves_double_quoted_assignment_data() { + let command = r#"D="run shellcheck later"; git status"#; + + assert_eq!( + rewrite_command_no_prefixes(command, &[]), + Some(r#"D="run shellcheck later"; rtk git status"#.into()) + ); + } + #[test] fn test_rewrite_compound_pipe_raw_filter() { // Producers stay raw; only a pipeline-safe final stage is rewritten.