Everything the previous guides harden, minimal permissions, SHA pinning, verified provenance, lives in YAML files. And those files are code like any other: whoever can modify them can undo them. This module covers the layer YAML cannot reach, the one deciding who is allowed to change what, and which third-party code is allowed to run.
What you will learn
- Place the three control layers: repository, organisation, traceability
- Understand why a hardened workflow does not protect against an added one
- Order the rollout of those controls, from the least costly to the most demanding
- Spot the controls that depend on your GitHub plan
Hardening YAML has a structural limit
Take an exemplary repository: permissions: {} everywhere, every action pinned
by SHA, no dangerous interpolation, signed provenance. A contributor holding
write access adds a file to .github/workflows/. That file inherits no
constraint from the others: it declares its own permissions, its own actions, its
own triggers.
None of the protections written in the other workflows applies. Hardening is per file, compromise is per repository.
That is precisely the modus operandi of GhostAction, in September 2025: the attackers exploited no workflow flaw. They took control of maintainer accounts, read the legitimate workflows to inventory the secret names, then committed a malicious workflow disguised as "Github Actions Security". 327 accounts, 817 repositories, 3,325 secrets.
The defensive question is therefore not only "is this workflow safe?", but "who can add one, and who reviews it?".
Three layers, three scopes
| Layer | Where it is configured | What it closes |
|---|---|---|
| Repository | Rulesets, CODEOWNERS | Who can modify the workflows, and with which review |
| Organisation | Actions policies, runner groups, org secrets | Which third-party code runs, on which machine, with which shared secrets |
| Traceability | The audit log | What was changed, by whom, and when |
Those layers do not replace one another. A flawless ruleset does not prevent the use of a compromised third-party action; a strict actions policy does not prevent a maintainer from weakening the ruleset. They complement each other, and it is their accumulation that makes a compromise difficult rather than improbable.
The rollout order that lasts
Applying all five controls at once mostly produces workarounds. The order below goes from the least costly in friction to the most demanding, and each step stays useful even if you stop there.
| Step | Control | Friction for the team |
|---|---|---|
| 1 | Ruleset: forbid force push and the deletion of main | None, nobody was doing it |
| 2 | CODEOWNERS on .github/workflows/ | One review per pipeline change |
| 3 | Ruleset: pull request required, one approval, code owner review | One review per change, whatever it is |
| 4 | An allowed actions policy | A request whenever a new action is needed |
| 5 | Runner groups and network segmentation | An infrastructure to design |
The first two steps pay the most: they close the path GhostAction took, and cost almost nothing as long as nobody touches the workflows.
The trap of the control you loosen
A governance control has an unpleasant property: it gets in the way of the people who put it in place first. The temptation is then to relax it "just this once", and that is precisely the moment the protection disappears without anyone noticing.
Two reflexes make that risk manageable:
- Back up the state before changing it, so restoring is a command rather than an effort of memory;
- Have compliance measured by a scanner, so the weakening leaves a red trace in the CI rather than passing unnoticed. A posture scanner checking branch protection will report the gap on its next run.
That second point is what separates real governance from declared governance: without measurement, the configuration drifts, and nobody knows since when.
Key points
- Hardening YAML is per file; compromise is per repository. An added workflow inherits none of the protections of the others.
- GhostAction exploited no workflow flaw: the attackers committed a malicious workflow into repositories they had taken control of.
- Three layers stack: the repository (rulesets,
CODEOWNERS), the organisation (allowed actions, runners, secrets) and traceability. - The order that lasts goes from the least costly to the most demanding: force push forbidden, then
CODEOWNERSon the workflows, then mandatory review, then the actions policy, then runner segmentation. - Governance is the part most conditioned by the plan: additional runner groups require Team, actions policies only exist at organisation level.
- A control you loosen must be backed up first and measured by a scanner, otherwise the weakening becomes permanent without anyone knowing.
Next steps
- CODEOWNERS and workflow review: the most profitable control of the module, the one closing the path GhostAction took.
- Restricting the allowed actions: deciding which third-party code may run, before the pinning question even arises.
- Runner groups and the network boundary: what a compromised runner can reach, and how to bound it.