T09 · Insecure Skill Coding Practices
- Location
scripts/collect_release_changes.sh:4- Finding
Unvalidated Git Revisions Allow Git Option Injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/collect_release_changes.sh, lines 4-31
Vulnerability Type: Git option injection through unvalidated and unquoted revision arguments
Risk Level: MediumVulnerable Code
bash since_ref="${1:-}" until_ref="${2:-HEAD}" if [[ -z "${since_ref}" ]]; then if git describe --tags --abbrev=0 >/dev/null 2>&1; then since_ref="$(git describe --tags --abbrev=0)" fi fi range="" if [[ -n "${since_ref}" ]]; then range="${since_ref}..${until_ref}" else range="${until_ref}" fi repo_root="$(git rev-parse --show-toplevel)" printf "Repo: %s\n" "${repo_root}" if [[ -n "${since_ref}" ]]; then printf "Range: %s..%s\n" "${since_ref}" "${until_ref}" else printf "Range: start..%s (no tags found)\n" "${until_ref}" fi printf "\n== Commits ==\n" git log --reverse --date=short --pretty=format:'%h|%ad|%s' ${range} printf "\n\n== Files Touched ==\n" git log --reverse --name-only --pretty=format:'--- %h %s' ${range} | sed '/^$/d'Technical Analysis
The script accepts
since_refanduntil_reffrom positional arguments without confirming that they resolve to valid Git revisions. The resultingrangevariable is then expanded without quotation in twogit logcommands.Consequently, shell word splitting and pathname expansion apply to the value. More importantly, when
since_refis empty, anuntil_refbeginning with-is passed directly togit logand can be interpreted as one or more command-line options rather than as a revision.This is Git option injection rather than direct shell command injection: shell metacharacters contained in the expanded variable are not reparsed as shell syntax. Nevertheless, supported Git log or diff options can alter command behavior. For example, output-related options may cause Git to write generated log content to a caller-selected writable path.
Attack Path
- An attacke ...[truncated 1701 chars]
- Remediation
View remediation
Remediation Suggestions
Validate both caller-provided values as Git revisions before using them, reject option-shaped inputs, and never pass an unquoted range to Git.
- Reject empty, whitespace-containing, or
--prefixed user-supplied revision names. - Resolve each revision to an immutable commit ID with
git rev-parse --verify. - Build the range from validated commit hashes, which cannot be interpreted as options.
- Quote the final range in every invocation.
- Return a clear error if either supplied revision does not resolve to a commit.
- Consider accepting only a restricted revision syntax if arbitrary Git revision expressions are unnecessary.
Example hardened implementation:
bash resolve_commit() { local ref="$1" if [[ -z "$ref" || "$ref" == -* || "$ref" =~ [[:space:]] ]]; then printf 'Invalid Git revision: %q\n' "$ref" >&2 return 1 fi git rev-parse --verify "${ref}^{commit}" } if [[ -n "$since_ref" ]]; then since_commit="$(resolve_commit "$since_ref")" fi until_commit="$(resolve_commit "$until_ref")" if [[ -n "${since_commit:-}" ]]; then range="${since_commit}..${until_commit}" else range="${until_commit}" fi git log --reverse --date=short \ --pretty=format:'%h|%ad|%s' "$range" git log --reverse --name-only \ --pretty=format:'--- %h %s' "$range" | sed '/^$/d'Where supported by the deployed Git version,
git rev-parse --verify --end-of-options "${ref}^{commit}"should also be used to strengthen option separation during validation.- Reject empty, whitespace-containing, or
