Skip to content

This tool is not affiliated with, endorsed by or sponsored by GitHub, Inc. or Microsoft Corporation. GitHub and GitHub Actions are trademarks of GitHub, Inc. Other names are trademarks of their respective owners.

GitHub supply chain attack investigation: a walkthrough

A fictional GitHub supply chain attack investigated end to end: stolen PAT, 38 repos cloned, AWS keys printed by a workflow, branch protection removed.

Published on 6 min read

TL;DR. This walkthrough uses the tool's fictional "Try a sample" incident (organization northwind-labs; people, repositories, tokens and IPs are all invented). A developer's classic PAT is replayed from a VPS in the Netherlands at 01:58 UTC. It clones 38 private repositories in about 18 minutes, pushes a workflow to a new branch that prints the AWS deploy keys double-base64'd, adds a write deploy key and removes branch protection on main. The analyzer returns Compromised with 10 findings. Below is how to read them and in what order to act.

Fictional scenario. Everything in this article comes from scripts/make-samples.py, which generates the sample served by the tool's "Try a sample" button. IP addresses are from documentation ranges (RFC 5737), and the AWS key pair is AWS's own documentation example. Any resemblance to a real organization is accidental.

The setup

northwind-labs is a small engineering organization: an owner (c-okafor) and three developers (m-hernandez, j-lindqvist, a-nowak) who mostly work from France and Sweden during office hours. The evidence is the three files an investigator would realistically collect, each in its real export format:

FileSourceContent
northwind-labs-audit-log.jsonOrganization audit log, UI JSON exportMember changes, workflow runs, secrets, keys, branch protection
export-northwind-labs-1789360000.json.gzGit events exportgit.clone / git.fetch / git.push with token metadata
northwind-labs-actions-run-4412.zip"Download log archive" of one runStep logs of the suspicious workflow run

The baseline covers three weeks (2026-08-24 to 2026-09-13). That matters, because the new-country and new-IP rules need history to compare against.

Step 1: the verdict

Drop the three files (or click Try a sample). The banner reads Compromised. Under Why, four findings drove it: actions-secret-exfil (critical), and token-new-country, git-clone-burst and branch-protection-removed (high). The coverage notes are empty. IPs, Git events and token metadata are all present, so none of the rules was blind.

The complete list, as the analyzer reports it:

SeverityRuleWhen (UTC, 2026-09-14 unless noted)What
criticalactions-secret-exfil02:40:08Step script pipes AWS keys through base64 twice. Next hop: AWS
hightoken-new-country01:58:12 → 03:06:3043 events from NL for a token only ever seen in FR
highgit-clone-burst02:14:00 → 02:31:5338 distinct repositories cloned from 203.0.113.66
highbranch-protection-removed03:05:44main on payments-api
mediumactions-encoded-output02:40:08Long base64 blob printed in the same step
mediumdeploy-key-added02:52:03backup-sync key on infra-terraform, write access
lowtoken-new-ip01:58:12 → 03:06:30Same 43 events, new IP
lowworkflow-new-branch02:39:40Workflow CI runs on ci/cache-fix for the first time
lowactions-secret-created2026-09-08a-nowak created the AWS secrets, legitimate
lowwebhook-created2026-09-03a-nowak created a CI webhook, legitimate

The last two are the honest noise of any real organization: routine admin work that the rules surface because it could matter. They share no actor, token or IP with the rest, so you can set them aside.

Step 2: which credential?

Open token-new-country. Its entities are the actor m-hernandez, one hashed_token and the IP 203.0.113.66. Pivot on the token (Entities → Tokens → Show events). It is a Personal access token (classic) with scopes repo, workflow, read:org. For three weeks it was used from 198.51.100.23 (FR) with the user agent git/2.39.5 (Apple Git-154), during the day. From 01:58 UTC on 2026-09-14 it shows up from 203.0.113.66 (NL) with git/2.43.0, and later with curl/8.5.0.

