6 min read
A workflow security incident needs a recovery design
A patched reference is not a recovery plan
Changing a compromised action reference is necessary, but it answers only one question: what code should run next time? It does not tell me which repositories used the action, what credentials those workflows could access, whether those credentials were exposed, or how to keep releases moving while I contain the risk.
That gap is timely. In its October 2026 report on the returning GhostAction campaign, GitGuardian reported that it had identified 772 affected repositories and that only 124 had been effectively cleaned in observed public history by October 5, 2026. The distinction matters: that cleanup figure describes what GitGuardian could observe publicly, not a verified inventory of every affected repository.
I would treat this as a CI architecture problem, not a sequence of emergency edits. Recovery depends on being able to connect three things: the dependency that changed, the workflows that consume it, and the credentials those workflows could reach. If any of those relationships are opaque, a team cannot confidently bound the incident. It may either leave access open or revoke so broadly that routine shipping stops.
Build the map before an incident
A reusable pipeline gives me a useful place to standardize workflow behavior, but reuse alone does not create visibility. I need an inventory that can answer concrete questions: which repositories call a shared workflow, which repositories reference a particular action, and where are credentials exposed to those jobs?
I would keep that inventory alongside the pipeline definitions and update it as part of the change process. For each repository, I want the workflow entry points, reusable workflows and third-party actions it invokes, the permissions granted to GITHUB_TOKEN, and the names and owners of credentials available to publishing or deployment jobs. I do not need to copy secret values into the inventory. The point is to know which credential to revoke, who can rotate it, and what depends on it.
This is also why I prefer explicit hand-offs over passing every secret through a shared workflow. GitHub documents that secrets: inherit passes all secrets available to the calling workflow to the called workflow in its reference for workflow syntax. That convenience widens the set of credentials a called workflow can access. I would pass only the specific secret a job needs, or avoid a long-lived secret where an identity-based mechanism fits the deployment target.
A workflow can make the permission boundary visible instead of relying on a broad default:
name: build
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@<full-commit-SHA>
- name: Run tests
run: ./ci/test.sh
The SHA is a placeholder for a reviewed, full commit identifier, not a value to paste literally. I would pin third-party actions to full commit SHAs and grant GITHUB_TOKEN only the permissions the workflow needs. GitHub’s guidance on token permissions describes setting permissions at workflow or job level and recommends least privilege. These controls do not prove that an action is safe. They limit how much it can do if it is not.
Contain access without disabling every release
When an action is suspected, I would first identify the repositories and workflow runs that consumed it, then determine which jobs could reach credentials. The answer is not always “every secret in the repository.” It depends on where a secret is configured, which workflow can access it, and whether a job actually ran with the affected action. GitHub’s security hardening guidance for Actions warns that a compromised action can access secrets configured for its repository and may be able to use GITHUB_TOKEN to write to that repository. That is why I would scope the investigation to actual workflow reachability rather than infer safety from a successful reference update.
I would separate credentials by purpose and blast radius: read-only access for tests, publishing credentials only in the publishing job, and deployment credentials available only to the relevant environment. Then a response can pause or disable the affected path while leaving unrelated checks and releases alone. That is a more deliberate choice than turning off Actions organization-wide and discovering later that the team has no tested way to ship a security fix.
Containment still means revoking credentials that could have been exposed, not merely rotating them eventually. The recovery map should name the authority that can revoke each credential and the replacement process. For a credential shared across many repositories, the safe sequence is to establish the replacement path, revoke or replace the old credential, and verify that each consumer uses the new access before declaring recovery complete. GitHub also recommends auditing and rotating registered secrets in its security hardening guidance. The architectural point is that rotation must be executable under incident conditions, not just written in a policy.
Keep one trusted path to ship
The hardest recovery trade-off is between cutting off a potentially compromised path and retaining enough CI capability to deliver a fix. I would design a constrained release path that does not depend on the suspect action or on a shared credential whose exposure is still uncertain. That path can be narrower and more manual than normal delivery; it should still be documented and rehearsed before an incident.
For a shared pipeline, I would make the consumer relationship visible and use a controlled update process. A single change to a reusable workflow can affect many repositories, so the pipeline owner needs a way to identify callers, roll out a corrected version, and see which callers have not adopted it. The same visibility makes an emergency response tractable: stop the compromised version from being used, direct consumers to a reviewed version, and track completion rather than assuming a central edit fixed every caller.
I would define recovery completion as more than “the workflow is green.” I want evidence that affected references have been removed or replaced, reachable credentials have been revoked or rotated, permissions have been reduced where they were broader than necessary, and the intended release path works without the compromised dependency. If the inventory cannot answer those questions, improving it is part of the remediation.
That is the shift I want in CI design: from asking whether a reference was updated to asking whether I can identify, contain, and recover from the dependency’s full blast radius while keeping a trusted way to ship.
Drafted with AI assistance, reviewed and edited by Ali.