Skip to content

ci: verify the shellcheck download instead of trusting the transport - #108

Merged
Jaro-c merged 1 commit into
mainfrom
ci/verify-the-shellcheck-download
Jul 31, 2026
Merged

ci: verify the shellcheck download instead of trusting the transport#108
Jaro-c merged 1 commit into
mainfrom
ci/verify-the-shellcheck-download

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

The only two network downloads in this repository — the shellcheck tarball in shell-ci.yml and in workflow-lint.yml — fetched over HTTPS and extracted unchecked, and shell-ci.yml declined a checksum in a comment: "the tarball comes straight from koalaman/shellcheck's own GitHub release over HTTPS, so there is no separate checksum to embed."

Every other tool here arrives through a registry that verifies what it hands over (Go's sumdb, crates.io, PyPI). These two were the only places in a repository built on "the pin is the security boundary" that trusted the transport instead.

What this buys, stated honestly: koalaman publishes no checksum file and no signatures alongside the release — I checked the assets for v0.11.0 and there are seven tarballs and nothing else. So the hash pins the artifact I measured, not one upstream attests to. It detects a replaced or re-uploaded asset; it does not detect an upstream that was malicious from the start.

Changes

  • The tarball lands in a file and is verified with sha256sum -c before extraction. curl | tar could never have verified anything — the bytes are extracted as they arrive.
  • The hash is an input beside shellcheck-version, so the two travel together: a consumer bumping only the version fails the install rather than silently downloading something unverified.
  • actionlint.yml gets the same install and passes the binary through -shellcheck. Its embedded pass was falling back to the runner image's floating shellcheck, which is exactly what workflow-lint installs a pinned one to avoid — this repository was linting itself to a weaker standard than the one it ships to consumers.
  • .gitignore: Python bytecode, since running the docs generator leaves scripts/__pycache__ behind.

Test plan

  • Ran the install script the way a runner does — a standalone bash script, not inside an && list. That distinction mattered: my first attempt wrapped it in &&, where errexit is ignored, and it wrongly showed a wrong hash passing.
    • correct hash: extracts, reports version: 0.11.0, exit 0
    • wrong hash: sha256sum: WARNING: 1 computed checksum did NOT match, exit 1, shellcheck-bin/ empty, nothing extracted
  • actionlint 1.7.12 clean over the tree locally.
  • Pages regenerated: the two new inputs are the only content change.

Note on the release

This touches two reusables, so it needs a tag for consumers to get it — and per the org rule, no tag before a consumer PR proves it green. That consumer PR is outside this repository, so I have not opened one.

The two downloads in this repository fetched shellcheck over HTTPS and
extracted it unchecked, and shell-ci.yml declined a checksum in a
comment: "the tarball comes straight from koalaman/shellcheck's own
GitHub release over HTTPS, so there is no separate checksum to embed."
Every other tool here arrives through a registry that verifies what it
hands over — Go's sumdb, crates.io, PyPI — so in a repository whose
doctrine is that the pin is the security boundary, these two were the
only places trusting the transport.

The tarball now lands in a file and is checked against a sha256 before
extraction. Piping curl into tar could never have verified anything:
the bytes are extracted as they arrive.

The hash is an input beside shellcheck-version, so the two travel
together and a consumer bumping only the version fails the install
rather than silently downloading unverified. Note what this does and
does not buy: koalaman publishes no checksum file and no signatures
with the release, so this pins the artifact I measured rather than one
upstream attests to. It detects a replaced or re-uploaded asset; it
does not detect an upstream that was malicious from the start.

actionlint.yml gets the same install and passes the binary through
-shellcheck. Its embedded pass was falling back to the runner image's
floating shellcheck, which is precisely what workflow-lint installs a
pinned one to avoid: this repository was linting itself to a weaker
standard than the one it ships.

Also gitignore Python bytecode, since running the docs generator leaves
scripts/__pycache__ behind.

Test plan: ran the install script as a runner would (standalone bash,
not inside an && list, where errexit is ignored and my first attempt
wrongly showed a pass) — the correct hash extracts and reports 0.11.0,
a wrong hash exits 1 with the directory left empty, nothing extracted.
actionlint 1.7.12 clean over the tree. Pages regenerated; the two new
inputs are the only content change.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c
Jaro-c merged commit e61aaf0 into main Jul 31, 2026
6 checks passed
@Jaro-c
Jaro-c deleted the ci/verify-the-shellcheck-download branch July 31, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant