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

PhaseActionPriority
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
RemediationRemove 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.

graph LR A["git push"] --> B["GitHub Commit Object Key visible in diff"] B --> C["Automated Bot Scan (seconds)"] C --> D["Key Captured"] D --> E["AWS API Calls with stolen key"] E --> F1["EC2 RunInstances (crypto mining)"] E --> F2["S3 GetObject (data exfiltration)"] E --> F3["IAM CreateUser (backdoor account)"] style A fill:#4a90d9,color:#fff style D fill:#d9534f,color:#fff style F1 fill:#e8a838,color:#fff style F2 fill:#e8a838,color:#fff style F3 fill:#e8a838,color:#fff
  1. Commit push — The key lands in the GitHub commit object, immediately visible in the diff.
  2. Bot scan — Automated scanners (both malicious and services like GitHub Secret Scanning) detect the AKIA pattern within seconds.
  3. 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.
  4. 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 creation
  • RunInstances, RequestSpotInstances — compute abuse (crypto mining)
  • GetObject, ListBuckets — S3 data exfiltration
  • CreateLoginProfile — 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.

stateDiagram-v2 [*] --> KeyActive : Access key exists in IAM KeyActive --> KeyDeactivated : aws iam update-access-key status Inactive KeyDeactivated --> [*] : aws iam delete-access-key KeyActive --> STSTokenIssued : sts AssumeRole called before deactivation STSTokenIssued --> STSTokenValid : Token valid until Expiration timestamp STSTokenValid --> SessionBlocked : Attach Deny-All inline policy to IAM user KeyDeactivated --> STSTokenValid : Existing tokens NOT invalidated by key deactivation SessionBlocked --> [*] : All API calls denied regardless of token validity
  1. Access key deactivated — No new STS tokens can be issued using this key.
  2. Existing STS tokens — Remain valid until their Expiration timestamp. This is the residual risk window.
  3. 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

TermDefinition
Access Key IDThe public identifier component of an IAM long-term credential pair, prefixed with AKIA.
Secret Access KeyThe private component of an IAM credential pair. Shown only once at creation time.
CloudTrailAWS 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.
GuardDutyAWS threat detection service that analyzes CloudTrail, VPC Flow Logs, and DNS logs for malicious or anomalous activity.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?