Skip to content

Harden CI workflows against script injection and credential exposure #363

Description

@SvenPistre

Problem

Pinning actions to SHAs (see #361) removes one CI attack surface, but it does not address vulnerabilities inside the workflow definitions themselves.
The workflows in this repository contain a few well-known GitHub Actions anti-patterns that an attacker could abuse:

  • Template injection: GitHub ${{ ... }} expressions are expanded before the shell runs, so any expression interpolated directly into a run: block is substituted as raw text into the script. Values that an attacker can influence (matrix entries, step outputs, cargo-dist install commands, anything derived from a pull request) can therefore break out of the intended command and execute arbitrary shell with access to repository secrets and the GITHUB_TOKEN.
  • Credential persistence: actions/checkout writes the GITHUB_TOKEN into .git/config by default (persist-credentials: true). Every later step - including any third-party action - can read that token and reuse it. This is called an ArtiPACKED Vulnerability.
  • Excessive permissions: the release workflow grants contents: write at the top level, so every job runs with write access even though only a couple of jobs actually need it.
  • Unpinned container images: a job pulls a container image from a dynamic matrix, which cannot be expressed as a static SHA pin and needs an explicit, documented exception.

cargo-chef produces the DockerHub image and the released binaries that downstream build pipelines depend on. A compromise of these workflows would let an attacker tamper with those build artifacts. This supply-chain risk needs to be ruled out by security-aware users before adopting cargo-chef.

Why static analysis (zizmor) fixes this

zizmor is a static analysis tool for GitHub Actions workflows. It detects template injection, credential persistence, over-broad permissions, unpinned images, and related findings, and many of them can be auto-corrected with zizmor --fix.

The fixes follow GitHub's own hardening guidance:

  • The template-injection fixes use the recommended pattern of passing the expression through an intermediate environment variable instead of interpolating it into the script body. This is the section zizmor --fix partly automated.
  • The permission tightening follows "Restricting permissions for tokens", defaulting the workflow to contents: read and granting contents: write only to the jobs that publish.

Proposed change

  • Run zizmor as a dedicated CI job so new workflow changes are checked automatically.
  • Apply zizmor --fix and the matching manual fixes:
    • move ${{ ... }} expressions out of run: blocks and into env: variables that are referenced as quoted shell variables,
    • set persist-credentials: false on every actions/checkout,
    • default the release workflow to contents: read and escalate to contents: write per job,
    • document the one unavoidable unpinned-images finding with an inline zizmor: ignore and a justification.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions