Skip to content

GitHub Actions Hardening

The enterprise-wide runbook context: the shared language for hardening GitHub Actions across every organization in a GitHub Enterprise Cloud account.

Required workflow: A workflow hosted in the central security-workflows repo that the enterprise ruleset forces to run on targeted repos, whether or not those repos authored it. This is the single merge-gating mechanism for both scanners. Avoid: required check (for the scanners), required action

Required status check: A named check that branch protection waits on, reported by a workflow that already exists in the target repo. Not used to gate the scanners — kept out of scope to avoid double-gating and check-name drift. Avoid: status gate, required check (as a synonym for required workflow)

PR security scanning: The single layer that runs static analysis on every pull request via centrally hosted workflows: zizmor (Actions security posture), which is required, and dcg (destructive shell commands), which is advisory. Replaces the earlier split of “Layer 2 / Layer 2b.” Avoid: audit layer, zizmor layer

Scanning workflow: A centrally hosted workflow that performs one kind of PR-time static analysis and uploads SARIF. The two are the zizmor scan and the dcg scan; only the zizmor scan is a required workflow. Avoid: audit workflow (for zizmor), audit (as a verb for either scanner)

Evaluate mode: The ruleset enforcement state that reports would-be violations via Insights without blocking merges. Flips to Active mode after the rollout window. Avoid: audit mode, dry-run mode

Active mode: The ruleset enforcement state that blocks merges on violations. Avoid: enforced mode, live mode

Pinning dry-run: The stand-in for the Actions policy’s missing Evaluate mode: zizmor’s unpinned-uses findings, counted enterprise-wide, tell you how much breakage Layer 1 would cause before you enable its hard cutover. This is why Layer 1 lands late in the rollout. Avoid: audit proxy, pinning pre-audit

Unautomatable control: An enterprise control GitHub exposes only through the web UI, with no stable API — the Layer 1 SHA-pin toggle, environment protection rules, and the outside-collaborator approval default. Tooling may report such a control as unset or unknown, and may print the values to enter, but can never apply them. Avoid: manual step, UI-only setting

Use these verbatim everywhere, including inside code and YAML samples.

<enterprise>: The enterprise slug. Avoid: <enterprise-slug>, acme-inc

<platform-org>: The one org designated to host shared workflows and tooling. Avoid: <company>-platform, PLATFORM_ORG, acme-platform

<org>: Any consuming organization in the enterprise. Avoid: <your-org>, <company>, acme-product

security-workflows: The fixed literal name of the shared workflows repo, always as <platform-org>/security-workflows. The “it’s only a convention” caveat is stated once in Appendix D and nowhere else. Avoid: ci-workflows, platform-tooling, “the central .github repo”

<org>/.github: The org-defaults repo (community health files, org profile, governance docs). Always qualified as “org-defaults repo”; never called “the central .github repo”. Avoid: “the .github repo” (unqualified)

SHA-pin: Pinning an action reference to a full-length commit SHA. The canonical prose term; the zizmor config expresses this as the literal hash-pin. Avoid: hash-pin (in prose), “full-length commit SHA pin”, “SHA-pinning” as a noun for the concept

Tag-pin: Pinning an action reference to a mutable ref (tag or branch). The canonical prose term; the zizmor config expresses this as the literal ref-pin. Avoid: ref-pin (in prose), “tag pinning”, “reference by tag”

Pin-policy parity: The invariant that the namespaces zizmor allows to ref-pin must exactly equal the namespaces the Layer 1 allowlist exempts from SHA-pinning (GitHub-created, verified Marketplace, and <org>/*). If the two sets drift, zizmor and the Actions policy give contradictory signals. Cross-referenced from both Layer 1 and Layer 2. Avoid: allowlist overlap, pin exemption match

Presence gate: An enterprise push ruleset that restricts pushes touching .github/dependabot.yml. GitHub push rulesets have no deletion-only rule for a path, so the available file_path_restriction rule blocks every push to that path — deletion, gutting, and legitimate edits alike. Guarantees the file survives; does NOT guarantee its contents are correct, and requires a bypass_actors entry for the content reconcile. Avoid: “blocks deletion” (the rule is broader), “prevents drift” (overstated), drift protection

Content reconcile: A scheduled harden.sh reconcile run that re-asserts the canonical dependabot.yml and opens a PR wherever a repo’s copy has drifted. The only mechanism that guarantees config content, as opposed to mere survival. Avoid: auto-PR option, sync job

Bootstrap: The one-time script run that seeds dependabot.yml into every repo that lacks it. Distinct from the ongoing presence gate and content reconcile. Avoid: rollout script

Baseline enterprise policy (Layer 0): The set of low-effort enterprise defaults applied before the four layers: read-only default token, no Actions-created PRs, OIDC over stored secrets, and environment protection. Independent of the additive layers. Avoid: Layer 1 (as a synonym), pre-reqs

Least-privilege token default: Setting the default GITHUB_TOKEN permission to read-only enterprise-wide, so workflows must opt up via an explicit permissions: block. Avoid: read-only token (unqualified), token hardening

OIDC: Short-lived cloud authentication via workload identity federation, scoped to a repo:<org>/<repo> subject, replacing stored long-lived cloud secrets. Avoid: keyless auth, federated login

Fast-track exception process: The auditable, time-boxed path for granting a narrow exception when a gate blocks legitimate work: a single SHA allowlist entry, one dcg project allowlist rule, or a time-boxed ruleset bypass, requested in the platform repo with a mandatory expiry. Never a standing or blanket disable. Avoid: bypass, waiver, override request