Skip to content

fix: harden tap bump against layout changes and bad downloads - #233

Merged
inureyes merged 1 commit into
mainfrom
fix/homebrew-bump-stanza-substitution
Jul 27, 2026
Merged

fix: harden tap bump against layout changes and bad downloads#233
inureyes merged 1 commit into
mainfrom
fix/homebrew-bump-stanza-substitution

Conversation

@inureyes

Copy link
Copy Markdown
Member

Problem

update_homebrew_formula.yml rewrote each checksum by line position:

gsed -i "/ARTIFACT\"/!b;n;c\      sha256 \"${sha}\""

It matched the url line, advanced one line (n), and replaced whatever it landed on (c\). That is correct only while sha256 sits directly beneath url inside an on_macos / on_linux block, which is how the three bssh formulae happen to be written today. Nine substitutions depend on it.

lablup/mlxcel ran the same construct, copied from the same source. When its formula moved the url to the top level with a version stanza in between, the next bump overwrote version with the new checksum and left the previous release's sha256 below it:

  url ".../v0.4.3/mlxcel-macos-aarch64.zip"
      sha256 "dea8a5c2..."   # new hash, wrong indent, replaced `version`
  sha256 "83a0b702..."       # stale v0.4.2 hash, and this is the one that wins

No version stanza, two sha256 lines, brew install failing on checksum. Nothing validated the result and it sat broken in the tap for three days (lablup/homebrew-tap 9969ec4).

bssh is not broken today, but it is one formula edit away from the same outcome, with three times the exposure.

Fix

Stanzas are located by name. set_artifact finds the url stanza whose value contains a given artifact, rewrites it, then rewrites the first sha256 stanza below it, preserving indentation. Anything in between is skipped rather than clobbered. Guards abort when a formula does not hold exactly one matching url, or when no sha256 follows it.

Two other faults surfaced while fixing this:

  • curl -Ls with no -f. A missing or renamed asset produced a zero-byte file whose checksum went to the tap unnoticed. Now -f --retry 3, plus an empty-file check and an archive integrity check (unzip -t / tar -tzf), so a truncated download cannot become a valid-looking sha256 either.
  • Unconditional git commit. Re-running for a version already in the tap failed on an empty index. It now exits cleanly.

A validation step runs ruby -c and brew style on each formula, checks the version stanza, and requires the exact set of checksums downloaded this run, which catches a substitution that silently no-ops.

Verification

test result
bump all three formulae to a synthetic version every url and checksum in the right place
brew style on the results no offenses
replay the mlxcel-breaking layout (url, version, sha256) url and sha256 updated, version untouched
replay the corrupted-file state guards trip (version 0, sha256 2)
missing artifact url guard trips
workflow YAML parse OK, 8 steps

ruby -c alone is not sufficient: the corrupted mlxcel formula was valid Ruby and passes a syntax check. That is why brew style is in the validation step.

Related

lablup/all-smi has the identical positional construct and is not covered here.

This workflow rewrote each checksum by line position:

    gsed -i "/ARTIFACT\"/!b;n;c\      sha256 \"${sha}\""

It matched the url line, advanced one line, and replaced whatever it
landed on. Correct only while sha256 sits directly beneath url inside an
on_macos or on_linux block, which is how the three bssh formulae happen
to be written today. Nine substitutions depend on that.

lablup/mlxcel ran the same construct, copied from the same source. When
its formula moved the url to the top level with a `version` stanza in
between, the next bump overwrote `version` with the new checksum and left
the previous release's sha256 below it. The formula ended up with no
version and two sha256 lines, the second of which wins, so `brew install
mlxcel` failed on checksum. Nothing validated the result and it sat broken
in the tap for three days (lablup/homebrew-tap 9969ec4).

bssh is not broken today, but it is one formula edit away from the same
outcome, with three times the exposure.

Stanzas are now located by name. `set_artifact` finds the url stanza whose
value contains a given artifact, rewrites it, then rewrites the first
sha256 stanza below it, preserving indentation. Anything in between is
skipped rather than clobbered. Guards abort the run when a formula does not
hold exactly one matching url, or when no sha256 follows it, instead of
editing the wrong line.

Two other faults surfaced while fixing this:

Downloads used `curl -Ls` with no `-f`, so a missing or renamed asset
produced a zero-byte file and its checksum went to the tap unnoticed. They
now use `-f --retry 3`, reject empty files, and verify each archive is
readable with `unzip -t` or `tar -tzf`, so a truncated download cannot
become a valid-looking sha256 either.

`git commit` ran unconditionally, so re-running for a version already in
the tap failed on an empty index. It now exits cleanly.

A validation step runs `ruby -c` and `brew style` on each formula, checks
the version stanza, and requires the exact set of checksums downloaded this
run, which catches a substitution that silently no-ops. `ruby -c` alone is
not enough: the corrupted mlxcel formula was valid Ruby.

Verified against the current tap formulae. Bumping all three to a synthetic
version puts every url and checksum in the right place and `brew style`
reports no offenses. Replaying the layout that broke mlxcel (url, then
`version`, then sha256) now updates url and sha256 and leaves `version`
untouched. The corrupted-file state trips the guards (version 0, sha256 2),
and a missing artifact trips the url guard. The workflow YAML parses.
@inureyes
inureyes merged commit 57d4d0f into main Jul 27, 2026
3 checks passed
@inureyes
inureyes deleted the fix/homebrew-bump-stanza-substitution branch July 27, 2026 15:57
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.

1 participant