Workflow write access is secret read access whenever the workflow you can write executes with access to those secrets. That condition is the whole argument, and it is narrower than the headline. Someone who can change a workflow file does not automatically hold every repository, organization or environment secret. They hold whichever secrets the platform lets that workflow reach, under whichever approval rules govern the run. Most teams have never written that mapping down, because the question they audit is who can deploy, not who can change the file that decides what deploys.
On 8 October 2026, StepSecurity reports, two compromised maintainer accounts pushed a workflow named security-audit.yml directly to default branches across 345 repositories, with no pull request and no review. This post is not the campaign's chronology. It uses the campaign's confirmed behavior to test one decision: who holds workflow write access, meaning the authority to add or change a workflow definition, and what that authority is worth to an attacker who borrows the identity.
Workflow Write Access Is a Secret Read Path
The argument in Your CI/CD Pipeline Is Your Real Infrastructure Control Plane was that the pipeline holds authority over infrastructure state change. It listed "pipeline configurations that any repository contributor can modify" as part of the credential surface. That named the exposure without turning it into a decision. Authority over state change is exercised through the pipeline. Authority over the pipeline is exercised through the file that defines it, which makes workflow write access the control point. In modern infrastructure and IaC architecture the second authority is the one most organizations have never inventoried.
What the injected file did is documented by two independent sources. StepSecurity and Socket both describe a workflow that runs on manual dispatch and on a push to any branch. It reads the names of the Actions secrets already referenced in the repository's other workflow files and includes those secrets in its payload. It scans the working tree for 13 credential patterns. It checks out the full history with fetch-depth: 0 and walks it with git log -p --all. It sends what it finds in a single HTTP POST to a hard-coded IP address. StepSecurity makes the point that matters most for design: a secret committed once and deleted the next day is harvested just the same.
The permissions detail is where the shorthand needs care. At uber/athenadriver, StepSecurity found the run's GITHUB_TOKEN was contents: read, so the workflow could not push or publish with it. The collector still acknowledged receipt four seconds after the run began. My reading, which the sources do not state: a read-only token bounds what a workflow can change in the repository, not what it can read and send out, and contents: read does not stop a full-history checkout. Token hardening is a real control. It answers a different question from the one GhostAction asked.
The headline holds only when four conditions hold together:
- The identity can get a workflow file onto a ref where it will run.
- Actions is enabled for that repository and no approval gate stops the run.
- The secret is scoped so that the job can reference it.
- The runner has a path to send data out.
Remove any one and workflow write access stops being secret read access for that secret. The audit at the end of this post walks those four conditions. One thing the sources do not establish is whether organization-level secrets were reachable in the affected repositories, so this post does not claim it.
What the Campaign Confirmed, and What It Did Not
A campaign still being reported is easy to over-read. The table separates what the sources observed about how workflow write access was used from what they infer, and says what each finding does not establish.
| Finding | Status | Source | What it does not establish |
|---|---|---|---|
| Workflow injected by direct, unsigned commits to default branches under two maintainer identities on 8 October | Confirmed (commit record) | StepSecurity | How the identities were obtained |
| Workflow behavior: push and dispatch triggers, named-secret read, 13-pattern working-tree scan, full-history sweep, single HTTP POST | Confirmed (file analyzed) | StepSecurity, Socket | Whether every variant behaves identically |
Collector acknowledged receipt in at least one run (uber/athenadriver) |
Confirmed (run log) | StepSecurity | What that POST carried in that run, or what was done with it |
Runs succeeded in kitao/pyxel; its Actions-secret path held publishing credentials |
Confirmed runs; credentials present per Socket | StepSecurity, Socket | That the credentials were received or used |
Runs stalled in action_required at typecho-fans/plugins, which requires approval for workflow runs |
Reported | StepSecurity | Which setting produced that state, or whether it generalizes |
| Initial access was a leaked personal access token | Suspected ("most plausibly") | StepSecurity; Socket says the method was not observed | The actual entry path |
| Malicious package releases from stolen credentials | Not observed at time of writing | StepSecurity, Socket | That the credentials are safe; StepSecurity cautions against that reading |
| GitHub began blocking the workflow on 9 October; waves on 10 and 12 October extracted no secrets | Reported | GitGuardian, via The Hacker News | The scope of the block, or whether it covers variants |
The campaign counts need the same discipline. StepSecurity counted 345 repositories at injection and 378 with a live workflow on a default branch on 9 October by code search, forks excluded. Socket counted 346 in the original burst and later reported more than 500 accounts and tens of thousands of repositories since 7 October, in an update whose timestamp it treats as a placeholder. GitGuardian, as reported by The Hacker News, reports more than 30,000 repositories targeted, about 3,000 users and organizations, about 1,900 accounts compromised and 3,174 secrets attempted. "Attempted" is not "stolen." These figures come from different populations and different windows. I do not add them, and no sentence in this post depends on a total.
Four Controls That Are Not Substitutes
Each control below answers a different question, and each leaves a different question open. Treating one as the fix is how an organization ends up with a hardened token and an open workflow directory. Workflow write access is governed by the first control and constrained by the other three. This is a control plane boundary question as much as a secrets question, the kind the Control Plane Boundaries stage covers: who may change the definition of what runs, and who may release what it runs with.
| Control | Question it answers | What it leaves open | Evidence status |
|---|---|---|---|
| Workflow-change authority | Who holds workflow write access to a file that will run with this repository's secrets? | A legitimate or borrowed identity with that right still succeeds | Incident: the attacker used maintainer identities. Review or ownership rules on the workflow path are a design option; their effect here is untested |
| Execution approval | Who or what must approve a run before it starts? | A file already on the default branch; repositories without the rule; reviewers who approve by habit | Reported at one repository (typecho-fans/plugins), mechanism unconfirmed |
| Secret scoping | Which jobs can reference which secrets, and under what conditions? | Repository-level secrets, which are not gated by environment rules; credentials already in history | Documentation-based; not tested in the incident |
| Issuer-side revocation | Can the credential be invalidated where it was issued? | Does not stop the next injection | Documentation and prior analysis; GhostAction repeat victims suggest unrotated secrets (GitGuardian's inference) |
The scoping row rests on GitHub's documentation, which I read directly. Environment secrets are available only to jobs that use the environment, and such a job has to satisfy the environment's protection rules before it runs or reads those secrets. Required reviewers, deployment branch and tag restrictions and a prevent-self-review option are the levers. Two limits matter for an audit. Administrators can bypass the rules unless that setting is disabled. And on GitHub Free, Pro and Team plans, rules such as required reviewers are available only for public repositories, so a private repository on those plans may have the environment but not the gate. See GitHub's documentation on managing environments for the current wording.
None of those controls was shown, in the sources I read, to have prevented or stopped GhostAction's pushes. StepSecurity reports direct, unsigned commits to default branches without pull requests. The sources do not state the repositories' branch rules or which credential type made the pushes. I am not claiming that branch protection would have stopped them.
⚠ Common mistake: Treating environment protection as workflow protection. Environment rules gate access to environment secrets for jobs that use the environment. They do not stop a new workflow file from being added, and a repository-level secret is not environment-scoped, so those rules do not gate it.
History and Forks: Copies a Repository Cleanup Does Not Revoke
Three things get conflated after an incident like this, and they need to stay separate: the workflow file, the credentials available to a run, and the credentials already sitting in history. Limiting workflow write access addresses the first and constrains the second. It does nothing for the third.
The workflow file travels. StepSecurity reports that 279 of the 318 repositories pushed under one account were forks, and that forks of infected repositories carry the file. Socket says downstream forks may inherit it when created or synced from an affected upstream. A copied file is only a file.
The credentials available to a run do not automatically travel with it. GitHub's documentation states that secrets are not passed to the runner when a workflow is triggered from a forked repository, with GITHUB_TOKEN as the stated exception. A copied workflow file therefore does not carry the source repository's Actions secrets. That sentence is not a statement that secrets are unavailable to every workflow associated with a fork, and the incident sources do not say that fork runs exfiltrated secrets. StepSecurity says enabling Actions on a fork runs the file in the fork owner's account context. What that context can reach depends on the fork owner's own configuration.
The credentials in history travel with the history. A fork is made from a repository's history. The sweep walks that history. A credential committed years ago and removed from the current tree is still in the history a checkout with fetch-depth: 0 retrieves. Rewriting the repository to remove it changes what future checkouts see. It does not recall anything already copied, and it does not touch forks or clones you do not control.
That is why the fourth control sits at the issuer. A credential is revoked where it was issued or it is not revoked. How much one revoke action has to enclose, and why a rotation can complete while a surviving credential still reads the replacement, is the argument in Rotating Credentials Is Not Revoking Them. GhostAction adds one practical input to that analysis: the set you must name now includes every credential that ever reached a branch, not only the ones in current files. GitGuardian reports that some victims were hit in earlier waves too, and reads that as attackers relying on unrotated secrets. That is GitGuardian's inference, not an established finding.
The Workflow-Write Audit
This is the decision the post is built around: who holds workflow write access, repository by repository. It is a worksheet, not a policy. Each row maps to one of the four conditions or one of the four controls.
| Question | Where to look | An answer that should worry you |
|---|---|---|
| Which identities hold workflow write access? | Repository and organization permission lists; personal access token, app, deploy key and bot inventory | Write access held by identities nobody has reviewed since onboarding; tokens with broad repository scope |
| Can any of them bypass review? | Branch and merge rules; administrator-bypass settings | A maintainer token that can push straight to the default branch |
| What runs on a push to any branch? | The on: triggers in each workflow file |
Workflows that run on any push and can reference secrets |
| Which secrets can a new workflow reach? | Secrets by scope: repository, organization, environment | Production credentials stored at repository level |
| What stops a run nobody asked for? | Actions run-approval settings; environment reviewers and branch restrictions; plan and visibility limits | Rules that exist on your public repositories but are unavailable on private ones on your plan |
| What can a run send out? | Runner egress controls, including self-hosted runner network policy | Unrestricted outbound HTTP from runners |
| Would you see a new workflow file appear? | Audit log and repository events for workflow-file additions | No alert on workflow-file additions outside review |
| Which credentials sit in history? | Secret scanning across full history, not only the current tree | Credentials committed long ago and still valid at the issuer |
Socket's remediation guidance points at the same places: audit organization logs for workflow-file additions, block the collector address in egress logs including on self-hosted runners, and require approval for workflow runs from outside collaborators. Those are recommendations from the incident reports. They are consistent with the worksheet, and none was tested against this campaign.
Run the table before you decide on a control. The answers tell you which of the four is missing, and they will usually show that the weakest row is not the one the incident made famous.
Diagnostic: "For each repository, which identities can put a file on a branch that will run with this repository's secrets, and what stops a run nobody asked for?"
Architect's Verdict
Workflow write access is secret read access under conditions you can enumerate: an identity that can place the file, a run that nothing stops, a secret the job can reference and a path out. GhostAction confirmed those conditions held in the runs the sources document. It did not confirm which of your controls would have broken them, and I have not claimed that it did.
The real problem is that many organizations guard the secret store and leave the question of who may ask the store for secrets to whoever has workflow write access. Authority over the pipeline was never in the vault. It sat in the file that requests the credentials, and in whoever can change that file. A borrowed maintainer identity inherits that authority without needing to defeat anything.
The four controls stay distinct because they fail distinctly. Change authority does not remove a file already merged. Approval does not scope a secret. Scoping does not reach history. Revocation does not stop the next injection. An architecture needs each of them to answer its own question, and a worksheet that shows which row is empty.
Know who can change the file that asks for your secrets, before someone else borrows that identity.
Additional Resources
- Modern Infrastructure & IaC Architecture — the pillar hub for pipeline, control plane and IaC authority design; where this post's workflow-definition question sits.
- Control Plane Boundaries — Learning Path stage on who may change what runs and what it runs with.
- Your CI/CD Pipeline Is Your Real Infrastructure Control Plane — the owner post: pipeline authority over infrastructure state change, and the contributor-modifiable configuration bullet this post turns into a decision.
- Rotating Credentials Is Not Revoking Them. The Revocation Unit Decides Whether You Can. — what one revoke action has to enclose, which history and fork copies make harder.
- StepSecurity: GhostAction Returns — primary analysis of the injected workflow, the confirmed runs and the commit record.
- Socket: GhostAction cloud credentials — independent analysis of the workflow, fork inheritance and remediation guidance.
- GitGuardian reporting via The Hacker News — wave dates, the GitHub block and GitGuardian's counts.
- GitHub Docs: managing environments — environment secrets, required reviewers, branch restrictions, administrator bypass and plan limits.
- GitHub Docs: using secrets in GitHub Actions — the statement that secrets are not passed to the runner for fork-triggered workflows.
Originally published at rack2cloud.com



Top comments (0)