Skip to content

chore(ci): harden GitHub Actions workflows with zizmor - #2740

Merged
FonduemangVI merged 5 commits into
TRaSH-Guides:masterfrom
rcdailey:chore/zizmor-hardening
Jun 21, 2026
Merged

chore(ci): harden GitHub Actions workflows with zizmor#2740
FonduemangVI merged 5 commits into
TRaSH-Guides:masterfrom
rcdailey:chore/zizmor-hardening

Conversation

@rcdailey

@rcdailey rcdailey commented May 16, 2026

Copy link
Copy Markdown
Contributor

Pull Request

Purpose

This PR adds zizmor as a pre-commit hook and addresses the security findings it reports against the GitHub Actions workflows in this repo.

zizmor is a static analysis tool for GitHub Actions workflows. It catches common security misconfigurations (credential leaks, template injection, overly broad permissions, etc.) before they ship. Adding it to pre-commit means future workflow changes get checked automatically.

Approach

Fixes applied:

  • Added persist-credentials: false to checkout steps that don't need git push access. This prevents the GitHub token from being persisted in the local git config, reducing the attack surface if an action later accesses the .git directory.
  • Added explicit permissions: blocks to workflows that previously relied on the default (broad) token permissions. Each job now requests only what it actually needs.
  • Moved the deploy-pr.yml permissions from workflow-level to job-level so the build job (read-only) doesn't inherit write permissions meant for the comment job.
  • Replaced manual actions/cache + pip cache directory steps with setup-python's built-in cache: pip in the deploy workflows. Same caching behavior, less boilerplate.
  • Converted template expressions (${{ ... }}) in run: blocks to environment variables where possible. This prevents potential code injection if an interpolated value ever contains shell metacharacters. Specifically:
    • deploy-pr.yml comment step now uses env: block instead of inline interpolation
    • automatic-changelog.yml uses ${LAST_EDIT_TIME} (shell expansion) instead of ${{ env.LAST_EDIT_TIME }} (template expansion)

Intentional suppressions (with inline justification comments):

  • artipacked on the two checkouts that use secrets.PAT: these workflows need credentials persisted to push commits back to the repo.
  • dangerous-triggers on deploy-pr.yml: the workflow_run trigger is the standard secure pattern for accessing secrets in PR preview deployments.
  • cache-poisoning on the deploy workflows: only trusted pushes to master (or repo-owned workflow_run triggers) can write to the cache; fork PRs cannot poison it.

Pre-commit hook:

