I Accidentally Committed My AWS Access Key to GitHub — Here's What to Do Immediately
Accidentally committing an AWS access key to a public GitHub repository is one of the most time-sensitive security incidents an engineer can face. Automated bots scan GitHub commits within seconds of a push, and a leaked IAM access key can result in unauthorized resource creation, data exfiltration, or significant billing charges before you even notice the breach. This guide walks through the exact remediation steps for managing IAM access keys after an exposure event.
TL;DR — IAM Access Key Exposure Response
| Phase | Action | Priority |
|---|---|---|
| Immediate (0–5 min) | Deactivate the exposed key via IAM console or CLI | 🔴 Critical |
| Immediate (0–5 min) | Check CloudTrail for unauthorized API calls | 🔴 Critical |
| Short-term (5–30 min) | Revoke active sessions, rotate credentials | 🟠 High |
| Short-term (5–30 min) | Audit IAM policies attached to the compromised key | 🟠 High |
| Remediation | Remove key from git history, enable GuardDuty | 🟡 Medium |
How AWS IAM Access Keys Work — and Why Exposure Is Severe
An IAM access key consists of two components: an Access Key ID (prefixed AKIA for long-term keys) and a Secret Access Key. Together they act as a username and password for programmatic AWS API calls. Unlike a console password, access keys carry no MFA requirement by default and are valid globally — any actor with the key pair can call AWS APIs from anywhere in the world, inheriting every permission attached to the associated IAM user or role. Deleting the key from your git history does not invalidate it; the key remains active in AWS until you explicitly deactivate or delete it.
- Commit push — The key lands in the GitHub commit object, immediately visible in the diff.
- Bot scan — Automated scanners (both malicious and services like GitHub Secret Scanning) detect the
AKIApattern within seconds. - API abuse window — An attacker uses the key to call AWS APIs, potentially spinning up EC2 instances, exfiltrating S3 data, or creating backdoor IAM users.
- Detection lag — Without GuardDuty or CloudTrail alerting, you may not notice until a billing anomaly surfaces.
Step 1: Deactivate the Exposed IAM Access Key Immediately
Before anything else — before rewriting git history, before filing a ticket — deactivate the key. Deactivation is reversible (unlike deletion), which preserves your ability to audit which calls were made with it while stopping any further use. This is the single action that closes the active attack window.
# Deactivate the key — replace AKIAIOSFODNN7EXAMPLE with your actual Key ID
aws iam update-access-key \
--access-key-id AKIAIOSFODNN7EXAMPLE \
--status Inactive \
--user-name your-iam-username
Verify the key is now inactive:
aws iam list-access-keys \
--user-name your-iam-username
Confirm the Status field shows Inactive in the response. If you do not know which IAM user owns the key, use the STS get-caller-identity call — but only if you still have a separate, uncompromised credential available:
aws sts get-caller-identity
Deactivating a key is like changing the locks on a door without removing the door. The attacker's copy of the key no longer works, but your audit trail of what that key did remains intact.
Step 2: Investigate Unauthorized Activity via CloudTrail
Deactivation stops the bleeding, but you need to know whether the key was used before you caught it. CloudTrail records management events (API calls) by default in most accounts, but the retention window and log delivery configuration vary. Check immediately — this is where the real damage assessment happens, and it's the step most engineers skip in the panic of the moment.
# Look up API calls made by the compromised access key in the last 24 hours
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
--start-time $(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ) \
--region us-east-1
On macOS, replace the date command with:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
--start-time $(date -u -v-24H +%Y-%m-%dT%H:%M:%SZ) \
--region us-east-1
Scan the output for these high-risk API calls specifically:
CreateUser,CreateAccessKey,AttachUserPolicy— backdoor IAM user creationRunInstances,RequestSpotInstances— compute abuse (crypto mining)GetObject,ListBuckets— S3 data exfiltrationCreateLoginProfile— console access granted to a new user
The absence of CloudTrail events does not guarantee no abuse occurred — if CloudTrail was not enabled in a region where the attacker operated, those calls are invisible to this lookup. Check all regions.
Step 3: Audit IAM Permissions Attached to the Compromised Identity
The blast radius of the exposure is bounded by what the compromised IAM user was allowed to do. An attacker who obtained a key attached to AdministratorAccess has a fundamentally different impact than one who got a read-only S3 key. You need this picture before you can scope the incident response.
# List all policies directly attached to the user
aws iam list-attached-user-policies \
--user-name your-iam-username
# List inline policies
aws iam list-user-policies \
--user-name your-iam-username
# List groups the user belongs to (inherited permissions)
aws iam list-groups-for-user \
--user-name your-iam-username
If the user had broad permissions, treat this as a full account compromise and escalate accordingly — check for newly created IAM users, roles, or access keys that weren't there before.
# Check for recently created IAM users (potential backdoors)
aws iam list-users \
--query 'Users[?CreateDate>=`2024-01-01`].[UserName,CreateDate]' \
--output table
Adjust the date filter to cover the window from your commit timestamp to now.
Step 4: Revoke Active Sessions and Rotate All Credentials
Even after deactivating the key, an attacker who called sts:AssumeRole or obtained temporary credentials may still have active sessions. Temporary credentials issued before key deactivation remain valid until their natural expiration — deactivating the originating access key does not invalidate already-issued STS tokens.
- Access key deactivated — No new STS tokens can be issued using this key.
- Existing STS tokens — Remain valid until their
Expirationtimestamp. This is the residual risk window. - Policy denial — Attaching an explicit deny policy to the IAM user immediately blocks all active sessions, including those backed by still-valid STS tokens.
To immediately invalidate all active sessions for the compromised user, attach an inline deny-all policy:
🔽 Click to expand — Inline deny-all policy JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*"
}
]
}
# Apply the deny-all policy to immediately block all sessions
aws iam put-user-policy \
--user-name your-iam-username \
--policy-name EmergencyDenyAll \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*"
}
]
}'
After confirming no legitimate workloads depend on this user, delete the compromised key entirely and create a new one for any legitimate use cases:
# Delete the compromised key permanently
aws iam delete-access-key \
--access-key-id AKIAIOSFODNN7EXAMPLE \
--user-name your-iam-username
# Create a new key only if this user still has a legitimate purpose
aws iam create-access-key \
--user-name your-iam-username
Step 5: Remove the Key from Git History
Deleting the file or adding a new commit that removes the key is insufficient — the secret remains in the git object store and is visible in the commit history. This step matters for compliance and to prevent future accidental re-exposure, but it does not affect the AWS security posture (that was handled in Steps 1–4).
GitHub's official recommendation is to use git-filter-repo (the successor to BFG Repo Cleaner and git filter-branch). If the repository is public, assume the key has already been captured — history rewriting prevents future exposure but cannot undo past exposure.
# Install git-filter-repo (pip install git-filter-repo)
# Then remove the specific file containing the key from all history
git filter-repo --path path/to/file-with-key --invert-paths
After rewriting history, force-push to all branches and notify collaborators that they must re-clone the repository. Also contact GitHub Support to clear cached views of the exposed commit if the repository is public.
The Misdiagnosis Engineers Make — and the Actual Risk Model
The most common mistake: an engineer finds the commit, deletes the file, pushes a new commit, and considers the incident closed. The reasoning is intuitive — "the key is gone from the repo." But this conflates git history with AWS credential state. The key is still active in IAM. The attacker's copy is still valid. The new commit changes nothing about the AWS attack surface.
The second misdiagnosis: assuming that because the repository was private, the key is safe. Private repositories are not immune — compromised GitHub tokens, misconfigured third-party integrations, or a single collaborator's machine being compromised can expose the key. Treat any committed credential as fully public regardless of repository visibility.
The actual risk model: the moment a credential touches a git commit, its confidentiality guarantee is broken. The only safe response is immediate rotation in the credential system that issued it — in this case, AWS IAM.
Step 6: Enable Preventive Controls to Stop Recurrence
Remediation without prevention is incomplete. These controls address the structural conditions that allowed the exposure.
Enable AWS GuardDuty — GuardDuty includes findings for anomalous credential use, including UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration and related finding types. It operates on CloudTrail, VPC Flow Logs, and DNS logs.
aws guardduty create-detector \
--enable \
--region us-east-1
Enable GitHub Secret Scanning — For public repositories, GitHub automatically scans for known secret patterns including AWS access keys and can notify you or even trigger automatic remediation via AWS's partnership with GitHub. Enable it in your repository's Security settings.
Use pre-commit hooks locally — Tools like git-secrets (from AWS Labs) or detect-secrets scan staged changes for credential patterns before a commit is created.
# Install git-secrets (macOS via Homebrew)
brew install git-secrets
# Register AWS credential patterns and install hooks in the current repo
git secrets --register-aws
git secrets --install
Migrate to IAM roles and short-lived credentials — Long-term access keys (AKIA-prefixed) are the root cause here. Applications running on EC2, Lambda, ECS, or EKS should use IAM roles, which issue temporary credentials via the instance metadata service. There is no static secret to accidentally commit.
IAM Policy for Least-Privilege Key Management
If you need to grant a team member or automation the ability to rotate access keys without granting broader IAM permissions, scope the policy to key management actions on a specific user:
🔽 Click to expand — Least-privilege key rotation policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:UpdateAccessKey",
"iam:ListAccessKeys"
],
"Resource": "arn:aws:iam::123456789012:user/your-iam-username"
}
]
}
Wrap-Up: Managing IAM Access Keys After an Exposure Event
The response to a leaked AWS access key follows a strict sequence: deactivate first, investigate second, rotate third, remediate history fourth. Reversing this order — or skipping the CloudTrail audit — leaves you with an incomplete picture of what the attacker may have done during the exposure window.
The long-term fix is architectural: eliminate long-term access keys wherever possible in favor of IAM roles and temporary credentials. A secret that doesn't exist can't be leaked.
For further reading, see the AWS IAM Access Keys documentation and the AWS CloudTrail User Guide.
Glossary
| Term | Definition |
|---|---|
| Access Key ID | The public identifier component of an IAM long-term credential pair, prefixed with AKIA. |
| Secret Access Key | The private component of an IAM credential pair. Shown only once at creation time. |
| CloudTrail | AWS service that records API calls made in your account, including the identity, time, and source IP of each call. |
| STS (Security Token Service) | AWS service that issues temporary, limited-privilege credentials for IAM roles and federated identities. |
| GuardDuty | AWS threat detection service that analyzes CloudTrail, VPC Flow Logs, and DNS logs for malicious or anomalous activity. |
Comments
Post a Comment