Skip to content

ci: pin actions to commit SHAs, scope the publish token, add Dependabot - #27

Merged
stormer78 merged 2 commits into
mainfrom
sec-4045/workflow-pinning
Sep 12, 2026
Merged

stormer78 merged 2 commits into
mainfrom
sec-4045/workflow-pinning

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Summary

  • Pins every uses: in .github/workflows/ci.yml and .github/workflows/publish.yml to a full 40-character commit SHA, with the corresponding release in a trailing comment.
  • Replaces dtolnay/rust-toolchain@stable / @1.95.0 (branch refs) with the runner's own rustup. Toolchains and components are unchanged: stable (with rustfmt / clippy in those jobs) and 1.95.0 for the MSRV job.
  • Adds top-level permissions: contents: read to ci.yml.
  • publish.yml: top-level permissions become contents: read; id-token: write is granted to the publish job only; the job runs in environment: crates-io.
  • Sets persist-credentials: false on every checkout (no job pushes).
  • Adds .github/dependabot.yml: github-actions weekly (grouped, cooldown: default-days: 7, ci(deps) prefix) and cargo weekly (cooldown: default-days: 7, chore(deps) prefix). There was no existing Dependabot config.

Pins

Action Was Now Resolved via gh api repos/<o>/<r>/git/ref/tags/<tag>
actions/checkout @v7 @3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 v7 is a lightweight tag on the v7.0.1 commit
Swatinem/rust-cache @v2 @6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 v2 is an annotated tag (49a0bdc7…), dereferenced to commit 6323deb1… = v2.9.2
rust-lang/crates-io-auth-action @v1 (branch) @c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 latest release v1.0.5 (lightweight tag); also the current head of branch v1
dtolnay/rust-toolchain @stable, @1.95.0 (branches) removed rustup toolchain install <stable|1.95.0> --profile minimal [--component …] then rustup default

Admin follow-up (settings, not code)

  1. Protect the environment. Repository Settings → Environments → crates-io: add required reviewers (e.g. @OpenVTC/openvtc-maintainers, with "Prevent self-review") and a deployment policy limited to tags matching v*.*.*. If the environment does not exist when the job first runs, GitHub creates it with no protection rules.
  2. Bind it on crates.io. For each crate published from this repository (dtg-credentials): crates.io → the crate → Settings → Trusted Publishing → set Environment to crates-io. crates.io then issues a publishing token only to a run of this workflow in that environment.

Ordering: merge this PR first, then do step 2. Adding environment: crates-io to the job before crates.io is told about it is harmless, because a Trusted Publisher with no environment constraint accepts a run from any environment. Doing step 2 first would make the next tag push fail at the auth step.

Verification

  • All three files parse with python3 yaml.safe_load (ci.yml, publish.yml, dependabot.yml).
  • git grep -nE 'uses: *[^ .][^ ]*@' -- .github | grep -vE '@[0-9a-f]{40}( |$)' prints nothing.
  • actionlint and zizmor were not available locally and were not run.
  • CI on this PR exercises the ci.yml changes. publish.yml runs only on a v*.*.* tag push or a manual dispatch, so its changes are first exercised at the next release.

Every `uses:` in `ci.yml` and `publish.yml` named a tag or a branch:
`actions/checkout@v7`, `Swatinem/rust-cache@v2`, `dtolnay/rust-toolchain@stable`
and `@1.95.0`, and `rust-lang/crates-io-auth-action@v1`. A tag or a branch can
be moved to point at different code. `publish.yml` runs with `id-token: write`
and mints the crates.io publishing token, so whatever those refs pointed at on
the day of a release ran with the means to publish.

- Third-party actions are pinned to full commit SHAs, with the release each
  corresponds to in a trailing comment: checkout v7.0.1, rust-cache v2.9.2 and
  crates-io-auth-action v1.0.5, which is also the commit `v1` pointed at.
- `dtolnay/rust-toolchain` is replaced with the runner's own `rustup`. The
  action is versioned by branch, so there is no release for a pin to name or
  for Dependabot to follow. Toolchains and components are unchanged.
- `ci.yml` declares `permissions: contents: read`. Nothing in it writes.
- `publish.yml` grants `id-token: write` to the publish job rather than the
  whole workflow, and runs that job in the `crates-io` environment. The
  environment's protection rules are repository settings and binding it in the
  crate's Trusted Publishing configuration is a crates.io setting; neither is
  part of this change. Naming the environment first is safe, because a Trusted
  Publisher with no environment configured accepts a run from any.
- Checkouts set `persist-credentials: false`. No job pushes.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
A SHA pin does not move on its own. Dependabot proposes a new pin, and
rewrites the version comment beside it, when an action publishes a release.

Weekly for `github-actions`, grouped into a single pull request, and weekly for
`cargo`. Both hold a new release back for seven days before proposing it, so a
retagged or withdrawn release has time to be noticed; security updates are not
held back.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested a review from a team as a code owner September 11, 2026 20:30
@stormer78
stormer78 merged commit 804cb8d into main Sep 12, 2026
7 checks passed
@stormer78
stormer78 deleted the sec-4045/workflow-pinning branch September 12, 2026 05:46
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