Skip to content

Declare the token scopes, drop the stored checkout credentials, lock the actions and add the Plumber check - #1066

Closed
Totara-thib wants to merge 5 commits into
linode:mainfrom
Totara-thib:ci-hardening
Closed

Totara-thib wants to merge 5 commits into
linode:mainfrom
Totara-thib:ci-hardening

Conversation

@Totara-thib

Copy link
Copy Markdown

📝 Description

What does this PR do and why is this change necessary?

Following Aurélien's exchange with Scott, here is the pull request offered.

  • Seven workflows had no permissions block: contents: read, plus pull-requests: read and checks: write on the fork integration job (its check run), issues: write on the labeler.
  • The eleven checkouts stop persisting the token, none pushes.
  • gosec runs its v2.30.0 release, not @master.
  • actions.lock is GitHub's own workflow dependency lockfile, generated with gh actions-lock: the commit behind each action (floating refs), enforced by the runner, while uses: keeps the release tags Dependabot bumps.
  • A Plumber workflow checks every change for these classes, badges the score in the README, fails under 85 points.

Why: the integration and nightly jobs hold a real Linode account's token. A wider job token or a moved action tag there reaches it, the tj-actions incident (CVE-2025-30066) went through that door.

If the allowed-actions setting uses tag patterns, switch them to owner/action@*.

✔️ How to Test

The checks run on this pull request, Plumber's included, and gh actions-lock --verify confirms the lockfile.

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

Seven workflows had no permissions block, so their jobs ran with the
repository's default token scopes. They read the tree, build and test,
or post to Slack with the bot token, so the workflow level says
contents: read. Two jobs write with the job token and say so: the
integration run on a fork updates its own check run, the labeler
creates and updates the repository labels. CodeQL, the release notes
cleanup and dependency review keep the blocks they already declare.
actions/checkout leaves the job token in .git/config by default, where
every later step and tool in the job can read it. The eleven checkouts
opt out: the test report goes to object storage with its own keys and
nothing pushes with git.
The security scan tracked the master branch of its action, which
changes with every push there. It now references the v2.30.0 release,
so the lockfile below holds one commit for it.
GitHub's workflow dependency lockfile holds the commit each action
reference resolves to, transitive actions included, and the runner uses
those commits even if a tag moves later. .github/workflows/actions.lock
comes from gh actions-lock (v0.4.0), which also narrowed every reference
to the exact release it locked, so Dependabot keeps bumping versions and
its security alerts keep matching. Re-run the same command after adding
or bumping an action.
A small workflow runs the Plumber CLI on pushes to main and on pull
requests. It checks the GitHub workflows for the classes of findings
the previous commits fixed (missing permissions, tokens left in
checkouts, floating action refs) and fails under 85 points, so one
small finding never blocks a pull request. Its two actions are recorded
in actions.lock like the others. The overlay keeps the built-in defaults
and trusts the four actions this repository uses on top of them. The
README gets the score badge in the badge row.
@Totara-thib
Totara-thib requested review from a team as code owners October 29, 2026 15:20
@Totara-thib
Totara-thib requested review from jriddle-linode and psnoch-akamai and removed request for a team October 29, 2026 15:20
@zliang-akamai
zliang-akamai requested a balanced review from Copilot October 30, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The gosec pin runs an older scanner, new workflows can bypass lockfile coverage, and the SARIF condition mishandles fork PRs and Dependabot pushes.

3 open issues
What changed in this PR

This PR hardens the Go client's GitHub Actions workflows, including jobs that use live Linode credentials, and adds automated workflow-security checks.

Changes:

  • Declares token permissions and disables credential persistence in checkout steps.
  • Replaces broad action references with release tags and adds a workflow dependency lockfile.
  • Adds Plumber checks with an 85-point threshold, repository configuration, and a README score badge.
File Description
README.md Adds the Plumber score badge.
.plumber.yaml Configures trusted actions alongside Plumber defaults.
.github/​workflows/​security_pr.yml Restricts permissions, disables stored credentials, and updates scanner references.
.github/​workflows/​release-notify-slack.yml Declares read-only permissions and specifies the Slack action release.
.github/​workflows/​release-cross-repo-test.yml Hardens checkouts and specifies action releases.
.github/​workflows/​plumber.yml Adds security scoring, report uploads, and badge publication.
.github/​workflows/​nightly_smoke_tests.yml Restricts permissions and hardens the live-test checkout.
.github/​workflows/​labeler.yml Grants label-management permissions and updates action references.
.github/​workflows/​integration_tests_pr.yml Scopes permissions for integration jobs and hardens checkouts.
.github/​workflows/​codeql.yml Disables stored credentials and specifies CodeQL releases.
.github/​workflows/​clean-release-notes.yml Specifies the GitHub Script action release.
.github/​workflows/​ci.yml Declares read-only permissions, hardens checkouts, and updates action references.
.github/​workflows/​actions.lock Records action commits and nested dependencies for listed workflows.

Review effort: Balanced

with:
persist-credentials: false

- uses: getplumber/plumber@v0.7.16
Comment on lines +31 to +34
# Code scanning upload needs security-events write, which PRs
# from forks do not get. The report stays available as a
# workflow artifact there.
upload-sarif: ${{ github.event.pull_request.head.repo.fork != true }}

- name: Run Gosec Security Scanner on root directory
uses: securego/gosec@master
uses: securego/gosec@v2.30.0
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.

3 participants