Skip to content

CI: hardens the workflows (SHA pins, scoped secrets and token permissions) - #2492

Merged
CarterLi merged 3 commits into
fastfetch-cli:devfrom
Totara-thib:ci-hardening
Aug 6, 2026
Merged

CI: hardens the workflows (SHA pins, scoped secrets and token permissions)#2492
CarterLi merged 3 commits into
fastfetch-cli:devfrom
Totara-thib:ci-hardening

Conversation

@Totara-thib

Copy link
Copy Markdown
Contributor

Drive-by CI hardening in three commits, one logical change each, found with the Plumber CLI (https://github.com/getplumber/plumber) and verified on dev.

Commit 1 pins every action to its commit sha, versions kept as comments. A tag like @v1 or a branch like @master is a movable pointer: whoever controls the action, or anyone who compromises it, can re-point it and your next run executes their code. The spots that matter here: build-release.yml creates the GitHub release with the binaries users download, and build-windows-hosts.yml holds the SignPath token, so a re-pointed tag in those paths could ship a tampered or wrongly signed release. Same pattern as the tj-actions/changed-files incident (CVE-2025-30066). You already pin docker/setup-qemu-action by sha and you bumped action majors by hand before (#808), so this just finishes the job: every sha was resolved from the upstream repo and cross checked against its release tag, and the three @master refs (cross-platform-actions, setup-alpine, get-latest-release) are pinned to their latest release tag. The pins stay maintainable with a dependabot config for the github-actions ecosystem (the repo has none today, happy to add one here if you want); dependabot and renovate both bump sha pins and keep the version comment in sync.

Commit 2 drops secrets: inherit from all 17 reusable workflow calls. The only secret any called workflow reads is SIGNPATH_API_TOKEN in build-windows-hosts.yml, so that one is declared and passed explicitly and nothing else gets the secret set. A compromised step in one of the BSD build VMs now has no signing token in reach. The secret is optional in the called workflow, so fork and PR runs behave exactly as before (the signing step is already guarded to upstream pushes).

Commit 3 scopes the GITHUB_TOKEN per workflow, derived from what each job actually does with it: every build workflow declares contents: read; build-linux-hosts.yml keeps security-events: write for its CodeQL upload while the other callers lose that grant (they never upload scanning results); build-windows-hosts.yml gets actions: read, which the SignPath action documents needing to read job details and fetch the unsigned artifact; build-release.yml declares the contents: write its caller already grants. Runs that execute PR code now hold a read-only token.

Two heads up:

  • If the org uses an Actions allowlist in settings, patterns written against tags (like owner/action@v1) stop matching once refs are shas and workflows refuse to start. Entries need to be owner/action@* in that case.
  • If you would rather not have a third-party action fetching the latest release tag in the release path at all, pozetroninc/github-action-get-latest-release can be replaced with a two-line gh api step; happy to do that here, kept the change minimal for now.

I will also open a PR which adds the tool to CI so this does not quietly drift back; that one is a bonus, this PR stands on its own.

Related issue (required for new logos for new distros)

Not a logo PR, no related issue.

Changes

  • Pin all 51 action references across the 19 workflows to full commit SHAs with version comments
  • Replace secrets: inherit with an explicit, declared SIGNPATH_API_TOKEN pass to the one workflow that uses it
  • Declare least-privilege GITHUB_TOKEN permissions on every workflow, derived from actual token use

Screenshots

No visual changes.

Checklist

  • I have tested my changes locally.

A version tag like @v1 or a branch like @master is a movable pointer:
whoever controls the action repository can re-point it, and the next
run executes whatever it points at. Several of these actions run in
jobs whose outputs ship to users: build-release.yml creates the GitHub
release with the downloadable binaries, and build-windows-hosts.yml
holds the SignPath signing token, so a re-pointed tag there could ship
a tampered or wrongly signed release. This is exactly how the
tj-actions/changed-files compromise propagated (CVE-2025-30066).

Pinning by full commit SHA makes the reviewed code the code that runs,
the same way docker/setup-qemu-action is already pinned in
build-linux-vms.yml. Refs that pointed at master (cross-platform-actions,
setup-alpine, get-latest-release) are pinned to their latest release
tag. Every pin keeps a version comment, and each SHA was resolved from
the upstream repository and cross-checked against its release tag.
Every reusable workflow call used 'secrets: inherit', which hands the
caller's whole secret set to the called workflow, but the only secret
any of them reads is SIGNPATH_API_TOKEN in build-windows-hosts.yml.
Passing that one secret explicitly and dropping inherit everywhere else
means a compromised step in, say, a BSD build VM has no signing token
to steal, and the workflow files now show exactly which secret flows
where.

The secret is declared optional in the reusable workflow so runs
without it (pull requests, forks) behave as before; the signing step
is already guarded to upstream push events.
Jobs without a permissions block get the repository's default token
scope, and reusable workflow calls pass the caller's grants straight
through. Scopes are now derived from what each workflow actually does
with the token:

- every build workflow declares contents: read; none of them writes
  through the GITHUB_TOKEN.
- build-linux-hosts.yml keeps security-events: write for the CodeQL
  upload; the other callers had that grant too but never upload
  scanning results, so they drop it.
- build-windows-hosts.yml gets actions: read, which the SignPath
  action documents needing to read job details and download the
  unsigned artifact.
- build-release.yml declares contents: write, matching the grant its
  caller already makes for creating the release.

Runs that execute pull request code now hold a token that can do
nothing but read the repository.
@CarterLi
CarterLi merged commit b6d95a0 into fastfetch-cli:dev Aug 6, 2026
23 checks passed
@flowerey

flowerey commented Aug 6, 2026

Copy link
Copy Markdown

So unneeded and not even useful. Why is this even merged?

@Totara-thib

Copy link
Copy Markdown
Contributor Author

So unneeded and not even useful. Why is this even merged?

Fair question. A concrete case: March 2025, tj-actions/changed-files (CVE-2025-30066). The attacker re-pointed the action's existing version tags at code that dumped CI secrets into public build logs, and over 20k repos ran it on their next build without changing a line. Repos pinned by commit SHA were untouched: the tags moved, their refs did not.

That pattern matters here because this CI holds real credentials. The Windows job carries the SignPath token that signs the release binaries, and the release workflow publishes what thousands of people download. One re-pointed tag on those paths and you get a tampered fastfetch, signed and shipped under the project's name. The secrets change is the same idea: before, all 17 build jobs received every repo secret; now only the signing job sees the signing token, so a compromised step in one of the BSD build VMs has nothing to steal.

Runtime cost is zero, the refs are just frozen, and dependabot keeps bumping them (it already did this morning)

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