Skip to content

ci: add zizmor and actionlint, harden workflows - #83

Open
kolyshkin wants to merge 8 commits into
opencontainers:mainfrom
kolyshkin:ci-zizmor
Open

kolyshkin wants to merge 8 commits into
opencontainers:mainfrom
kolyshkin:ci-zizmor

Conversation

@kolyshkin

@kolyshkin kolyshkin commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Add zizmor (security-focused) and actionlint (correctness-focused)
checks for GitHub Actions workflows, and fix what zizmor finds.
This mirrors what was done in runc.

  • pin all actions to commit hashes (all at their latest releases);
  • set persist-credentials: false for actions/checkout;
  • ignore zizmor's cache-poisoning audit via .github/zizmor.yml
    (with an explanation why it does not apply here);
  • add 7 days cooldown to dependabot config;
  • add zizmor job (to validate.yml) and actionlint workflow;
  • drop unneeded pull-requests: read permission from the lint job;
  • add concurrency limits (cancel superseded PR runs);
  • rename all-done job in validate.yml to all-done-validate,
    so it does not collide with the one in test.yml.

NOTE to admins: branch protection for main currently requires all-done,
which matches jobs from both test.yml and validate.yml. Once this is merged,
please add all-done-validate and all-done-actionlint to the required checks.

@kolyshkin
kolyshkin requested a review from a team as a code owner October 2, 2026 20:39
@kolyshkin kolyshkin added this to the 0.1.1 milestone Oct 2, 2026
kolyshkin and others added 4 commits October 7, 2026 17:31
A version tag (even a "full" one like v7.0.1) is mutable: whoever
controls the action's repository can move it to point to any other
commit. Pinning to a full commit hash is the only way to get the exact
same action code on every run.

Found by zizmor's unpinned-uses audit. Dependabot is already enabled for
the github-actions ecosystem and knows how to update hash pins together
with their version comments, so this should not add maintenance burden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Set persist-credentials: false for actions/checkout, as applied by
"zizmor --fix". By default checkout leaves the credentials it used in
.git/config, where any later step (or anything that archives the
workspace) can pick them up. Nothing here pushes back to the repository,
so they are not needed.

The remaining findings are all cache-poisoning, and are ignored via
.github/zizmor.yml, which explains why they do not apply to us.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Wait for 7 days before proposing an update, so that a compromised or
broken release has a chance to be noticed and pulled before it lands
here.

As recommended by zizmor's dependabot-cooldown audit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
zizmor is a static analysis tool for GitHub Actions workflows, focused
on security issues.

Use the official action, which runs zizmor from a digest-pinned
container image. A few non-default settings:

 - version: pin it, so a new zizmor release adding new audits does not
   suddenly fail CI on an unrelated pull request. Bumping it is then a
   deliberate change.

 - advanced-security: false + annotations: true. The default uploads
   SARIF to the repository's security tab, which needs the job to have
   security-events: write. Inline annotations are enough for us and
   keep the workflow at contents: read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
kolyshkin and others added 4 commits October 7, 2026 17:45
Debugging GHA yaml is not fun -- usually when there is an issue with a
workflow file, it just doesn't run.

Add a separate actionlint workflow to catch workflow issues. It is
deliberately a separate workflow, so that it still runs when another
workflow file is broken.

Use the actively maintained kjanat/actionlint fork of rhysd/actionlint,
which knows about the newer GitHub-hosted runner images (such as
ubuntu-26.04 used here), and ships its own action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The lint job does not need pull-requests: read: golangci-lint-action only
uses it for its only-new-issues option, which we do not set.

The lint-extra step does its own "new code only" filtering with
--new-from-rev=HEAD~1, which is plain git against the fetch-depth: 2
checkout, and the PR annotations come from checks: write.

Found by zizmor's undocumented-permissions audit (--persona=pedantic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Cancel CI runs that are already superseded, rather than letting them run
to completion and waste runner time.

The concurrency group is keyed by PR number, so only runs of the same
pull request cancel each other. For anything else -- in particular
pushes to main, to release-*, to a v* tag, and scheduled runs -- there
is no PR number, so the group falls back to the unique run_id and no run
is ever cancelled. Keying by github.head_ref instead would be wrong, as
two pull requests from different forks can use the same branch name.

Found by zizmor's concurrency-limits audit (--persona=pedantic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Rename all-done job in validate.yml to all-done-validate, so it does not
collide with the all-done job in test.yml in CI summaries and UI output
when both workflows are triggered together.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants