Skip to content

Harden release workflow against shell-expanded inputs - #1022

Merged
olegbrok merged 2 commits into
mainfrom
agent/542-release-notes-shell-safety
Aug 7, 2026
Merged

Harden release workflow against shell-expanded inputs#1022
olegbrok merged 2 commits into
mainfrom
agent/542-release-notes-shell-safety

Conversation

@olegbrok

@olegbrok olegbrok commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • pass release inputs, the dispatch ref, and validated version outputs through step env: blocks instead of materializing them inside run: scripts
  • write custom release notes literally with printf '%s\n' "$CUSTOM_NOTES"
  • pass /tmp/notes.md directly to the release action via body_path, avoiding delimiter collisions in $GITHUB_OUTPUT
  • close Release workflow executes custom release notes via shell expansion #542

Root cause

GitHub Actions expands ${{ ... }} expressions before Bash parses a run: block. The previous custom_notes="${{ inputs.release_notes }}" line therefore turned release-note contents into executable script text, allowing backticks or $() to execute or break the step. A quoted heredoc would have the same template-time problem and would also collide with a bare delimiter line. Passing the value through env: keeps it out of script text entirely.

The generated body was also exported through a fixed NOTES_EOF $GITHUB_OUTPUT delimiter. An otherwise valid custom-note line equal to NOTES_EOF could close that value early and leave malformed runner commands. The release action now reads the generated file directly.

Interpolation audit

The full workflow was checked for expressions embedded in run: blocks:

Step Before After
Check version format + branch inputs.version and github.ref expanded directly in Bash RELEASE_VERSION and WORKFLOW_REF are populated via env: and referenced as quoted shell variables
Verify tag doesn't exist the validated version output was expanded twice in Bash RELEASE_VERSION is populated via env:
Generate release notes the validated version and inputs.release_notes were expanded in Bash both arrive via env:; custom notes are emitted with printf '%s\n' "$CUSTOM_NOTES"
Create annotated tag the validated version output was expanded in Bash RELEASE_VERSION is populated via env:
Create GitHub Release the body crossed a fixed NOTES_EOF step-output delimiter body_path: /tmp/notes.md passes the file directly to the release action

No ${{ inputs.* }} or ${{ github.event.* }} interpolation remains in a run: block; the audit found no workflow expression of any kind remaining in executable shell text.

Validation

  • git diff --check
  • Ruby/Psych YAML syntax parse of .github/workflows/release.yml
  • uv run ruff check tests/test_release_workflow.py
  • uv run pytest -q tests/test_release_workflow.py (2 passed)
  • committed regressions confirming zero ${{ ... }} expressions inside run: blocks and literal handling of backticks, $(), single and double quotes, multiline Markdown, and a bare NOTES_EOF line

actionlint is not installed in this environment. The Release workflow itself cannot be CI-exercised pre-merge without performing the live tag-and-publish path, so this PR does not claim a live release run.

🤖 Opened by Kuzya

@olegbrok
olegbrok merged commit 02e0ca1 into main Aug 7, 2026
8 checks passed
@olegbrok
olegbrok deleted the agent/542-release-notes-shell-safety branch August 7, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release workflow executes custom release notes via shell expansion

1 participant