Skip to content

ci: declare read-only tokens, stop persisting checkout credentials and lock the workflow actions - #978

Open
Totara-thib wants to merge 3 commits into
googleworkspace:mainfrom
Totara-thib:ci-hardening
Open

Totara-thib wants to merge 3 commits into
googleworkspace:mainfrom
Totara-thib:ci-hardening

Conversation

@Totara-thib

Copy link
Copy Markdown

Description

Hello! Three CI hardening commits:

  • CI, Audit, Coverage and Policy had no permissions block, so their thirteen jobs ran with the repository's default token scope. They build, test, lint, audit and measure coverage, and write nothing with the job token (Codecov uses its own), so each workflow now declares contents: read.
  • Eighteen checkouts stop persisting the token. The three jobs that commit from their clone keep the bot token they check out with: the formatter, the skills generator and the changesets release.
  • .github/workflows/actions.lock, GitHub's own workflow dependency lockfile from gh actions-lock, takes over from the hand-kept commit pins. It records the commit every action resolves to, transitive ones included, and the runner enforces it. The workflow files go back to readable release versions, so Dependabot can bump them and its security alerts match again, which they never do on commit pins. The dtolnay sites say toolchain: stable explicitly, so the ref no longer carries that choice. After a bump: gh actions-lock, commit the file.

Why: a moved tag in any of those jobs reaches the smoketest credentials, the npm and crates.io tokens and the bot token these workflows hold (the tj-actions incident, CVE-2025-30066). Hand-kept pins close that door but hide the version and blind the alerts, the lockfile keeps it closed with both back.

Plumber scores these workflows D 40/100 before this PR and up to A 94/100 with it.

Found and fixed by Plumber's analysis, reviewed and submitted by me.

Dry Run Output:

// no command changed

Checklist:

  • My code follows the AGENTS.md guidelines (no generated google-* crates).
  • I have run cargo fmt --all to format the code perfectly. (no Rust change)
  • I have run cargo clippy -- -D warnings and resolved all warnings. (no Rust change)
  • I have added tests that prove my fix is effective or that my feature works. (workflow files only)
  • I have provided a Changeset file (e.g. via pnpx changeset) to document my changes. (the policy check skips it without Rust or Cargo changes)

…workflows

These four workflows had no permissions block, so each of their
thirteen jobs ran with the repository's default token scope. They
check out, build, test, lint, audit the dependencies, measure coverage
and check the policy: the only writes are artifact uploads and the
Codecov upload, which uses its own token. Each workflow now declares
contents: read at the top, which is all the jobs read. The smoketest
keeps its credentials secret, which is not a token scope.
actions/checkout keeps the job token in the clone's git config, where
every later step can read it. Eighteen checkouts now set
persist-credentials: false: the CI, audit, coverage, policy,
publish-skills and release jobs build, test and publish from the clone
but never push to it, and the release job creates the GitHub release
with the token it is given explicitly.

The three jobs that commit from their clone keep the bot token they
check out with: the formatter on main, the skills generator and the
changesets release.
The workflows pinned each action by a hand-maintained commit, with the
version in a comment. GitHub's workflow dependency lockfile,
.github/workflows/actions.lock, generated with gh actions-lock, holds
that guarantee in one place: it records the commit every action
reference resolves to, transitive actions included, and the runner uses
those commits even if a tag moves later. Every reference moves from the
commit to the release that commit is, so the version is readable in the
workflow again and Dependabot can bump it and match its security
alerts, which never cover commit pins. The four references whose commit
is on no release (taiki-e/install-action, the two DeterminateSystems
actions and the attest actions behind attest-build-provenance) stay on
their commit, recorded as such.

The dtolnay/rust-toolchain sites move to the v1 tag with an explicit
toolchain: stable input: the stable commit they pinned is on no tag or
branch upstream anymore, dtolnay rebases those branches, so the lock
could not verify it, and the toolchain choice now sits in the input
where a tag change cannot move it.

Maintenance: after adding or bumping an action, run gh actions-lock at
the repository root and commit the file with the workflow change.
@google-cla

google-cla Bot commented Nov 4, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@changeset-bot

changeset-bot Bot commented Nov 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 12d8fbc

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Totara-thib

Copy link
Copy Markdown
Author

The 50 zizmor errors are one finding: its unpinned-uses audit wants a 40-hex commit on each uses: line and does not read .github/workflows/actions.lock. That file is GitHub's workflow dependency lockfile (https://github.com/proxy/docs.github.com/en/actions/concepts/security/workflow-dependency-lockfiles): it records the commit every reference here resolves to, transitive actions included, and the runner uses those commits, so the tag in the workflow file is a label and the lockfile is the pin. If you would rather keep the hash pins in the files until zizmor reads the lockfile (or use Plumber which is similar than zizmor but support lockfiles), I drop the last commit and keep the first two.

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