Skip to content

Fix the security audit workflow and move it off Node 20 - #51

Merged
dduugg merged 1 commit into
mainfrom
fix-audit-workflow
Aug 19, 2026
Merged

Fix the security audit workflow and move it off Node 20#51
dduugg merged 1 commit into
mainfrom
fix-audit-workflow

Conversation

@dduugg

@dduugg dduugg commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

The audit check has been red on every PR that reaches it (for example this run on #50). It never gets as far as auditing anything — it fails while building its own tooling.

What was actually failing

actions-rs/audit-check@v1 builds cargo-audit with cargo install, which no longer compiles on the runner's toolchain:

error: failed to compile `cargo-audit v0.22.2`
Caused by:
  rustc 1.92.0 is not supported by the following package:
    kstring@2.0.4 requires rustc 1.96.0

actions-rs has been unmaintained for years. Its successor rustsec/audit-check@v2 is not a fix here: its bundle still calls findOrInstall("cargo-audit"), so it hits the identical wall, and it also targets Node 20.

So this installs a prebuilt cargo-audit from the upstream GitHub releases via taiki-e/install-action and calls cargo audit directly. No compile step, so the job stops depending on the runner's rustc being new enough to build cargo-audit's dependency tree.

Node 20 → 24

The runner currently force-upgrades this workflow and warns:

Node.js 20 is deprecated. The following actions target Node.js 20 but are being
forced to run on Node.js 24: actions-rs/audit-check@v1, actions/checkout@v2

Both sources are now gone:

before after Node
checkout actions/checkout@v2 actions/checkout@v5 node24 natively
audit tool actions-rs/audit-check@v1 taiki-e/install-action@v2 composite — no Node runtime

No action in this workflow targets Node 20 any more.

To be clear about cause and effect: Node was not why the job failed — the runner was already forcing Node 24 successfully. The compile error was the failure. This PR fixes both, but they're independent.

Permissions

Dropped checks: write and issues: write, which the old action needed to post annotations and file issues. Running cargo audit directly needs only contents: read. Tradeoff worth naming: a vulnerability now surfaces as a failed step rather than as an annotation or auto-filed issue.

Verification

cargo audit exits 0 against the current Cargo.lock (230 dependencies, no advisories), so this should come back green rather than swapping a tooling failure for a findings failure. The scheduled Monday cron and the Cargo.toml/Cargo.lock path triggers are unchanged.

Sent with Claude Code

The `audit` job has been failing at the tooling stage, never reaching an
actual audit. `actions-rs/audit-check@v1` builds cargo-audit with
`cargo install`, and that no longer compiles on the runner's toolchain:

    error: failed to compile `cargo-audit v0.22.2`
    Caused by:
      rustc 1.92.0 is not supported by the following package:
        kstring@2.0.4 requires rustc 1.96.0

`actions-rs` has been unmaintained for years. Its successor,
`rustsec/audit-check`, still calls `findOrInstall("cargo-audit")` and so
hits the same wall, and it targets Node 20 as well.

Install a prebuilt cargo-audit from the upstream GitHub releases via
`taiki-e/install-action` instead and invoke `cargo audit` directly. That
skips the compile entirely, so the job no longer depends on the runner's
rustc being new enough to build cargo-audit's dependency tree.

Also moves the workflow off Node 20, which the runner now force-upgrades
to Node 24 with a deprecation warning:

  - `actions/checkout@v2` -> `@v5`, which targets Node 24 natively
  - `taiki-e/install-action` is a composite action, so it has no Node
    runtime at all

No action in this workflow targets Node 20 any more.

Dropped the `checks: write` and `issues: write` permissions, which existed
for the old action's annotation and issue-filing behaviour. Running
`cargo audit` directly only needs `contents: read`; a failure now surfaces
as a failed step rather than an annotation.

Verified `cargo audit` exits 0 against the current Cargo.lock (230
dependencies, no advisories), so this should report green rather than
trade a tooling failure for a findings failure.
@dduugg
dduugg requested a review from a team as a code owner August 19, 2026 18:27
@github-project-automation github-project-automation Bot moved this to Triage in Modularity Aug 19, 2026
@dduugg
dduugg enabled auto-merge (squash) August 19, 2026 18:30
@dduugg
dduugg merged commit 18f705d into main Aug 19, 2026
12 checks passed
@dduugg
dduugg deleted the fix-audit-workflow branch August 19, 2026 22:09
@github-project-automation github-project-automation Bot moved this from Triage to Done in Modularity Aug 19, 2026
dduugg added a commit that referenced this pull request Aug 19, 2026
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.
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.

2 participants