Title: fix(security): prevent command injection via shell=True (CWE-78) by bearomorphism · Pull Request #1941 · commitizen-tools/commitizen · GitHub
Open Graph Title: fix(security): prevent command injection via shell=True (CWE-78) by bearomorphism · Pull Request #1941 · commitizen-tools/commitizen
X Title: fix(security): prevent command injection via shell=True (CWE-78) by bearomorphism · Pull Request #1941 · commitizen-tools/commitizen
Description: Description Fix command injection vulnerability (CWE-78) where shell=True in cmd.run() allowed arbitrary command execution via user-controlled values interpolated into git commands (e.g., annotated_tag_message in pyproject.toml). Closes #1918 Changes Split cmd.run() into two explicit APIs: cmd.run(Sequence[str]) -- executes commands with shell=False (safe, no injection) cmd.run(str) -- deprecated, emits DeprecationWarning, still uses shell=True for backward compat cmd.run_shell(str) -- explicit opt-in for shell=True (only used by user-defined hooks) Converted all git operations to list-based execution (shell=False): git.tag(), git.add(), git.commit(), git.get_commits(), git.get_tags() git.get_filenames_in_commit(), git.tag_exist(), git.is_signed_tag() git.get_tag_message(), git.get_tag_names(), git.find_git_project_root() git.is_staging_clean(), git.is_git_project(), git.get_core_editor() git._get_log_as_str_list(), git.get_default_branch() Eliminated args string splitting (list->string->list round-trip): git.commit(args=...) and git.get_commits(args=...) now accept Sequence[str] instead of str cli.py passes extra_cli_args as a list directly from argparse (no " ".join()) bump._get_commit_args() returns list[str] instead of joined string This preserves arguments with spaces that were previously broken by naive .split() Other improvements: git.commit() uses env={"GIT_COMMITTER_DATE": ...} instead of OS-specific shell tricks (cmd /v /c on Windows, env prefix on Unix) Removed _create_commit_cmd_string() helper (no longer needed) Removed shell quoting artifacts from get_tags() and get_tag_message() format strings (fixes a Windows bug where literal quotes appeared in output) hooks.py uses cmd.run_shell() -- intentionally shell-based for user-defined hooks with pipes/redirects Checklist I have read the contributing guidelines Was generative AI tooling used to co-author this PR? Yes (please specify the tool below) Generated-by: GitHub Copilot CLI following the guidelines Code Changes Add test cases to all the changes you introduce Run uv run poe all locally to ensure this change passes linter check and tests Manually test the changes: Verify the feature/bug fix works as expected in real-world scenarios Test edge cases and error conditions Ensure backward compatibility is maintained Document any manual testing steps performed Update the documentation for the changes Documentation Changes N/A - Internal implementation change only, no user-facing behavior or CLI changes. Expected Behavior User-controlled values (tag names, messages, file paths) are passed as literal arguments to git commands without shell interpretation, preventing command injection attacks in CI/CD pipelines. Steps to Test This Pull Request Create a pyproject.toml with a malicious annotated_tag_message: [tool.commitizen] name = "cz_conventional_commits" version = "0.1.0" tag_format = "$version" annotated_tag = true annotated_tag_message = 'Release" && echo INJECTED && echo "' Run cz bump --yes Verify that INJECTED is NOT printed (command not executed) Verify the tag is created with the literal message string Verification: All Attack Vectors from #1918 Addressed Affected code (from issue) Fix applied cmd.run(f'git tag {option} {tag} -m "{msg or tag}"') cmd.run(["git", "tag", option, tag, "-m", msg or tag]) (line 177) cmd.run(f"git tag {tag}") cmd.run(["git", "tag", tag]) (line 171) cmd.run(f"git add {' '.join(args)}") cmd.run(["git", "add", *args]) (line 181) subprocess.Popen(cmd, shell=True) shell=False for all list-based cmd.run() calls Manual validation result: Running cz bump with a malicious annotated_tag_message = 'Release" && echo INJECTED && echo "' -- the injected command was NOT executed, and the tag was created with the literal string as its message. Backward Compatibility cmd.run() accepts both str (deprecated, emits DeprecationWarning, uses shell=True) and Sequence[str] (safe, shell=False). External plugins calling cmd.run("git ...") will continue to work but receive a deprecation warning guiding them to migrate. Additional Context Related: CWE-78 (OS Command Injection) The attack vector requires a poisoned pyproject.toml (e.g., via malicious PR) that gets executed in CI/CD running cz bump. With shell=True, values like annotated_tag_message were interpolated into shell command strings, allowing arbitrary command execution. With shell=False + list args, each value is a literal argv entry -- shell metacharacters (&&, ;, |, backticks) have no special meaning.
Open Graph Description: Description Fix command injection vulnerability (CWE-78) where shell=True in cmd.run() allowed arbitrary command execution via user-controlled values interpolated into git commands (e.g., annotated...
X Description: Description Fix command injection vulnerability (CWE-78) where shell=True in cmd.run() allowed arbitrary command execution via user-controlled values interpolated into git commands (e.g., annotated...
Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1941
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:0621a181-03a4-8cf4-d68d-fed166dcd178 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | EDC2:38A7FD:1506ECF:1D4CF1E:6A4DF126 |
| html-safe-nonce | ab223a6e5f920c95f8bea085b407c99bab770f6decb6a890bbb7aee81b96bbde |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFREMyOjM4QTdGRDoxNTA2RUNGOjFENENGMUU6NkE0REYxMjYiLCJ2aXNpdG9yX2lkIjoiMTk2MTE1NTkxNTY3NjM0NzI2IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 7ddcf2e4c253fb8a525d04587adb703e425d154fc7f826f88c57b46e33dfcbb1 |
| hovercard-subject-tag | pull_request:3619405962 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1941/files |
| twitter:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| og:image:alt | Description Fix command injection vulnerability (CWE-78) where shell=True in cmd.run() allowed arbitrary command execution via user-controlled values interpolated into git commands (e.g., annotated... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5818716c93c6a2925b815402541a32814e43a7b1261c322b0c2df75224289566 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git |
| octolytics-dimension-user_id | 62252524 |
| octolytics-dimension-user_login | commitizen-tools |
| octolytics-dimension-repository_id | 106127589 |
| octolytics-dimension-repository_nwo | commitizen-tools/commitizen |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 106127589 |
| octolytics-dimension-repository_network_root_nwo | commitizen-tools/commitizen |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f4bb89367ca678f057d79b1abc45d6675b1bd5b2 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width