Skip to content

Pin all GitHub Actions to commits and add the zizmor linter - #55

Merged
dduugg merged 1 commit into
mainfrom
harden-workflows-with-zizmor
Aug 19, 2026
Merged

Pin all GitHub Actions to commits and add the zizmor linter#55
dduugg merged 1 commit into
mainfrom
harden-workflows-with-zizmor

Conversation

@dduugg

@dduugg dduugg commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #51. Review there raised two questions: is taiki-e/install-action trustworthy, and should we pin actions by SHA and run zizmor like other rubyatscale repos. This does the pinning, adds the linter, and clears everything it reports.

On taiki-e/install-action

Worth stating plainly: it had no precedent in either orgtaiki-e appears nowhere in Gusto or rubyatscale. So the ask for due diligence was fair. What I found:

  • Maintained by Taiki Endo, a significant Rust ecosystem maintainer (cargo-hack, cargo-llvm-cov, pin-project); Apache-2.0, 532 stars, released v2.86.3 the day before this PR.
  • Widely used in the Rust ecosystem — casey/just, orhun/git-cliff, rolldown/rolldown.
  • Verifies SHA256 checksums by default, verifies artifact attestations/signatures where upstream publishes them, and applies a dependency cooldown to blunt supply-chain attacks. actions-rs/audit-check, which it replaced, did none of these.

I also found one gap #51 left open: the default fallback: cargo-binstall. On a failed download it falls back to cargo-binstall (which gets a token passed to it) and can reach cargo install — the exact compile path whose failure #51 existed to fix. This sets fallback: none so that cannot quietly come back.

zizmor

.github/workflows/zizmor.yml is copied verbatim from shared-config, byte-identical to the one visualize_packs uses. It's a copied file rather than a uses: of a reusable workflow because shared-config's zizmor.yml has no workflow_call trigger — unlike its codeql.yml, which we do call.

This cannot turn the build red on its own. The action defaults to advanced-security: true, which runs zizmor in SARIF mode, and zizmor exits 0 in that mode even with findings (plain mode exits 14). Verified against all workflows with findings present: exit 0. Results land in the Security tab.

Pinning

Every action in the repo is now pinned to a commit, version in a trailing comment.

file what changed
audit.yml install-action → v2.86.3; checkout v5 → v7.0.1
ci.yml checkout@v2 → v7.0.1
release.yml all 17 uses, via config — see below

checkout was on v5, two majors behind the v7.0.1 shared-config standardizes on. I confirmed the pinned SHAs expose the inputs used (tool/checksum/fallback; 3d3c42e5 is "prep v7.0.1 release").

release.yml is generated

release.yml is autogenerated by dist (cargo-dist), so editing it directly does not survive the next dist generate. The supported knob is [dist.github-action-commits] in dist-workspace.toml — it landed in dist 0.29.0 and we pin 0.30.3, so no version bump was needed.

Each action stays on the major version dist 0.30.3 already emitted, making this a pure pin with no behavior change. I installed dist 0.30.3 and verified:

  • dist generate --check passed before any change, so dist reproduces the checked-in file exactly.
  • After adding the config and regenerating, every changed line is an actions pin — nothing else moved.
  • dist generate --check passes now, so the file and config are in sync.

Hardening beyond pinning

  • persist-credentials: false on every checkout, so the job token isn't left in .git/config (zizmor's artipacked).
  • permissions: {} at the top of audit.yml, leaving the job to opt into contents: read.

What's ignored, and why

.github/zizmor.yml ignores three audits, scoped to release.yml only: excessive-permissions, template-injection, unpinned-images. All ten findings are structural to dist's template with no config knob, and dist would overwrite inline # zizmor: ignore comments. They're file-scoped rather than line-scoped because regeneration shifts line numbers. Per-audit reasoning is in the file.

Briefly: contents: write is at workflow level because dist's announce job creates the Release; the template-injection hits are dist interpolating github.ref_name / needs.plan.outputs.tag* / its own matrix.* plan output, where the tag-derived values require push access and expand to '' on pull_request; and unpinned-images is matrix.container.image, which is always null since this repo configures no custom container runners.

One caveat found while validating: zizmor does not error on an unknown rule name in its config — a typo silently does nothing. So I verified the ignores take effect by diffing against --no-config rather than trusting the file.

Result

findings
before 50
after pins + hardening (--no-config) 26
with documented ignores 0

cargo test passes (0 failures), and all five touched YAML files parse.

Follow-up to #51. Denis raised two things on that PR: whether
`taiki-e/install-action` is trustworthy, and whether we should pin actions
by SHA and run zizmor like other rubyatscale repos do. This does the
pinning, adds the linter, and clears every finding it reports.

## zizmor

`.github/workflows/zizmor.yml` is copied verbatim from shared-config, which
is also what visualize_packs uses. It is a copied file rather than a
`uses:` of a reusable workflow because shared-config's zizmor.yml has no
`workflow_call` trigger, unlike its codeql.yml.

It defaults to `advanced-security: true`, which runs zizmor in SARIF mode.
zizmor exits 0 in that mode even when it has findings, so this reports into
the Security tab and cannot turn the build red on its own.

## Pinning

Every action in the repo is now pinned to a commit, with the version in a
trailing comment:

- `audit.yml`: `install-action` to v2.86.3 and `checkout` to v7.0.1.
  `checkout` was on v5, two majors behind the v7.0.1 that shared-config
  standardizes on.
- `ci.yml`: three `checkout@v2` uses, also now v7.0.1.
- `release.yml`: all 17 uses, via `[dist.github-action-commits]` in
  dist-workspace.toml. That file is generated by dist, so editing it
  directly does not survive the next `dist generate`; the config is the
  supported knob for this and landed in dist 0.29.0, and we pin 0.30.3.
  Each action stays on the major version dist 0.30.3 already emitted, so
  this is a pure pin with no behavior change. `dist generate --check`
  passes, and the regenerated diff touches nothing but the pinned refs.

## Hardening beyond pinning

- `persist-credentials: false` on every checkout, so the job's token is not
  left behind in `.git/config` (zizmor's artipacked audit).
- `permissions: {}` at the top of audit.yml, leaving the job to opt into
  `contents: read`.
- `fallback: none` on install-action. Its default falls back to
  cargo-binstall, which it passes a token to, and can reach `cargo install`
  -- the exact path whose compile failure #51 fixed. This keeps it from
  quietly coming back if a download fails.

On install-action itself: it verifies SHA256 checksums by default and
attestations where upstream publishes them, and applies a dependency
cooldown, none of which the `actions-rs/audit-check` it replaced did.

## What is ignored, and why

`.github/zizmor.yml` ignores three audits, scoped to release.yml only:
excessive-permissions, template-injection, unpinned-images. All ten are
structural to dist's template with no config knob, and dist would overwrite
inline `# zizmor: ignore` comments. They are file-scoped rather than
line-scoped because regeneration shifts line numbers. Reasoning for each is
in the file.

Net: 50 findings before, 26 after the pins and hardening, 0 with the
documented ignores. `cargo test` passes.
@dduugg
dduugg requested a review from a team as a code owner August 19, 2026 22:16
@github-project-automation github-project-automation Bot moved this to Triage in Modularity Aug 19, 2026
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@dduugg
dduugg enabled auto-merge (squash) August 19, 2026 22:21
@dduugg
dduugg merged commit 980a9f6 into main Aug 19, 2026
14 checks passed
@dduugg
dduugg deleted the harden-workflows-with-zizmor branch August 19, 2026 22:21
@github-project-automation github-project-automation Bot moved this from Triage to Done in Modularity Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants