Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

zagware-security-workflows

Single source-of-truth workflow for org-wide Zagware Scanner enforcement on GitHub, mirroring what GitLab's Pipeline Execution Policies already give you natively. Two paths, depending on plan:

workflows ruleset rule Reusable workflow + caller
Plan required GitHub Team or higher Any (Free/Pro/Team/Enterprise)
Caller file needed in target repo No Yes, one per repo
Silently stops enforcing if... Never — nothing to tamper with in the target repo Caller file is deleted/renamed, or its job ID drifts from the branch-protection "required" name
Supported trigger events pull_request, pull_request_target, merge_group only Anything the caller declares
Fails closed if Actions is disabled on target repo Yes — the check never resolves and merge is permanently blocked (expect support tickets) Same

Confirmed by testing directly against both a Free-plan org (zagware) and a Team-plan org (zagware): orgs/{org}/rulesets returns 403 Upgrade to GitHub Team on Free/Pro, full stop — there is no repo-level substitute for the workflows rule type at any plan (repos/{owner}/{repo}/rulesets rejects it structurally, GitHub's own docs confirm it's org/enterprise-only). Repository-level rulesets are otherwise unrestricted by plan; they just don't carry this specific rule.

Path A — workflows ruleset rule (GitHub Team+)

.github/workflows/security-scan.yml in this repo is the file a workflows-type organization ruleset points at. GitHub runs it directly against every targeted repo's pull requests — the github context (repo, ref, token, PR number) is bound to the target repo the same way a reusable workflow's context is bound to its caller, so actions/checkout with no repository: argument checks out the target PR automatically, and secrets referenced in the workflow resolve against whatever the target repo can see.

Setup:

./rulesets/setup-org-level.sh <your-org>

This defines a zagware-scan-scope custom property, then creates one organization ruleset scoped to zagware-scan-scope=enabled repos. Opt a repo in without touching the ruleset:

gh api orgs/<org>/properties/values -X PATCH \
  -f 'repository_names[]=<repo>' \
  -f 'properties[][property_name]=zagware-scan-scope' \
  -f 'properties[][value]=enabled'

Verified live end-to-end against zagware/gtp-test-insecure — a repo with zero .github/workflows/ files: the required check "IaC + SCA + Secrets security scan" appeared, ran, and passed, and GET /repos/{owner}/{repo}/rules/branches/{branch} confirms it as an active rule sourced from the organization ruleset (ruleset_source_type: "Organization").

Per-target-repo prerequisite (real GitHub setting, not something the ruleset can grant): Settings → Actions → General → Workflow permissions → "Read and write permissions", needed for the PR comment.

Org-level rulesets also need this same setting available org-wide (orgs/<org>/actions/permissions/workflow) if you want it to apply to every targeted repo by default rather than toggling it per repo.

Path B — reusable workflow + caller (any plan)

.github/workflows/reusable-scan.yml holds the actual scan logic behind a workflow_call trigger. Each target repo gets a thin caller:

# .github/workflows/zagware.yml (in the TARGET repo)
name: Zagware Security Scanner
on:
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  scan:
    uses: <org>/zagware-security-workflows/.github/workflows/reusable-scan.yml@main
    secrets: inherit

Then mark the scan job's check (Zagware Security Scanner / scan) required in the target repo's branch protection or a repository-level ruleset's required_status_checks rule (that rule type IS repo-level-compatible on any plan). Bumping the image tag, adding an env var, or changing the scan logic in this one reusable-scan.yml file propagates to every caller on its next run — but the caller file itself is real, per-repo state that can drift or be deleted without the org noticing.

Verified live against zagware/zagware-ruleset-demo (Free plan) using this exact pattern.

Interactive /zagware suppress and required workflows — a real caveat

/zagware suppress pushes a commit back to the PR branch using GITHUB_TOKEN. GitHub's anti-recursion protection means a GITHUB_TOKEN- authored push never re-triggers pull_request: synchronize — so under Path A, the new commit's required check would never get created and the PR would stay blocked indefinitely. This isn't solvable from inside the workflow; it needs a non-GITHUB_TOKEN identity (a GitHub App installation token) to make the push, since App-authored pushes DO re-trigger workflows. See docs/GITHUB_ORG_ENFORCEMENT_DESIGN.md in zagware/git-tracking-platform (private repo) for the full writeup, including why a single narrow, opt-in contents: write grant on the platform's existing shared GitHub App (not a new per-customer App, and not the org-wide write grants an earlier draft of that doc wrongly asked for) is the only permission change worth considering here — everything else about org-wide enforcement setup is designed to run under the org owner's own credentials, not the App's.

About

Centralized required-workflow source for org-wide Zagware Scanner enforcement via GitHub organization rulesets

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages