chore(security): prevent script and commit-message injections#512
chore(security): prevent script and commit-message injections#512olivermeyer merged 4 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Hardens GitHub Actions workflows against script/command injection by avoiding direct interpolation of GitHub context/step-output expressions inside run: bash blocks, and instead passing values via env with proper quoting.
Changes:
- Refactors commit-message retrieval in
ci-cd.ymlto use env vars for event data and quotes commit messages safely. - Updates release publishing steps to pass tag/changelog paths via env vars and quote them when used by
cat/gh. - Updates the composite test action to pass inputs via env vars and avoid direct expression interpolation in shell.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| .github/workflows/ci-cd.yml | Uses env vars for event fields and quotes commit message handling when exporting to outputs. |
| .github/workflows/_package-publish.yml | Uses env vars for changelog/tag/notes content and quotes CLI arguments for release creation and printing notes. |
| .github/actions/run-tests/action.yml | Uses env vars for composite-action inputs when running make and writing step summaries. |
a0fe79f to
d0cbc14
Compare
d0cbc14 to
deb91ac
Compare
|
Claude finished @olivermeyer's task in 4m 13s —— View job PR Review: Security Hardening for GitHub Actions ✅This PR implements comprehensive and correct security improvements to prevent script injection and commit-message injection attacks in GitHub Actions workflows. Summary of Changes4 files changed with systematic security hardening:
✅ Security Improvements ValidatedAttack Vector MitigatedThe PR prevents script injection where malicious content in GitHub context expressions (commit messages, step outputs, etc.) could execute arbitrary commands when interpolated directly into shell Example attack scenario (now prevented): # ❌ VULNERABLE (before):
run: echo "${{ github.event.head_commit.message }}"
# If commit message is: `"; rm -rf / #`
# Shell executes: echo ""; rm -rf / #"
# ✅ SECURE (after):
env:
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: echo "$COMMIT_MSG"
# Shell variable expansion is safe - no command injection possibleSecurity Best Practices Applied
📋 Detailed Review1. ci-cd.yml (Lines 43-64)# ✅ EXCELLENT
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
COMMIT_MESSAGE=$(git log -1 --format=%B $PR_HEAD_SHA) # ← Quoted in build-native-only.yml
else
COMMIT_MESSAGE="$HEAD_COMMIT_MESSAGE"
fi
{
echo "commit_message<<EOF"
printf "%s\n" "$COMMIT_MESSAGE" # ← Safe output
echo "EOF"
} >> $GITHUB_OUTPUTNote: In
Suggestion: For consistency, consider quoting it in 2. _package-publish.yml2a. Print release notes (Lines 169-172): # ✅ EXCELLENT
env:
GIT_CLIFF_CHANGELOG: ${{ steps.git-cliff.outputs.changelog }}
run: cat "$GIT_CLIFF_CHANGELOG"2b. Create GitHub release (Lines 206-213, 220-227): # ✅ EXCELLENT
env:
GH_REF_NAME: ${{ github.ref_name }}
GIT_CLIFF_CHANGELOG: ${{ steps.git-cliff.outputs.changelog }}
run: |
gh release create "$GH_REF_NAME" ./dist/* ./dist_native_zipped/* \
--notes-file "$GIT_CLIFF_CHANGELOG"2c. Convert release notes (Lines 242-252): # ✅ EXCELLENT - Addresses Copilot's comment!
env:
GIT_CLIFF_CONTENT: ${{ steps.git-cliff.outputs.content }}
run: |
SLACK_RELEASE_NOTES=$(printf '%s\n' "$GIT_CLIFF_CONTENT" | \
sed -E 's/\[([^]]+)\]\(([^)]+)\)/<\2|\1>/g' | \
sed -E 's/\*\*([^*]+)\*\*/*\1*/g')Note: Copilot suggested using 3. build-native-only.yml (Lines 33-51, 63-68)# ✅ EXCELLENT - Same pattern as ci-cd.yml
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
run: |
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
COMMIT_MESSAGE=$(git log -1 --format=%B "$PR_HEAD_SHA") # ← Quoted (good!)
else
COMMIT_MESSAGE="$HEAD_COMMIT_MESSAGE"
fiBonus improvement: Lines 63-68 also secure the workflow trigger check: # ✅ EXCELLENT
env:
COMMIT_HAS_MARKER: ${{ contains(..., 'build:native:only') }}
PR_HAS_MARKER: ${{ contains(..., 'build:native:only') }}
run: |
if [[ "$COMMIT_HAS_MARKER" == "true" ]] || \
[[ "$PR_HAS_MARKER" == "true" ]]; then4. run-tests/action.yml (Lines 33-38, 50)# ✅ EXCELLENT
env:
MAKE_TARGET: ${{ inputs.make-target }}
SUMMARY_TITLE: ${{ inputs.summary-title }}
run: |
make $MAKE_TARGET
# ...
echo "# $SUMMARY_TITLE" >> $GITHUB_STEP_SUMMARY🔍 Additional Patterns to Consider (Non-Blocking)While reviewing, I found similar patterns in other workflows that could benefit from the same treatment: _scheduled-test-daily.ymlLine 260: FAILED_TESTS="${{ steps.test-status.outputs.failed_tests }}"Line 353: echo "❌ Workflow failed due to test failures: ${{ steps.test-status.outputs.failed_tests }}"Analysis: Suggested fix (optional): env:
FAILED_TESTS: ${{ steps.test-status.outputs.failed_tests }}
run: |
echo "❌ Workflow failed due to test failures: $FAILED_TESTS"_test.yml & _scheduled-test-daily.yml (outcome checks)Pattern: if [ "${{ steps.unit.outcome }}" == "failure" ]Analysis: 📚 Documentation Update (Suggestion)Consider adding this security pattern to ## Security: Preventing Script Injection in Workflows
**CRITICAL**: Never interpolate GitHub context expressions directly into shell `run:` blocks.
### ❌ Vulnerable Pattern
```yaml
run: echo "${{ github.event.head_commit.message }}"
run: cat "${{ steps.example.outputs.file }}"✅ Secure Patternenv:
COMMIT_MSG: ${{ github.event.head_commit.message }}
OUTPUT_FILE: ${{ steps.example.outputs.file }}
run: |
echo "$COMMIT_MSG"
cat "$OUTPUT_FILE"Key principles:
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
| if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then | ||
| # For PR events, get the commit message from the PR head SHA | ||
| COMMIT_MESSAGE=$(git log -1 --format=%B ${{ github.event.pull_request.head.sha }}) | ||
| COMMIT_MESSAGE=$(git log -1 --format=%B $PR_HEAD_SHA) |
There was a problem hiding this comment.
Quote $PR_HEAD_SHA in the git log call (as done in build-native-only.yml) to avoid word-splitting/globbing and keep the shell style consistent.
| COMMIT_MESSAGE=$(git log -1 --format=%B $PR_HEAD_SHA) | |
| COMMIT_MESSAGE=$(git log -1 --format=%B "$PR_HEAD_SHA") |
| env: | ||
| GIT_CLIFF_CONTENT: ${{ steps.git-cliff.outputs.content }} | ||
| run: | | ||
| # Convert Markdown links [text](url) to Slack mrkdwn <url|text> | ||
| # Convert bold **text** to *text* | ||
| SLACK_RELEASE_NOTES=$(echo '${{ toJSON(steps.git-cliff.outputs.content) }}' | \ | ||
| SLACK_RELEASE_NOTES=$(printf '%s\n' "$GIT_CLIFF_CONTENT" | \ | ||
| sed -E 's/\[([^]]+)\]\(([^)]+)\)/<\2|\1>/g' | \ | ||
| sed -E 's/\*\*([^*]+)\*\*/*\1*/g') |
There was a problem hiding this comment.
steps.slack-notes.outputs.content is later inserted into the Slack payload without surrounding quotes (line 262). Previously this step used toJSON(...), so the output was already a JSON-escaped string literal; after switching to printf of raw content, the output will contain unescaped characters/newlines, which will likely make the payload invalid (and can break the Slack announcement step). Consider JSON-encoding the converted notes before writing them to $GITHUB_OUTPUT, or update the payload to quote/escape the value consistently.
There was a problem hiding this comment.
@olivermeyer Is this a Markdown output or a JSON output?
As far as I know, it's markdown and we can ignore this comment
Codecov Report✅ All modified and coverable lines are covered by tests. |
arne-aignx
left a comment
There was a problem hiding this comment.
LGTM.
I left one question, if this is answered the PR can be merged
| env: | ||
| GIT_CLIFF_CONTENT: ${{ steps.git-cliff.outputs.content }} | ||
| run: | | ||
| # Convert Markdown links [text](url) to Slack mrkdwn <url|text> | ||
| # Convert bold **text** to *text* | ||
| SLACK_RELEASE_NOTES=$(echo '${{ toJSON(steps.git-cliff.outputs.content) }}' | \ | ||
| SLACK_RELEASE_NOTES=$(printf '%s\n' "$GIT_CLIFF_CONTENT" | \ | ||
| sed -E 's/\[([^]]+)\]\(([^)]+)\)/<\2|\1>/g' | \ | ||
| sed -E 's/\*\*([^*]+)\*\*/*\1*/g') |
There was a problem hiding this comment.
@olivermeyer Is this a Markdown output or a JSON output?
As far as I know, it's markdown and we can ignore this comment



No description provided.