Leaked cloud keys in GitHub Actions: the cloud pivot
AWS, Google Cloud or Azure keys leaked from GitHub Actions or a repo: contain the key, trace its use in the cloud audit logs, then replace keys with OIDC.
TL;DR. Once a cloud key leaves GitHub (printed by a workflow, cloned in a repository's history, found by secret scanning), the GitHub investigation becomes a cloud investigation. Deactivate the key, but first note its ID and the exact time window. Search the cloud's own logs for every call made with it: CloudTrail for AWS, Cloud Audit Logs for Google Cloud, Activity and Entra ID sign-in logs for Azure. Replace stored keys with OIDC federation so that the next workflow compromise yields a short-lived, narrowly scoped token instead.
GitHub is rarely the attacker's final target. Repositories and Actions secrets hold the credentials of the systems that matter: production cloud accounts, registries and databases. The analyzer marks this handoff as a next hop. When the actions-secret-exfil rule sees AWS, Google Cloud or Azure credential names in a script that encodes or ships secrets, or when a secret-scanning alert concerns a cloud credential, the result links to the relevant cloud tool.
Where cloud keys leak from GitHub
| Source | Evidence on the GitHub side | Covered in |
|---|---|---|
| Workflow prints or sends secrets | Run log archive: encoder or curl in the step script, long encoded blob in the output | Actions secrets leak |
| Key committed to a repository, then cloned | git.clone bursts, secret-scanning alert, push protection bypass | Repository exfiltration |
| Environment protection removed | environment.remove_protection_rule before a run that used the environment | Branch protection tampering |
| Self-hosted runner compromised | Runner host with an instance role or cached CLI credentials | Self-hosted runner security |
Secret-scanning alerts (secret_scanning_alert.create, secret_scanning_alert.public_leak) and push protection bypasses (secret_scanning_push_protection.bypass) are raised by the rules secret-leak-alert and push-protection-bypass. Drop the organization's secret-scanning alerts JSON next to the audit log to include them.
First: pin down the key and the window
Before you rotate, record:
- The key identifier. For AWS, the access key ID (
AKIA…for long-term user keys,ASIA…for temporary credentials). For Google Cloud, the service account email and key ID in the JSON. For Azure, the application (client) ID of the service principal and the secret or certificate ID. - The exposure start. The time of the malicious run or push, the clone, or the alert. The analyzer's finding gives the first-seen time in UTC.
- Where it was valid. Which account, project or subscription, and which permissions it had.
Then deactivate or delete the key. In AWS, deactivating an access key lets you reactivate it if production breaks. Once you have confirmed nothing legitimate still depends on it, delete it.
AWS: CloudTrail
Search CloudTrail for the access key ID from the exposure start onwards. Event history and CloudTrail Lake can filter on the access key. Look for:
- Identity checks that attackers typically run first (
GetCallerIdentity), followed by enumeration (List*,Describe*,Get*in bursts). - Calls from IPs and user agents that do not match your CI (GitHub-hosted runners come from GitHub's published ranges. A laptop's
aws-clifrom a VPS is not them). - Persistence:
CreateUser,CreateAccessKey,CreateLoginProfile,AttachUserPolicy, new roles or trust-policy changes. - Data access: S3
GetObjectat volume, Secrets Manager or SSM Parameter Store reads.
The sibling AWS Forensics tool analyses CloudTrail exports the same way this site analyses GitHub: in the browser, nothing uploaded.
Google Cloud: Cloud Audit Logs
For a leaked service account key, filter Cloud Audit Logs on the service account as principal and look at Admin Activity and, where enabled, Data Access logs. Watch for new service account keys, IAM policy bindings, and access to Cloud Storage or Secret Manager. Continue with GCP Forensics.
Azure: Activity log and Entra ID
For a service principal secret, check the Entra ID service principal sign-in logs for the application ID, then the Activity log for what it changed: role assignments, new credentials on applications, and Key Vault access. Continue with Azure Forensics.
Replace keys with OIDC federation
Most of these incidents exist because a long-lived key sits in a GitHub secret. GitHub Actions can request a short-lived OIDC token for each job, and AWS, Google Cloud and Azure can trust it directly. See GitHub's OpenID Connect documentation and the OIDC federation glossary entry. GitHub's secure use reference recommends it for cloud deployments.
What changes for an attacker:
| Stored access key | OIDC federation | |
|---|---|---|
| Lifetime | Until someone rotates it | Minutes to an hour (cloud-side session) |
| Usable outside the workflow | Yes, from anywhere | Only until it expires |
| Scope | Whatever the key's user has | A role whose trust policy can require a specific repository, branch or environment (sub claim) |
| Visible in logs | Key ID | Role session tied to the GitHub claims |
OIDC is not a cure-all. A poisoned workflow running on an allowed branch still gets a valid token during the run. But the window shrinks from "until rotation" to "while the job runs", and trust conditions such as repo:ORG/REPO:environment:production block tokens requested from attacker branches.
Checklist
- Record the key ID, the account and the exposure window. Then deactivate the key.
- Pull the cloud logs for the window. Look for identity checks, enumeration, persistence and data access.
- Rotate every other secret the same workflow could read (see the Actions article).
- Remove what the attacker created in the cloud: users, keys, roles, functions, instances.
- Move deployments to OIDC with trust conditions on repository and environment.
Related
- GitHub organization compromised? Incident response guide
- The fictional northwind-labs walkthrough: an AWS key pair printed double-base64
- MITRE ATT&CK T1552.001 Credentials In Files