Skip to content

Harden GitHub Actions workflows - #83

Merged
flavorjones merged 8 commits into
masterfrom
harden-gh-actions
Jul 13, 2026
Merged

Harden GitHub Actions workflows#83
flavorjones merged 8 commits into
masterfrom
harden-gh-actions

Conversation

@flavorjones

@flavorjones flavorjones commented Jul 13, 2026

Copy link
Copy Markdown
Member

This hardens the GitHub Actions workflows against the vulnerability classes that zizmor audits. It handles the cache-poisoning concern differently from #76, see thread in that PR.

Changes

  • A lint-actions job in ci.yml runs actionlint and zizmor on every push and pull request, so workflow regressions surface in CI.
  • Both github-actions entries in dependabot.yml gain a seven-day cooldown, which matches the minimum age used when pinning.
  • Every action is pinned to a commit SHA with pinact. The ruby/actions ruby_versions.yml reusable workflow stays on @master so that the Ruby version matrix remains current, and an inline comment records why that reference is not pinned.
  • GITHUB_TOKEN permissions are scoped per job. Both workflows deny all permissions at the top level and grant each job only the permissions it needs.
  • Every checkout sets persist-credentials: false. The release job remains correct because rubygems/release-gem configures its own git credential cache for the tag push.

Cache poisoning in the release workflow

I kept bundler-cache: true in push_gem.yml and suppressed the zizmor cache-poisoning finding rather than disabling the cache. GitHub Actions caches are isolated by git ref. A cache created by a pull request lives in the refs/pull/<n>/merge scope and can only be restored by re-runs of that pull request, so nothing a fork or pull request does can reach the cache that this tag-push release build restores. The inline suppression comment records this reasoning.

Verification

zizmor reports no findings under both the default and pedantic personas, and actionlint is clean.

- suppress cache-poisoning on release setup-ruby (ref-isolated cache)
- suppress unpinned-uses on ruby/actions reusable workflow (@master needed
  for a current Ruby version matrix)
Deny-all at the workflow level and grant each job only what it needs:
lint-actions and test get contents:read; ruby-versions (reusable workflow)
and ci-pass need none.
Neither the test job nor the release job needs the persisted checkout token:
rubygems/release-gem configures its own git credential cache for the tag
push (its README documents persist-credentials: false).
The single push job already declares its own contents:write and
id-token:write, so the workflow-level block can be permissions: {}.
CI runs zizmor v1.23.1, which flags the direct ${{ needs.test.result }}
expansion in the ci-pass run block as template-injection (newer zizmor no
longer does). Move the expression into an env var so the shell reads a plain
variable instead.
@flavorjones
flavorjones merged commit b7901d7 into master Jul 13, 2026
9 checks passed
@flavorjones
flavorjones deleted the harden-gh-actions branch July 13, 2026 13:00
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