Skip to content

ci: declare least-privilege permissions and cancel superseded runs - #7

Merged
aeddi merged 6 commits into
mainfrom
ci/least-privilege-and-concurrency
Nov 3, 2026
Merged

aeddi merged 6 commits into
mainfrom
ci/least-privilege-and-concurrency

Conversation

@aeddi

@aeddi aeddi commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Two workflow changes.

Least-privilege permissions. ci.yml and security_audit.yml declare their token
permissions instead of inheriting the repository default. Every CI job gets
contents: read. The audit job also gets checks: write, granted on the job rather than the
workflow, so a job added later starts with no write of its own.

This repository's default token was already read-only, so the only privilege dropped today
is Packages: read. The value is durability: a change to the repository default can no
longer silently widen what these workflows may do.

Cancel superseded pull request runs. A new push to a PR branch cancels the run already in
flight for it. Every other run gets a concurrency group of its own, keyed on the run id: a
shared group would make GitHub cancel a run that is still queued when the next push
arrives, which could leave a commit on main with no CI result at all.

What changes for the security audit

With checks: write, the audit action publishes its findings as a check run on pull
requests and pushes that touch Cargo.lock, instead of failing with a 403. The formatted
advisory list moves from the job log into that check run. The job itself stays red while
live advisories exist — the action still fails on critical findings — so such a PR shows
the existing red job plus a red "Security audit" check.

The nightly run takes the other path and files an issue, which needs issues: write. That is
not granted, and this repository has issues disabled, so a scheduled run reports through its
log alone.

Verification

  • actionlint clean on all three workflows.
  • zizmor: 8 excessive-permissions findings before, 0 after, at both the default and the
    pedantic persona, with nothing else introduced.
  • Mutants, each confirmed to differ from the original: permission: and
    cancel-in-progres: are rejected by actionlint; contents: write passes actionlint and is
    caught by zizmor. The tools are complementary, not redundant.
  • No tool here can see the property the concurrency fix rests on — three plausible
    regressions of that line pass both silently. It rests on GitHub's documented behaviour, a
    queued run in the same group is cancelled whatever cancel-in-progress says, and on four
    real pushes to main 24 seconds apart: all four completed under the base, and two would
    have been cancelled while queued under a shared group.

@aeddi
aeddi marked this pull request as ready for review November 3, 2026 21:02
@aeddi
aeddi merged commit 26e3783 into main Nov 3, 2026
8 of 9 checks passed
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