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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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