Added zizmor v1.25.0 via the zizmorcore/zizmor-pre-commit repo. It runs on .github/workflows/*.yml files.

Open Questions and Pre-Merge TODOs

  • Verify the deploy workflows still cache pip correctly with cache: pip on setup-python (this is functionally equivalent to the old manual approach, just less code)

Requirements

Summary by Sourcery

Harden GitHub Actions workflows by tightening permissions, reducing credential exposure, and integrating automated security scanning for workflow configs.

New Features:

  • Add zizmor static analysis as a pre-commit hook to automatically scan GitHub Actions workflows for security issues.

Enhancements:

  • Add explicit, least-privilege permissions to multiple workflows and jobs that previously relied on default token permissions.
  • Disable credential persistence for checkout steps that do not require push access to reduce token exposure risk.
  • Simplify Python dependency caching in deploy workflows by using setup-python's built-in pip cache instead of manual cache steps.
  • Refine workflow scripting by replacing GitHub expression interpolation in shell commands with environment variables to mitigate potential injection risks.
  • Clarify and adjust workflow_run and caching behavior with documented, intentional security rule suppressions where required.
  • Set an explicit empty permissions block in the hold-merge workflow to prevent unintended token access.

CI:

  • Strengthen CI security posture across workflows by standardizing permissions, token handling, and cache usage.

Deployment:

  • Ensure deployment-related workflows request only necessary permissions and use safer token and caching configurations.

@rcdailey
rcdailey requested a review from a team as a code owner May 16, 2026 14:57
@sourcery-ai

sourcery-ai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces zizmor as a pre-commit hook and updates multiple GitHub Actions workflows to harden security by tightening permissions, disabling unnecessary credential persistence, reducing template interpolation in shell, and simplifying Python dependency caching.

Flow diagram for deploy-pr workflow permissions and jobs

flowchart LR
  Trigger["Workflow Trigger<br>workflow_run: Trigger PR Build"]

  subgraph Workflow_deploy_pr[Workflow deploy-pr]
    direction TB
    BuildJob["Job build<br>permissions:<br>- contents: read<br>- actions: read"]
    CommentJob["Job comment-pr<br>permissions:<br>- contents: read<br>- pull-requests: write"]
  end

  Trigger --> BuildJob
  BuildJob -->|needs: build<br>outputs url, branch_url, pr_sha, pr_number| CommentJob
Loading

File-Level Changes

Change Details Files
Harden deploy-pr workflow by scoping permissions, disabling persisted credentials, simplifying pip caching, and avoiding template interpolation in shell scripts.
  • Move permissions from workflow-level to per-job, giving build read-only and comment job PR write permissions
  • Set persist-credentials: false on checkout steps that do not push back to the repo
  • Replace manual actions/cache-based pip caching with setup-python cache: pip
  • Use env: variables instead of inline ${{ }} expressions in run: blocks and rely on GITHUB_REPOSITORY in the comment URL
.github/workflows/deploy-pr.yml
Harden main deploy workflow and rely on setup-python’s built-in pip caching.
  • Add top-level permissions with read-only contents for the workflow
  • Disable persisted credentials on checkout
  • Replace manual pip cache steps with setup-python cache: pip and document cache-poisoning suppression
.github/workflows/deploy.yml
Reduce template interpolation in automatic-changelog workflow shell commands and justify PAT usage.
  • Annotate checkout using PAT with zizmor ignore for artipacked finding
  • Switch git log --since argument from GitHub expression interpolation to plain shell variable expansion
.github/workflows/automatic-changelog.yml
Tighten permissions and disable credential persistence across validation and utility workflows.
  • Add explicit read-only contents permissions where missing
  • Set persist-credentials: false on checkout steps that don’t need to push
  • Ensure existing job-level permissions include contents: read where appropriate
.github/workflows/custom-format-validation.yml
.github/workflows/metadata-validation.yml
.github/workflows/pre-commit.yml
.github/workflows/quality-profile-validation.yml
.github/workflows/mkdocs_bug_prevent.yml
.github/workflows/hold-merge.yml
Document intentional use of PAT and add zizmor suppression for contributor update workflow.
  • Annotate checkout that uses secrets.PAT with zizmor ignore for artipacked since it must push back to repo
.github/workflows/update_contributors.yml
Add zizmor as a pre-commit hook to statically analyze GitHub workflows.
  • Configure zizmor-pre-commit repo at v1.25.0 in pre-commit config
  • Enable zizmor hook targeting .github/workflows/*.yml files
.pre-commit-config.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions github-actions Bot added Area: github_actions Github Actions Related Area: Backend Backend Changes, not related to a specific section labels May 16, 2026
@rcdailey

Copy link
Copy Markdown
Contributor Author

flagging @saltydk since you've done some security hardening in the past here.

@github-actions

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:fb1d758caa43aecb3f4e216ae0824ccc38f001dd
Status: ✅  Deploy successful!
Preview URL:https://0e38e11c.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@sourcery-ai sourcery-ai Bot 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.

Hey - I've left some high level feedback:

  • In the deploy-pr.yml comment step you’ve moved most template expressions into env, but secrets.GITHUB_TOKEN is still interpolated directly inside the run: block; consider passing it via env: as well to keep all sensitive values out of the shell command line and align with the rest of the hardening changes.
  • For workflows where you’ve added cache: pip to actions/setup-python, it may be worth explicitly setting cache-dependency-path (for example to docs/requirements.txt) so the cache key tracks the actual dependency file you use rather than relying on the default.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `deploy-pr.yml` comment step you’ve moved most template expressions into `env`, but `secrets.GITHUB_TOKEN` is still interpolated directly inside the `run:` block; consider passing it via `env:` as well to keep all sensitive values out of the shell command line and align with the rest of the hardening changes.
- For workflows where you’ve added `cache: pip` to `actions/setup-python`, it may be worth explicitly setting `cache-dependency-path` (for example to `docs/requirements.txt`) so the cache key tracks the actual dependency file you use rather than relying on the default.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@rcdailey rcdailey changed the title chore: harden GitHub Actions workflows with zizmor chore(ci): harden GitHub Actions workflows with zizmor May 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:b08bda83deeca4227b9ee1a4fb27d4e2e783fb5b
Status: ✅  Deploy successful!
Preview URL:https://2e1d1205.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@github-actions github-actions Bot added the Status: Conflicted Pull Request is Conflicted label May 18, 2026
@rcdailey
rcdailey force-pushed the chore/zizmor-hardening branch from b08bda8 to 07234d1 Compare May 21, 2026 14:27
@github-actions

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:07234d1e0b93547a30c8f8c2c3d360a0628a36c6
Status: ✅  Deploy successful!
Preview URL:https://9f992306.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@github-actions

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:24b2ff25fccfc6de9206cf27019390e72671a7e2
Status: ✅  Deploy successful!
Preview URL:https://0a374535.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@github-actions github-actions Bot removed the Status: Conflicted Pull Request is Conflicted label May 21, 2026
@github-actions github-actions Bot added the Status: Conflicted Pull Request is Conflicted label Jun 2, 2026
@rcdailey
rcdailey force-pushed the chore/zizmor-hardening branch from 24b2ff2 to 06d9741 Compare June 7, 2026 13:11
@github-actions github-actions Bot removed the Status: Conflicted Pull Request is Conflicted label Jun 7, 2026
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:06d97415cf9e96d08de6cd8c5201d82603710b45
Status: ✅  Deploy successful!
Preview URL:https://a00ae7b4.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@rcdailey
rcdailey force-pushed the chore/zizmor-hardening branch from 06d9741 to 4ea8185 Compare June 10, 2026 22:51
@github-actions github-actions Bot removed the Area: Backend Backend Changes, not related to a specific section label Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deploying with ⚡ Cloudflare Pages

Latest commit:4ea8185636820cbd572f07c2a7fce93aa45c75e8
Status: ✅  Deploy successful!
Preview URL:https://98e993b5.trash-guides-6xu.pages.dev
Branch Preview URL:https://pr-2740.trash-guides-6xu.pages.dev

@github-actions github-actions Bot added the Status: Conflicted Pull Request is Conflicted label Jun 20, 2026

@FonduemangVI FonduemangVI 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.

Reviewed the workflow hardening. Solid, well-scoped PR, template-injection fixes, least-privilege permissions, and the persist-credentials: false split are all correct. Two optional cleanups inline; nothing blocking.

Comment thread .github/workflows/automatic-changelog.yml Outdated
Comment thread .pre-commit-config.yaml
rcdailey added 5 commits June 21, 2026 07:44
Apply security hardening across all workflow files based on zizmor
static analysis findings. Workflow-level permissions are locked down to
least privilege, and a zizmor pre-commit hook enforces ongoing
compliance.

- Add permissions: contents: read at workflow level to all workflows
  missing explicit permissions
- Add persist-credentials: false to all actions/checkout steps to
  prevent credential leakage
- Narrow deploy-pr.yml job-level permissions; remove overly broad
  workflow-level block
- Move inline expression interpolations to env vars in deploy-pr.yml
  comment step to prevent injection
- Consolidate pip caching in deploy.yml and deploy-pr.yml to use setup-
  python built-in cache
- Add zizmor: ignore annotations for intentional artipacked and cache-
  poisoning suppressions
- Add zizmor pre-commit hook (v1.25.0) for continuous static analysis
- Move secrets.GITHUB_TOKEN to job-level env block in deploy-pr comment
  step
- Replace inline secret interpolation with env var reference in curl
  command
- Add cache-dependency-path: docs/requirements.txt to setup-python in
  both workflows
Addresses remaining zizmor findings by moving user-controllable template
expressions out of run steps and into env blocks, and quoting unquoted
shell variable references.

- Move PAT secret to env block and use ${PAT} in git push URL in
  automatic-changelog.yml
- Replace ${{ github.workspace }} with $GITHUB_WORKSPACE and quote
  variable references in deploy-pr.yml
- Remove unnecessary checkout step from comment-pr job that had no files
  to check out
- Move github.event.before and github.sha to env block in
  mkdocs_bug_prevent.yml
- Quote $GITHUB_ENV redirect target and shell variables consistently
@rcdailey
rcdailey force-pushed the chore/zizmor-hardening branch from 4ea8185 to 98acc83 Compare June 21, 2026 12:49
@github-actions github-actions Bot removed the Status: Conflicted Pull Request is Conflicted label Jun 21, 2026

@FonduemangVI FonduemangVI 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.

Good to go. Thanks for this

@FonduemangVI
FonduemangVI merged commit 4c914af into TRaSH-Guides:master Jun 21, 2026
11 checks passed
@rcdailey
rcdailey deleted the chore/zizmor-hardening branch June 21, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: github_actions Github Actions Related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants