Playbook: Identifying which AWS account owns an unknown/stale access key
Trigger
A stale or leaked AWS access key (AKIA...) needs to be revoked, but it’s unclear which
AWS account it belongs to, and GetAccessKeyLastUsed doesn’t return a UserName when
queried with credentials from an account you administer.
Root cause of the missing UserName
GetAccessKeyLastUsed works for any valid AWS access key ID globally — that’s by design,
so a leaked key found on GitHub can be checked for usage without needing permissions in the
owning account. But it only returns UserName in one specific case: the key belongs to the
AWS account’s root user, not an IAM user. Root isn’t an IAM “user,” so there’s no username to
resolve, regardless of which account is asking or what permissions it has. This is easy to
misread as “the key is in a different account” — it isn’t necessarily. Confirmed on #2047: a
key that decoded to our own automation account still returned no UserName, because it was a
root key on that same account.
How to confirm which account owns the key — no credentials needed
AWS access key IDs publicly encode their account ID in a documented, reversible way. The key ID itself is not secret (same sensitivity class as a username — the paired secret access key is the sensitive half), so this is safe to run against any key ID without touching credentials:
import base64
def get_account_id(access_key):
trimmed_key = access_key[4:] # strip AKIA/ASIA prefix
padded = trimmed_key + '=' * ((8 - len(trimmed_key) % 8) % 8)
x = base64.b32decode(padded)
y = x[0:6]
z = int.from_bytes(y, byteorder='big')
mask = int.from_bytes(bytes.fromhex('7fffffffff80'), byteorder='big')
e = (z & mask) >> 7
return e
print(get_account_id('AKIAYGQMT4PQ3ZLOWHRR')) # -> 563740926945Always validate the decode first against a key whose account you already know for certain
(e.g. aws sts get-caller-identity with credentials you hold) — confirm the algorithm recovers
the correct account ID before trusting it on the unknown key. Takes one extra line, costs nothing,
and catches any edge case in the (unofficial, reverse-engineered) technique before you act on it.
Decision tree once you know the account
- Decoded account ID matches an account you already have admin credentials for? → Don’t assume “solved, use IAM API.” Check whether the key is a root key first (see below) — if so, IAM API access in that account still can’t touch it.
- Is it a root key? Call
GetAccessKeyLastUsedwith credentials from that account — ifUserNameis absent, root key is the leading hypothesis (the alternative is an already-deleted IAM user, which is functionally equivalent to “already revoked,” just historically remembered). Confirm definitively by logging into the AWS console as root → top-right account menu → Security credentials → Access keys. If the key ID is listed there, it’s root’s. - Root key confirmed → console-only fix, no automation possible: root login → Security credentials → select the key → Actions → Deactivate (then delete once confirmed nothing depends on it). No IAM policy, however permissive, grants any other principal control over root’s own access keys — this is an AWS platform restriction, not a scoping gap to work around.
- Not listed under root → the key belongs to a deleted IAM user. Access keys don’t survive user deletion, so this means the key is already functionally dead. Safe to close out the investigation as resolved-by-attrition.
- Decoded account ID matches an account you have NO credentials for → genuinely a different,
unreachable account. Needs the account’s owner/root-email holder to be identified through other
means (billing history, old “Welcome to AWS” emails, password manager entries from around the
key’s
Created ondate).
Prevention
- Programmatic root access keys are a known AWS anti-pattern (the console itself surfaces a “best practices” link discouraging it) — if you find one during this process, it’s worth flagging for removal even outside the specific stale-key investigation that led you here.
- When multiple access keys show under root with one much older/staler than another (e.g. an abandoned rotation attempt — new key created, workload never cut over, old key never removed), clean up both while you’re already on the console page rather than filing a separate issue for the second one later.
Related
- #2047 — the incident that produced this playbook (
AKIAYGQMT4PQ3ZLOWHRR, confirmed root key on account563740926945, deactivated 2026-07-31) docs/playbooks/static-api-key-incident-rotation.md— general credential rotation/incident process once a key’s owning account and scope are known