Fix the security audit workflow and move it off Node 20 - #51
Merged
Conversation
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.
perryqh
approved these changes
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
auditcheck 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@v1builds cargo-audit withcargo install, which no longer compiles on the runner's toolchain:actions-rshas been unmaintained for years. Its successorrustsec/audit-check@v2is not a fix here: its bundle still callsfindOrInstall("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-actionand callscargo auditdirectly. 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:
Both sources are now gone:
actions/checkout@v2actions/checkout@v5actions-rs/audit-check@v1taiki-e/install-action@v2No 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: writeandissues: write, which the old action needed to post annotations and file issues. Runningcargo auditdirectly needs onlycontents: read. Tradeoff worth naming: a vulnerability now surfaces as a failed step rather than as an annotation or auto-filed issue.Verification
cargo auditexits 0 against the currentCargo.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 theCargo.toml/Cargo.lockpath triggers are unchanged.Sent with Claude Code