fix(scripts/pr): shell-escape env file values to prevent command injection via branch names

This commit is contained in:
zsxsoft 2026-03-31 03:04:24 +00:00 committed by Vincent Koc
parent f865a5455e
commit d15d7d0962
5 changed files with 85 additions and 62 deletions

View File

@ -114,15 +114,16 @@ prepare_gates() {
fi
fi
cat > .local/gates.env <<EOF_ENV
PR_NUMBER=$pr
DOCS_ONLY=$docs_only
CHANGELOG_REQUIRED=$changelog_required
GATES_MODE=$gates_mode
LAST_VERIFIED_HEAD_SHA=$current_head
FULL_GATES_HEAD_SHA=${previous_full_gates_head:-}
GATES_PASSED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF_ENV
# Security: shell-escape values to prevent command injection when sourced.
printf '%s=%q\n' \
PR_NUMBER "$pr" \
DOCS_ONLY "$docs_only" \
CHANGELOG_REQUIRED "$changelog_required" \
GATES_MODE "$gates_mode" \
LAST_VERIFIED_HEAD_SHA "$current_head" \
FULL_GATES_HEAD_SHA "${previous_full_gates_head:-}" \
GATES_PASSED_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
> .local/gates.env
echo "docs_only=$docs_only"
echo "changelog_required=$changelog_required"

View File

@ -78,13 +78,14 @@ prepare_init() {
git checkout -B "pr-$pr-prep" "pr-$pr"
git fetch origin main
cat > .local/prep-context.env <<EOF_ENV
PR_NUMBER=$pr
PR_HEAD=$head
PR_HEAD_SHA_BEFORE=$pr_head_sha_before
PREP_BRANCH=pr-$pr-prep
PREP_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF_ENV
# Security: shell-escape values to prevent command injection via malicious branch names.
printf '%s=%q\n' \
PR_NUMBER "$pr" \
PR_HEAD "$head" \
PR_HEAD_SHA_BEFORE "$pr_head_sha_before" \
PREP_BRANCH "pr-$pr-prep" \
PREP_STARTED_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
> .local/prep-context.env
if [ ! -f .local/prep.md ]; then
cat > .local/prep.md <<EOF_PREP
@ -173,14 +174,15 @@ prepare_push() {
- Verified PR head contains origin/main.
EOF_PREP
cat > .local/prep.env <<EOF_ENV
PR_NUMBER=$PR_NUMBER
PR_AUTHOR=$contrib
PR_HEAD=$PR_HEAD
PR_HEAD_SHA_BEFORE=$pushed_from_sha
PREP_HEAD_SHA=$prep_head_sha
COAUTHOR_EMAIL=$coauthor_email
EOF_ENV
# Security: shell-escape values to prevent command injection via propagated PR_HEAD.
printf '%s=%q\n' \
PR_NUMBER "$PR_NUMBER" \
PR_AUTHOR "$contrib" \
PR_HEAD "$PR_HEAD" \
PR_HEAD_SHA_BEFORE "$pushed_from_sha" \
PREP_HEAD_SHA "$prep_head_sha" \
COAUTHOR_EMAIL "$coauthor_email" \
> .local/prep.env
ls -la .local/prep.md .local/prep.env >/dev/null
@ -245,14 +247,15 @@ prepare_sync_head() {
- Prepare gates reran automatically when the sync rebase changed the prep head.
EOF_PREP
cat > .local/prep.env <<EOF_ENV
PR_NUMBER=$PR_NUMBER
PR_AUTHOR=$contrib
PR_HEAD=$PR_HEAD
PR_HEAD_SHA_BEFORE=$pushed_from_sha
PREP_HEAD_SHA=$prep_head_sha
COAUTHOR_EMAIL=$coauthor_email
EOF_ENV
# Security: shell-escape values to prevent command injection via propagated PR_HEAD.
printf '%s=%q\n' \
PR_NUMBER "$PR_NUMBER" \
PR_AUTHOR "$contrib" \
PR_HEAD "$PR_HEAD" \
PR_HEAD_SHA_BEFORE "$pushed_from_sha" \
PREP_HEAD_SHA "$prep_head_sha" \
COAUTHOR_EMAIL "$coauthor_email" \
> .local/prep.env
ls -la .local/prep.md .local/prep.env >/dev/null

View File

@ -288,9 +288,10 @@ push_prep_head_to_pr_branch() {
exit 1
}
git branch -D "pr-$pr-verify" 2>/dev/null || true
cat > "$result_env_path" <<EOF_ENV
PUSH_PREP_HEAD_SHA=$prep_head_sha
PUSHED_FROM_SHA=$pushed_from_sha
PR_HEAD_SHA_AFTER_PUSH=$pr_head_sha_after
EOF_ENV
# Security: shell-escape values to prevent command injection when sourced.
printf '%s=%q\n' \
PUSH_PREP_HEAD_SHA "$prep_head_sha" \
PUSHED_FROM_SHA "$pushed_from_sha" \
PR_HEAD_SHA_AFTER_PUSH "$pr_head_sha_after" \
> "$result_env_path"
}

View File

@ -1,9 +1,10 @@
set_review_mode() {
local mode="$1"
cat > .local/review-mode.env <<EOF_ENV
REVIEW_MODE=$mode
REVIEW_MODE_SET_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF_ENV
# Security: shell-escape values to prevent command injection when sourced.
printf '%s=%q\n' \
REVIEW_MODE "$mode" \
REVIEW_MODE_SET_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
> .local/review-mode.env
}
review_claim() {
@ -482,10 +483,11 @@ review_tests() {
exit 1
fi
{
echo "REVIEW_TESTS_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "REVIEW_TEST_TARGET_COUNT=$#"
} > .local/review-tests.env
# Security: shell-escape values to prevent command injection when sourced.
printf '%s=%q\n' \
REVIEW_TESTS_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
REVIEW_TEST_TARGET_COUNT "$#" \
> .local/review-tests.env
echo "review tests passed and were observed in output"
}
@ -502,11 +504,12 @@ review_init() {
local mb
mb=$(git merge-base origin/main "pr-$pr")
cat > .local/review-context.env <<EOF_ENV
PR_NUMBER=$pr
MERGE_BASE=$mb
REVIEW_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF_ENV
# Security: shell-escape values to prevent command injection when sourced.
printf '%s=%q\n' \
PR_NUMBER "$pr" \
MERGE_BASE "$mb" \
REVIEW_STARTED_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
> .local/review-context.env
set_review_mode main
printf '%s\n' "$json" | jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headSha:.headRefOid,headRepo:.headRepository.nameWithOwner,additions,deletions,files:(.files|length)}'

View File

@ -55,18 +55,33 @@ write_pr_meta_files() {
printf '%s\n' "$json" > .local/pr-meta.json
cat > .local/pr-meta.env <<EOF_ENV
PR_NUMBER=$(printf '%s\n' "$json" | jq -r .number)
PR_URL=$(printf '%s\n' "$json" | jq -r .url)
PR_AUTHOR=$(printf '%s\n' "$json" | jq -r .author.login)
PR_BASE=$(printf '%s\n' "$json" | jq -r .baseRefName)
PR_HEAD=$(printf '%s\n' "$json" | jq -r .headRefName)
PR_HEAD_SHA=$(printf '%s\n' "$json" | jq -r .headRefOid)
PR_HEAD_REPO=$(printf '%s\n' "$json" | jq -r .headRepository.nameWithOwner)
PR_HEAD_REPO_URL=$(printf '%s\n' "$json" | jq -r '.headRepository.url // ""')
PR_HEAD_OWNER=$(printf '%s\n' "$json" | jq -r '.headRepositoryOwner.login // ""')
PR_HEAD_REPO_NAME=$(printf '%s\n' "$json" | jq -r '.headRepository.name // ""')
EOF_ENV
# Security: shell-escape all values with printf %q to prevent command injection
# via malicious branch names containing $() or backticks. See GHSA-xxxx-xxxx-xxxx.
local pr_number pr_url pr_author pr_base pr_head pr_head_sha
local pr_head_repo pr_head_repo_url pr_head_owner pr_head_repo_name
pr_number=$(printf '%s\n' "$json" | jq -r .number)
pr_url=$(printf '%s\n' "$json" | jq -r .url)
pr_author=$(printf '%s\n' "$json" | jq -r .author.login)
pr_base=$(printf '%s\n' "$json" | jq -r .baseRefName)
pr_head=$(printf '%s\n' "$json" | jq -r .headRefName)
pr_head_sha=$(printf '%s\n' "$json" | jq -r .headRefOid)
pr_head_repo=$(printf '%s\n' "$json" | jq -r .headRepository.nameWithOwner)
pr_head_repo_url=$(printf '%s\n' "$json" | jq -r '.headRepository.url // ""')
pr_head_owner=$(printf '%s\n' "$json" | jq -r '.headRepositoryOwner.login // ""')
pr_head_repo_name=$(printf '%s\n' "$json" | jq -r '.headRepository.name // ""')
printf '%s=%q\n' \
PR_NUMBER "$pr_number" \
PR_URL "$pr_url" \
PR_AUTHOR "$pr_author" \
PR_BASE "$pr_base" \
PR_HEAD "$pr_head" \
PR_HEAD_SHA "$pr_head_sha" \
PR_HEAD_REPO "$pr_head_repo" \
PR_HEAD_REPO_URL "$pr_head_repo_url" \
PR_HEAD_OWNER "$pr_head_owner" \
PR_HEAD_REPO_NAME "$pr_head_repo_name" \
> .local/pr-meta.env
}
list_pr_worktrees() {