Skip to content

ci(release): pass tag message via env to prevent shell injection (fix empty v2.6.0-testnet)#303

Merged
mateeullahmalik merged 1 commit into
masterfrom
fix/build-release-tag-message-injection
Jul 2, 2026
Merged

ci(release): pass tag message via env to prevent shell injection (fix empty v2.6.0-testnet)#303
mateeullahmalik merged 1 commit into
masterfrom
fix/build-release-tag-message-injection

Conversation

@mateeullahmalik

Copy link
Copy Markdown
Collaborator

Summary

The release pipeline on tag pushes has been broken since (at least) v2.6.0-rc1, and again on v2.6.0-testnet. Both releases were published to GitHub with zero downloadable assets, which means sn-manager on the testnet fleet cannot fetch the update tarball — the testnet SNs are stuck on v2.5.0-testnet.

Root cause

In .github/workflows/build&release.yml, the Check deploy intent step did:

- name: Check deploy intent
  id: deploy_check
  run: |
    TAG_MSG="${{ steps.tag_info.outputs.tag_message }}"
    if echo "$TAG_MSG" | grep -qi '\[no-deploy\]'; then
      ...

${{ steps.tag_info.outputs.tag_message }} is expanded by the workflow engine before the shell sees it, so the tag/commit body is spliced verbatim into the bash script. For the v2.6.0-testnet tag, the merge-commit body included lines like Verified via deadcode diff on origin/master ... — bash then tried to execute the token deadcode and aborted with exit 127. Everything after Check deploy intent (including the artifact upload step) was skipped, so the release stayed empty.

Failing run: https://github.com/LumeraProtocol/supernode/actions/runs/28608759515

Log excerpt:

line 22: deadcode: command not found
##[error]Process completed with exit code 127.

Fix

Pass the tag message via the step's env: map instead of ${{ }} interpolation. This is the GHA-recommended pattern for consuming multi-line and/or untrusted values inside run: scripts. Behavior is otherwise unchanged (still a single case-insensitive grep for [no-deploy], still writes draft=… to $GITHUB_OUTPUT).

Why not just re-run the failing job?

Because the workflow runs on every tag push and the failure is deterministic for any tag whose message happens to contain a token that shells look up as a command. Same bug would take down every future testnet/mainnet release cut from a merge commit with a rich message.

Blast radius

  • Affects only .github/workflows/build&release.yml, only the release job, only the Check deploy intent step.
  • No change to the actual build, artifact naming, tarball layout, softprops/action-gh-release step, or release-body composition.
  • Prepare Release Body (line 208) also references ${{ steps.tag_info.outputs.tag_message }} but does so inside a quoted heredoc (<<'EOT') which suppresses expansion, so it is not vulnerable to the same failure mode and is intentionally left untouched to keep the diff minimal.

Rollback

Revert the commit; there's no state or migration involved.

Post-merge action

Once this lands on master, I will:

  1. Cherry-pick nothing — the existing v2.6.0-testnet tag already contains all the intended code; only the release assets are missing.
  2. Attach the correctly-built supernode-linux-amd64 + supernode-linux-amd64.tar.gz to the existing v2.6.0-testnet release (built from tag SHA 095b2f2, matching the tag exactly). This is what sn-manager needs.
  3. Verify testnet fleet rolls forward via ./testnet_version_check.sh.

Verification of this PR

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/build&release.yml'))" → OK.
  • Diff is 14 lines, no behavior drift.
  • The next tag push (after merge) will exercise the fixed step end-to-end.

The 'Check deploy intent' step interpolated the multi-line tag message
directly into a bash script via ${{ steps.tag_info.outputs.tag_message }}.
When the tag body contains lines that look like shell commands (e.g. the
literal token 'deadcode' from a merge-commit trailer), those lines are
executed as bash. That is exactly what happened on v2.6.0-testnet and
v2.6.0-rc1: the release job aborted with exit code 127 ("deadcode:
command not found") before any artifacts were uploaded, leaving the
GitHub Release with zero downloadable assets and sn-manager unable to
roll the testnet fleet forward.

Fix: expose the tag message through the step's `env:` block and read it
as a shell variable inside the script. This is the GHA-recommended way
to consume untrusted multi-line values.

Verified locally with yaml.safe_load; runtime behavior is a strict
subset of the previous logic (single grep, same output key).

Root cause captured for future readers in the inline comment.

Signed-off-by: Matee ullah Malik <mateeullahmalik@hotmail.com>
@mateeullahmalik mateeullahmalik requested a review from akobrin1 July 2, 2026 21:20
@a-ok123 a-ok123 self-requested a review July 2, 2026 22:52
@mateeullahmalik mateeullahmalik merged commit 8eae29e into master Jul 2, 2026
8 checks passed
mateeullahmalik added a commit that referenced this pull request Jul 5, 2026
Follow-up to #303. The env-based approach in that PR triggered a GHA
startup failure (workflow accepted, 0 jobs scheduled, run completed
in the same second with conclusion=failure) because a step-level
`env:` value cannot safely carry the multi-line `tag_message`
output — GHA rejects the workflow at dispatch time.

Verified failure signature on 8eae29e:
  run 28627254564 / 28627293599 — status=completed, conclusion=failure,
  jobs=[], run_started_at == updated_at (single-second dispatch reject).

Fix: perform the `[no-deploy]` grep INSIDE the `Get tag information`
step, where `$TAG_MESSAGE` already exists as a plain bash variable.
Export the single-line `draft` boolean from the same step. The
multi-line `tag_message` output is preserved (still consumed by the
release-body heredoc, which is safe because it uses `<<'EOT'`).

Update the two downstream references from
  steps.deploy_check.outputs.draft
to
  steps.tag_info.outputs.draft.

Root cause and reasoning preserved as an inline comment.

Signed-off-by: Matee ullah Malik <mateeullahmalik@hotmail.com>
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.

2 participants