6 min read
Treat workflow changes as governed releases
A workflow change is a code change
A GitHub Actions workflow is executable code with access to whatever permissions and credentials its run receives. Treating it as ordinary repository configuration leaves a gap: a workflow can change the behavior of future builds and deployments, while a reviewer may see only a small YAML diff among routine changes.
The Hacker News reported that Socket identified credential-stealing workflows committed by more than 500 GitHub accounts to tens of thousands of repositories. I take the organization-wide change path as the design problem. Cleaning malicious files out of affected repositories matters, but it happens after the workflow has spread. I would instead make workflow changes visible as a distinct class of change and require trusted review before they become part of the path to credentials.
That does not mean every workflow edit needs a committee. It means that changes under .github/workflows/ deserve a clearly assigned owner, a review requirement that cannot be satisfied by the change author, and a release path that does not hand credentials to an untrusted run.
Put the review boundary in the protected branch
A practical starting point is CODEOWNERS for the workflow directory, together with a branch rule that requires code-owner approval. GitHub documents that code-owner reviews can be required for protected branches, and that the CODEOWNERS file used for review requests must exist on the pull request’s base branch. See GitHub’s documentation on code owners and the available rules for rulesets.
For example:
# .github/CODEOWNERS
/.github/workflows/ @example-org/ci-security
The team name is a placeholder; the important part is that the owners are trusted to judge workflow changes and are not just the people who happened to write the application code. Require the review in the protected-branch rule. If the rule is optional, or contributors can merge around it, the file only expresses intent.
I would also protect the ownership rule itself. A pull request that changes both a workflow and CODEOWNERS must not be able to remove its own review requirement. Keep branch protections and bypass permissions under a smaller administrative boundary, and review who can bypass them. Those are design recommendations, not properties that a CODEOWNERS file grants by itself.
This review boundary controls what can merge. It does not make a workflow safe to execute while a pull request is open. Review and execution are separate controls, and confusing them is an easy way to grant a proposed change access before the required reviewer has approved it.
Separate validation from credentialed work
I would make pull-request validation useful but deliberately unprivileged. It can check formatting, compile code, or run tests without receiving deployment credentials. Credentialed work should happen only after the change has crossed the trusted merge boundary, and its credentials should be scoped to the deployment job rather than made available to every job in the workflow.
A minimal validation workflow might begin like this:
name: Validate pull request
on:
pull_request:
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<reviewed-full-commit-sha>
- run: dotnet test
The SHA is intentionally a placeholder, not a value to copy. For an actual workflow, I would resolve and review the action’s full commit SHA. GitHub’s secure-use reference recommends pinning actions to a full-length commit SHA.
The example shows the shape of the boundary, not a complete deployment pipeline. In particular, contents: read is a starting point for this validation job, not a claim that it is the right permission set for every repository. I would add permissions only when a job needs them, and keep publishing or deployment in a separate job with its own narrowly scoped access. If a pull-request check needs a secret to run, I would first ask whether the check can be redesigned to run without it.
A trusted review of a diff is still necessary: least privilege limits what a bad change can do, but does not make it harmless. Conversely, review is not a substitute for least privilege. One reviewer can miss an added permission, an altered trigger, or a new action. The controls are stronger together because they address different failure modes.
Make unexpected changes visible across repositories
A review rule in each repository is only one part of the system. If workflow policy is copied into many repositories, differences accumulate: a repository may lack the owner rule, have a different workflow directory, or have an exception nobody remembers. I would treat the organization’s workflow estate as something to inventory, not assume that a template rollout made uniform.
Keep a machine-readable record of each repository’s workflow paths and their current contents. Compare that record with changes proposed and merged, and alert on additions, removals, or edits outside the expected change process. The useful alert is not just “a file changed.” It should identify the repository, the changed workflow, whether the expected owner reviewed it, and whether the change alters triggers, permissions, actions, or credential access. That gives a responder a path to investigate without pretending every workflow edit is an incident.
For a shared pipeline, I would prefer a small number of centrally maintained reusable workflows over copied implementations, while still making each repository’s call to the shared workflow subject to review. Centralization reduces duplicated policy, but it does not remove the need to govern the caller: a repository can change which workflow it calls, what inputs it passes, or what permissions it grants. I would make those changes visible in the same inventory and review process.
There is an operational cost. An organization-wide alert can be noisy, and a central workflow team can become a merge bottleneck. I would keep the policy narrow: elevate changes that can alter execution or credential access, assign a clear owner, and document exceptions with an expiry or review point. If the alert cannot tell an engineer what changed and why it matters, it will become background noise.
Govern the path, not just the incident
Secret rotation and repository cleanup are necessary responses when a malicious workflow has run. They do not prevent the next workflow change from crossing the same boundary. My preference is to make the boundary explicit before that happens: inventory workflow files, require trusted review for changes, limit what pull-request runs can access, and reserve credentials for the trusted release path.
That shifts the question from “How quickly can I clean up one compromised repository?” to “How did this workflow change become acceptable across repositories, and what would have stopped it?” The answer should be enforced in the change path, not left to someone noticing a suspicious YAML diff.
Drafted with AI assistance, reviewed and edited by Ali.