Audit workflows with zizmor, and fix 13 of the 15 findings it reports today - #179
Merged
Merged
Conversation
CodeQL default setup was turned on for this repository on 2026-09-21 and covers the Actions language. This adds zizmor, which the [OWASP GitHub Actions Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/GitHub_Actions_Security_Cheat_Sheet.html) recommends beside CodeQL for defense in depth. zizmor reads each workflow's logic and checks what CodeQL does not: hash pins no tag points to, version comments that disagree with their pin, App tokens broader than the job needs, and credentials a checkout leaves in `.git/config`. Run against `integration` before this change with online audits on, zizmor 1.30.1 reported 15 findings. This clears all of them, so the Security tab starts clean. - **`board-reconcile.yml`, 2 high.** The App installation token inherited the installation's whole grant and every repository it covers. It now names this repository and the four permissions the reconciler uses. The installation is that narrow today, but its settings live outside this repository and were over-provisioned once already, with `repository_projects: write` removed on 2026-09-21. With the grant stated in the workflow, a wider installation still mints a narrow token. The repository is named literally because a scheduled run carries no repository in its event payload. - **`pr-intake.yml`, 1 high.** zizmor flags every `pull_request_target`. This one checks out no code and reads the pull request body as data through an environment variable, which the workflow's header already explains. It carries an inline `zizmor: ignore` that points at that explanation. - **Eleven checkouts across nine workflows, 11 low.** Each left the job's token in `.git/config`. None of those jobs pushes with git. They call `gh` with `GH_TOKEN` or touch no network at all, and `sync_version.yml` hands its push to `create-pull-request`, which configures its own token. All eleven now set `persist-credentials: false`. The checkout in `validate-owasp-metadata.yaml` also gains the `# v7.0.1` comment every other pin carries. - **`reference-implementation.yml`, 1 medium.** The setup-bun pin `735343b` is v2.0.2, and its comment said v2.0.1. Only the comment changes. Dependabot's #83 already treats it as 2.0.2. The new workflow runs zizmor from a `zizmor` dependency group in `uv.lock`, pinned with hashes. No new action enters the Actions allowlist, and Dependabot's uv ecosystem moves zizmor under the existing 7-day cooldown. The group is not a default group, so `uv sync --locked` in the deploy and test jobs never installs it. A default sync against this lockfile would uninstall it. It runs on pull requests and pushes that touch `.github/`, an action definition, `pyproject.toml`, or `uv.lock`, and weekly, because the online audits compare pins against advisories published after the pin. Findings upload as SARIF under the category `zizmor`, and the job stays green on findings so code scanning tracks them and a ruleset can gate on them later. A tool or upload failure still fails the job. A pull request from a fork cannot upload with its read-only token, so it gets annotations instead. `upload-sarif` is pinned at v4.38.0 because v4.38.1 was three days old, inside the cooldown this repository applies to its dependencies. One change cannot be verified before merge. The `board-automation` environment releases its key only to `integration`, so a dry-run dispatch from this branch cannot mint a token. After merge, dispatch `Reconcile project board` on `integration` with apply off. A wrong scope fails the token request loudly, and the next nightly run is the one at risk, so the dry run belongs on the same day. Open pull requests #162 and #169 add workflows that zizmor will flag once they land, for unset `persist-credentials` and the same `# v2.0.1` setup-bun comment. Verified locally: zizmor 1.30.1 with online audits reports no findings, with 1 ignored and 26 low-confidence findings suppressed by the default persona. The workflow's own command produces SARIF 2.1.0 with zero results. `actionlint` is clean. `uv lock --check` passes under uv 0.9.9, the version CI pins, and the lockfile diff adds only zizmor. `uv run pytest` gives 278 passed, 1 skipped. Signed-off-by: Rock Lambros <rock@rockcyber.com>
rocklambros
requested review from
GangGreenTemperTatum,
afogel,
bar-capsule,
fewdisc,
mamicidal and
sclintonowasp
as code owners
September 21, 2026 16:38
This comment has been minimized.
This comment has been minimized.
|
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. |
Token scopes are not changing in this pull request. zizmor's two github-app findings on board-reconcile.yml stay open in code scanning, where they record the deferred decision instead of disappearing behind a suppression. Signed-off-by: rocklambros <rock@rockcyber.com>
10 tasks
rocklambros
added a commit
that referenced
this pull request
Sep 22, 2026
v0.1.2 reached `main` with #59 on 2026-09-09 and stayed untagged until today, when it was tagged by hand on `7743826`, the commit that first carried it. Tagging was a manual step with no owner, so nothing noticed the gap. `Tag release` makes it automatic. A push to `main` that changes `version.txt` is the release event, and the workflow tags that commit `v<version>` as an annotated tag, which matches how v0.1.1 and v0.1.2 are shaped. It exits quietly when the tag already exists, so reruns and unrelated pushes are safe. A manual dispatch from any branch other than `main` is skipped, since it would tag a commit `main` never published. It also refuses to tag when `version.txt`, `pyproject.toml`, and `uv.lock` disagree. The release version lives in those three files and no other check keeps them in step. A tag is the point where a mismatch becomes permanent, so the run fails with all three values in the error. `version.txt` is attacker-controlled by anyone with write access and becomes a git ref here, so it passes the same semver check `sync_version.yml` uses before it reaches `git tag`. No `${{ }}` expression appears inside `run:`. The job holds `contents: write` and nothing else, and the only action is `actions/checkout` at the SHA every other workflow pins. The workflow lives on `main` only after the next promotion, so the first release it tags is the next version bump that reaches `main` after that. Tested against a local bare remote with the step's own script, extracted from the YAML. An existing version exits 0 with "already exists". A new version in all three files creates an annotated tag and a rerun is a no-op. A stale `uv.lock` fails with the three values. An invalid version fails before touching git. `actionlint` is clean and `uv run pytest` gives 278 passed, 1 skipped. The checkout keeps its credentials because the tag push authenticates with them. zizmor's `artipacked` audit flags that, and the finding is suppressed inline with the reason: the job uploads no artifacts, which is the leak the audit exists to catch. With #179 merged, zizmor reports nothing on this workflow. CONTRIBUTING.md's Release Process section now says tagging is automatic. Signed-off-by: Rock Lambros <rock@rockcyber.com>
President
added a commit
to disciplinedware/agent-control-standard
that referenced
this pull request
Sep 23, 2026
…comment in the Go workflow zizmor, added in GenAI-Security-Project#179, reports both once this workflow lands. Nothing in the job pushes with git, so the checkout sets persist-credentials to false. The setup-bun pin 735343b is v2.0.2, and only its comment changes. Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
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.
CodeQL default setup was turned on for this repository on 2026-09-21 and covers the Actions language. This adds zizmor, which the OWASP GitHub Actions Security Cheat Sheet recommends beside CodeQL for defense in depth. zizmor reads each workflow's logic and checks what CodeQL does not: hash pins no tag points to, version comments that disagree with their pin, App tokens broader than the job needs, and credentials a checkout leaves in
.git/config.Run against
integrationbefore this change with online audits on, zizmor 1.30.1 reported 15 findings. This clears 13. The other two aregithub-appfindings onboard-reconcile.yml, whose App token takes the installation's full grant instead of naming its repository and permissions. Token scopes are not changing right now, so those two stay open in code scanning, where they record the deferred decision instead of disappearing behind a suppression.pr-intake.yml, 1 high. zizmor flags everypull_request_target. This one checks out no code and reads the pull request body as data through an environment variable, which the workflow's header already explains. It carries an inlinezizmor: ignorethat points at that explanation..git/config. None of those jobs pushes with git. They callghwithGH_TOKENor touch no network at all, andsync_version.ymlhands its push tocreate-pull-request, which configures its own token. All eleven now setpersist-credentials: false. The checkout invalidate-owasp-metadata.yamlalso gains the# v7.0.1comment every other pin carries.reference-implementation.yml, 1 medium. The setup-bun pin735343bis v2.0.2, and its comment said v2.0.1. Only the comment changes. Dependabot's ci: bump oven-sh/setup-bun from 2.0.2 to 2.2.0 #83 already treats it as 2.0.2.The new workflow runs zizmor from a
zizmordependency group inuv.lock, pinned with hashes. No new action enters the Actions allowlist, and Dependabot's uv ecosystem moves zizmor under the existing 7-day cooldown. The group is not a default group, souv sync --lockedin the deploy and test jobs never installs it. A default sync against this lockfile would uninstall it.It runs on pull requests and pushes that touch
.github/, an action definition,pyproject.toml, oruv.lock, and weekly, because the online audits compare pins against advisories published after the pin. Findings upload as SARIF under the categoryzizmor, and the job stays green on findings so code scanning tracks them and a ruleset can gate on them later. A tool or upload failure still fails the job. A pull request from a fork cannot upload with its read-only token, so it gets annotations instead.upload-sarifis pinned at v4.38.0 because v4.38.1 was three days old, inside the cooldown this repository applies to its dependencies.Open pull requests #162 and #169 add workflows that zizmor will flag once they land, for unset
persist-credentialsand the same# v2.0.1setup-bun comment.Verified locally: zizmor 1.30.1 with online audits reports only the two deferred
github-appfindings, with 1 ignored and 26 low-confidence findings suppressed by the default persona. The workflow's own command produces valid SARIF 2.1.0.actionlintis clean.uv lock --checkpasses under uv 0.9.9, the version CI pins, and the lockfile diff adds only zizmor.uv run pytestgives 278 passed, 1 skipped.Signed-off-by: Rock Lambros rock@rockcyber.com