Copied to Clipboard
Then apply the patch to a clean clone:
git clone git@github.com:ORG/REPO.git clean-repo
cd clean-repo
git checkout -b recover-safe-work
git apply --check ../safe-work.patch
git apply ../safe-work.patch
Review before pushing.
This protects ongoing work without carrying malicious files forward.
Step 5: Clean GitHub repositories
There are three cleanup options. The best one depends on what the malware did.
Option A: Cleanup PR that removes the malicious files
This is usually the best first recovery step for private enterprise repositories when the malicious file did not contain secrets and the priority is safe recovery.
Process:
Create cleanup branch
Remove malicious files and configs
Remove references from package scripts, workflows, Docker, devcontainer, editor configs
Open PR
Require security/platform review
Merge after scanner and CI pass
Keep audit trail intact
This is operationally clean because history remains available for forensics.
The downside: the malicious blob remains in Git history. That may be acceptable for internal containment if secrets were not committed and credentials are rotated where execution occurred.
Option B: Recreate infected branches from a clean base and cherry-pick safe commits
This is best for ongoing feature branches.
Process:
git fetch origin
git checkout origin/main
git checkout -b clean-feature-branch
# Cherry-pick only reviewed safe commits
git cherry-pick <safe_commit_sha_1>
git cherry-pick <safe_commit_sha_2>
If a commit mixes good work with malicious files, avoid cherry-picking it directly. Create a patch excluding risky paths or manually reapply the safe changes.
This is cleaner than trying to rebase a branch that already contains malicious commits.
Option C: Rewrite history
Use this only when necessary, for example:
Secrets were committed
Malware must be removed from history for legal/compliance reasons
Public repositories or forks make retained history unacceptable
Large-scale credential exposure requires complete removal
History rewrite is disruptive. It requires coordinated force-pushes, branch protection handling, developer reclones, fork handling, and communication. It also does not remove copies already cloned elsewhere. Rotate credentials regardless.
Tools commonly used for this type of cleanup include git filter-repo and BFG Repo-Cleaner. The exact choice depends on repo size, hosting constraints, and whether the team needs to remove files, strings, or large objects.
A safe rule:
If the goal is fast containment and no secrets were committed, use cleanup PRs and preserve history.
If secrets or regulated content were committed, rotate credentials and plan a controlled history rewrite.
If feature branches are infected, recreate them from a clean base and cherry-pick safe work.
Step 6: Restore and verify protections
After cleanup:
Restore branch protection and rulesets.
Confirm push rulesets are active.
Confirm CODEOWNERS review is enforced.
Confirm bypass actors are limited.
Run drift scanner.
Run SIEM audit queries for new suspicious activity.
Confirm no new infected files appear.
Let’s not reopen normal merging just because files were deleted. Reopen only after the propagation path is contained.
How to protect ongoing work during cleanup
This is where many teams create unnecessary pain.
Developers may have legitimate work sitting on infected branches. Throwing everything away is safe, but expensive. Blind rebasing is risky.
The best approach is:
- Freeze the infected branch.
- Create a clean branch from a known-clean protected base.
- Extract only safe application changes.
- Exclude risky paths.
- Apply patch to the clean branch.
- Run tests and scanner.
- Open a fresh PR.
Practical command flow:
# On the infected branch
git diff origin/main...HEAD -- . ':(exclude).github/**' ':(exclude).claude/**' ':(exclude).gemini/**' ':(exclude).cursor/**' ':(exclude).vscode/tasks.json' ':(exclude).vscode/launch.json' > ../safe-feature-work.patch
# In a clean clone
git checkout origin/main
git checkout -b recover-feature-work
git apply --check ../safe-feature-work.patch
git apply ../safe-feature-work.patch
git status
git diff --stat
Then review the patch manually:
git diff
Run the normal test suite and the central scanner before opening the PR.
This keeps delivery moving without dragging the infection forward.
How AI can help without becoming another risk
AI is useful here, but only if it is placed behind deterministic controls.
Good uses of AI:
Summarize suspicious diffs for responders
Explain developer-machine impact
Identify whether a change can execute in CI, Docker, devcontainer, or local tooling
Generate cleanup PR descriptions
Draft incident timelines from audit logs
Suggest safe cherry-pick candidates
Review patches for accidental inclusion of blocked paths
Poor uses of AI:
Auto-approving PRs
Auto-revoking users or tokens without human approval
Receiving full repositories or secrets
Being the only detector
Mass-editing repositories without review
A safe AI prompt pattern:
We are reviewing a GitHub change for supply-chain risk.
Inputs:
- Repository:
- Actor:
- Branch:
- Commit:
- Changed files:
- Matched scanner rules:
- Diff excerpt:
Tasks:
1. Explain what changed in plain English.
2. Identify whether this can execute on a developer machine, CI runner, Docker build, devcontainer, or production runtime.
3. Identify whether it can access credentials or tokens.
4. Rate the change as normal, suspicious, or likely malicious.
5. Use only the provided evidence.
6. Recommend one action: allow, request owner review, block merge, or incident response.
AI should receive small diff excerpts, matched rules, and metadata. It should not receive .env files, private keys, customer data, or full proprietary repositories.
The value is speed and clarity. AI can remove the back-and-forth by turning raw diffs and audit logs into a concise triage note for SOC, platform, and engineering managers.
A clean incident timeline from detection to recovery
Here is the sequence I would use.
0–15 minutes: confirm and contain
Open incident channel.
Assign incident commander.
Freeze merges if protected branches or many repos are affected.
Disable suspicious token/app/user path.
Preserve audit logs.
Block high-risk paths with push ruleset if not already active.
15–60 minutes: scope
Export audit logs.
List affected repos and branches.
Identify actor/token/app/user agent/IP.
Search for follow-on pushes after protection changes.
Check CI workflow execution.
Check whether developer machines executed suspicious files.
1–4 hours: eradicate
Create cleanup PRs for affected repos.
Recreate feature branches from clean base where needed.
Rotate credentials if local or CI execution occurred.
Restore branch protection/rulesets.
Disable or re-scope risky OAuth apps, PATs, and GitHub Apps.
Same day: verify
Run drift scanner.
Run SIEM audit queries.
Confirm no new infected files.
Confirm no new branch protection changes.
Confirm no new mass repo modification.
Confirm endpoint findings are triaged.
Next 1–2 weeks: harden
Deploy central scanner.
Add CODEOWNERS for sensitive paths.
Tune SIEM rules.
Roll out local developer guardrails.
Review automation identities.
Document exception process.
Run tabletop or controlled simulation.
The prevention and cure in one view
| Area |
Prevention |
Cure |
| Developer Macs |
Local guardrails, targeted endpoint checks, short-lived credentials |
Isolate if executed, preserve evidence, rotate credentials, reclone clean |
| GitHub pushes |
Push rulesets for high-risk paths |
Block reinfection and remove malicious files by cleanup PR |
| Branches |
Branch rulesets and limited bypass |
Restore protections and review pushes after weakening |
| PR review |
CODEOWNERS for sensitive paths |
Route cleanup and risky changes to Platform/Security |
| Docker/devcontainer |
Scan and require Platform review |
Remove risky lifecycle commands and host mounts |
| CI/CD |
Workflow review and status checks |
Disable malicious workflow paths and preserve logs |
| Tokens/apps |
Least privilege and approval process |
Revoke, rotate, re-scope, and audit usage |
| SIEM |
Datadog or equivalent rules for GitHub audit logs |
Alert, correlate, and drive response |
| AI |
Summarize high-risk findings |
Draft cleanup notes and reduce responder back-and-forth |
Final thoughts
A GitHub supply-chain malware incident is not solved by deleting one file.
The clean answer is layered:
Developer machine safety
GitHub push rulesets
Branch rulesets
CODEOWNERS
Central push/PR scanner
SIEM detections
Targeted endpoint findings
Credential rotation
Careful branch recovery
AI-assisted triage
The most important mindset shift is this:
Treat GitHub as a production control plane.
Once the team sees it that way, the controls become obvious. Protect the developer machine. Protect the push. Protect the merge. Monitor the control plane. Keep cleanup evidence intact. Use AI to summarize and accelerate, not to blindly decide.
That is how a team can recover cleanly and make the next attack much harder.