diff --git a/esphome/build_helpers/ninja.py b/esphome/build_helpers/ninja.py index 9a9189e8ee..6a7bac1f56 100644 --- a/esphome/build_helpers/ninja.py +++ b/esphome/build_helpers/ninja.py @@ -52,7 +52,10 @@ def quote_arg(tok: str) -> str: return f'"{quoted}"' -_NEEDS_QUOTE = re.compile(r'[\s"\']') +# Force-quote any token containing a character outside the shlex.quote-style +# safe set: ninja hands POSIX commands to /bin/sh -c, so bare (, ;, <, *, ` +# and friends would be re-parsed as shell syntax. +_NEEDS_QUOTE = re.compile(r"[^\w@%+=:,./-]") def shell_token(tok: str, force: bool = False) -> str: diff --git a/tests/unit_tests/build_helpers/test_ninja.py b/tests/unit_tests/build_helpers/test_ninja.py index 9143988acd..1dc49396c6 100644 --- a/tests/unit_tests/build_helpers/test_ninja.py +++ b/tests/unit_tests/build_helpers/test_ninja.py @@ -62,11 +62,17 @@ def test_quote_arg_windows_argv_rule() -> None: def test_shell_token_quotes_only_when_needed() -> None: assert ninja_helper.shell_token("-Os") == "-Os" - assert ninja_helper.shell_token("-DX=$HOME") == "-DX=$$HOME" assert ninja_helper.shell_token("-DP=C:\\x y") == '"-DP=C:\\x y"' assert ninja_helper.shell_token("plain", force=True) == '"plain"' +def test_shell_token_quotes_shell_metacharacters() -> None: + """Tokens like -DMASK=(1<<3) must not reach /bin/sh -c bare.""" + assert ninja_helper.shell_token("-DMASK=(1<<3)") == '"-DMASK=(1<<3)"' + assert ninja_helper.shell_token("-DX=a;b") == '"-DX=a;b"' + assert ninja_helper.shell_token("-DX=$HOME") == '"-DX=$$HOME"' + + def test_quote_path_force_quotes() -> None: assert ninja_helper.quote_path(Path("a b")) == '"a b"' assert ninja_helper.quote_path("simple") == '"simple"'