Same person, same token, different machine, different country, middle of the night: the token was replayed. The workflow scope explains how the attacker could push a workflow file. In the scenario, the source is an infostealer on the developer's laptop. The GitHub logs cannot show that part, which is why the leaked token playbook insists on checking the endpoint.

Step 3: what did it take?

git-clone-burst counts 38 distinct repositories. In the Git events, the clones run from 02:14:00 to 02:31:53, one roughly every 29 seconds. That regular rate is scripted. m-hernandez normally touches four repositories (payments-api, billing-worker, ledger, web-app). The burst includes infra-terraform, secrets-rotation, kyc-service and terraform-modules, repositories they never pushed to.

Conclusion for the report: the source code and full history of 38 repositories are in the attacker's hands. The remediation item "Treat cloned code as leaked" follows from it: scan those histories for secrets and rotate what you find. The repository exfiltration article covers that scoping.

Step 4: what did it change?

Read the timeline with Findings only:

  1. 02:39:21 git.push to payments-api from the VPS.
  2. 02:39:40 workflows.created_workflow_run: workflow CI, event push, branch ci/cache-fix. This is the first run on that branch, so workflow-new-branch fires.
  3. 02:40:08 In run log archive 4412, step Restore build cache runs echo "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" | base64 -w0 | base64 -w0 and prints a long blob. The mask did not apply because the value was encoded. Decoded twice, it is the key pair (here, AWS's documentation example). actions-secret-exfil fires as critical with the AWS next hop, and actions-encoded-output fires on the blob.
  4. 02:52:03 public_key.create on infra-terraform: title backup-sync, read_only: false, user agent curl/8.5.0. A write deploy key is persistence that survives the PAT's revocation.
  5. 03:05:44 protected_branch.destroy on payments-api / main. The rule required two approving reviews.
  6. 03:06:30 git.push to payments-api, one minute after protection came off.
  7. 07:48:10 protected_branch.create by c-okafor from the office: the owner noticed and restored the rule.

Step 6 is the one that should worry you most. Something was pushed to main without review. The audit log does not contain the diff, so the commit must be reviewed in the repository (see branch protection tampering).

Step 5: remediation, in the tool's order

The Remediation tab orders the checklist by urgency:

  1. Preserve the evidence: re-export the audit log and Git events and keep the run log archive, before anyone deletes run 4412's logs.
  2. Rotate every Actions secret in reach: all secrets readable by workflows in payments-api, not only the two AWS ones.
  3. Rotate cloud credentials and investigate the cloud: deactivate the AWS key and search CloudTrail from 02:40 UTC. See the cloud pivot and AWS Forensics.
  4. Review workflow changes and runs: diff .github/workflows on ci/cache-fix, delete the branch, and require CODEOWNERS review for workflow files.
  5. Revoke the token and reset the account, after re-imaging the laptop.
  6. Treat cloned code as leaked: scan the 38 repositories' history.
  7. Restore branch protection and review what landed: the 03:06 push.
  8. Remove persistence: delete the backup-sync deploy key and look for anything else created from 203.0.113.66.

What this sample does not show

Real incidents are messier. There is no owner takeover here, no self-hosted runner and no audit-stream removal. The attacker did not spread activity over days to stay under the thresholds either. If a rule did not fire on your data, check the coverage notes and what the audit log does not record before you conclude.

Related articles

Rogue or compromised GitHub self-hosted runners: the audit log events to check, host evidence in _diag, and how to scope and rebuild after an incident.
How GitHub Actions secrets leak despite *** masking: poisoned workflows, new branches, encoded output and third-party actions. The evidence to collect and read.
Analyze GitHub audit log exports offline: drop JSON, Git events and Actions logs, read the verdict, triage findings, pivot on tokens and IPs, export a report.

This tool is not affiliated with, endorsed by or sponsored by GitHub, Inc. or Microsoft Corporation. GitHub and GitHub Actions are trademarks of GitHub, Inc. Other names are trademarks of their respective owners.