A stolen AWS access key works from anywhere on the internet, so the first sign of theft is usually the same key calling AWS from a network it has never used before. Long-term IAM user keys (the ones beginning AKIA) are typically used by a small, stable set of hosts: a CI runner, an on-premises server, a vendor integration. That stability is a detection opportunity, because every CloudTrail record carries the caller's sourceIPAddress and userAgent. This guide shows how to turn that into working detection: which built-in signals AWS gives you and what they miss, how to build a per-key baseline in Athena, which rules to alert on, and how to add an IP guardrail so an unusual location is blocked rather than only reported. It is written for cloud security engineers and detection engineers. Scoping a confirmed compromise is covered in How to Investigate CloudTrail for a Compromised Access Key, and replacing the key in How to rotate an AWS IAM access key without downtime.
This is an independent guide from NHI Mgmt Group, based on AWS's public CloudTrail, IAM and GuardDuty documentation as of November 2026. It is not sponsored or reviewed by AWS. Product features change, so confirm settings against AWS's current documentation.
Key takeaways
- Location means network origin, not just country. For a machine key the useful questions are: is this IP in the known egress range, is the network owner (ASN) new, and is it a cloud or hosting range the key has never used. Country is a weak signal on its own.
- GuardDuty covers part of the problem. Its IAM findings flag Tor exit nodes, known malicious IPs, pentest distributions and anomalous API use, and the
InstanceCredentialExfiltrationandResourceCredentialExfiltrationfindings catch role credentials used outside their home. It does not alert simply because an IAM user key appeared on a new, clean IP. - Your own baseline fills the gap. A per-key list of observed
sourceIPAddress, ASN anduserAgentvalues from 30 to 90 days of CloudTrail makes "first seen" rules cheap and precise for keys with fixed origins. - Follow the children. An attacker with an
AKIAkey often callsAssumeRoleorGetSessionTokenand works with the resultingASIAcredentials, so baseline and alert on the sessions a key creates, not only the key itself. - Prevent where origins are fixed. An identity policy that denies requests when
aws:SourceIpis outside your egress ranges turns a stolen key into a stream ofAccessDeniedevents, which are themselves a high-quality alert.
When you need this
Use this guide when you still run long-term IAM user access keys and cannot remove them yet, for example for on-premises systems, third-party SaaS integrations that only accept a key pair, or legacy tooling. The better end state for most workloads is no long-term key at all: roles for compute in AWS, IAM Roles Anywhere for on-premises servers with certificates, and OIDC federation for CI. See the AWS Access Keys Security Guide for that migration. Until then, unusual-location detection is one of the few signals that separates the legitimate holder of a key from someone who copied it out of a repository, a .env file or a build log.
The technique being detected is use of valid cloud credentials, MITRE ATT&CK T1078.004 Valid Accounts: Cloud Accounts, usually after collection through T1552.001 Credentials In Files or T1552.005 Cloud Instance Metadata API. Attackers who expect location checks route through proxies, Tor or cloud hosts (T1090.003 Multi-hop Proxy), which is why the network owner matters as much as the country.
Signals AWS gives you, and what they miss
| Signal | What it catches | What it misses |
|---|---|---|
GuardDuty UnauthorizedAccess:IAMUser/TorIPCaller and Recon:IAMUser/TorIPCaller | Any API call (or list and describe calls for the Recon variant) from a Tor exit node | Residential proxies, VPNs and rented cloud hosts |
GuardDuty UnauthorizedAccess:IAMUser/MaliciousIPCaller and .Custom | Calls from IPs on AWS threat intelligence lists, or on a threat list you upload | Clean IPs; these findings use reputation lists, not machine learning |
GuardDuty *:IAMUser/AnomalousBehavior (Discovery, Persistence, Exfiltration and others) | Sensitive APIs invoked in an anomalous way; AWS says the model tracks the user, the location the request came from and the API | Use of routine APIs from a new location; a finding needs an anomalous API pattern, not only a new IP |
GuardDuty CredentialAccess:IAMUser/CompromisedCredentials | A key Amazon threat intelligence identifies as potentially compromised | Keys that leaked somewhere AWS has not seen |
GuardDuty InstanceCredentialExfiltration.OutsideAWS / .InsideAWS and ResourceCredentialExfiltration | EC2 role credentials, or ECS task and Lambda credentials, used from an external IP or another AWS account | IAM user keys (AKIA); these findings are for role credentials of compute |
GuardDuty UnauthorizedAccess:IAMUser/ConsoleLoginSuccess.B | Successful console logins for one user from several geographic locations around the same time | Programmatic key use; this finding is for console sign-in |
| CloudTrail Insights | Unusual API call rates and API error rates against a learned baseline | Location; Insights does not look at where calls come from |
IAM GetAccessKeyLastUsed | Last used date, service and Region per key | The IP address; useful for inventory, not for location detection |
The gap is clear: a key used from a clean, never-seen IP to call ordinary APIs can pass every built-in control. That is the case your own baseline rules cover. See the Amazon GuardDuty Guide for enabling GuardDuty across an organisation.
Before you start
- Organisation trail: an AWS Organizations trail covering all Regions and all accounts, delivering to a central log archive bucket. IAM and global STS calls are recorded in us-east-1, so a trail that skips that Region misses key creation events. See the AWS CloudTrail Guide.
- Data events where it matters: management events show
ListBucketsorGetCallerIdentity, butGetObjecton sensitive buckets only appears if S3 data events are enabled for them. - A query engine: an Athena table over the trail bucket (created from the CloudTrail console) or the same data in your SIEM. The examples below use Athena column names (
useridentity.accesskeyid,sourceipaddress,useragent). - IP enrichment: a GeoIP and ASN data source in the SIEM, and AWS's published
ip-ranges.jsonso you can label addresses that belong to AWS itself. - A key register: every active
AKIAkey, its IAM user, owner, the system that uses it and the networks it should come from. Keys with no owner are a finding in their own right; see How to find unused AWS IAM roles and access keys.
Step-by-step: build the detection
Step 1: Classify each key by its expected origin
Put every long-term key into one of three classes. Fixed origin: the key is used only from known egress IPs (a data centre NAT range, a fixed CI runner pool, a vendor that publishes its egress list). AWS-hosted origin: the key is used from inside AWS, ideally through a VPC endpoint (and should really be a role). Variable origin: a human developer key used from home and travel networks. Fixed-origin keys get strict allow-list rules and a guardrail policy; variable-origin keys get softer anomaly rules and a plan to replace them with IAM Identity Center sessions.
Step 2: Build a per-key baseline from CloudTrail
Take 30 to 90 days of history and list, per key, the distinct source addresses and user agents with counts and first and last seen times. In Athena:
SELECT useridentity.accesskeyid AS key_id,
useridentity.username AS iam_user,
sourceipaddress, useragent,
count(*) AS calls,
min(eventtime) AS first_seen, max(eventtime) AS last_seen
FROM cloudtrail_logs
WHERE useridentity.accesskeyid LIKE 'AKIA%'
AND eventtime > '2026-08-01'
GROUP BY 1, 2, 3, 4
ORDER BY key_id, calls DESC;
Enrich each address with ASN, country and an "AWS-owned" flag, then review the result with the key owner. Expect a short list for most machine keys. Anything the owner cannot explain is a lead to investigate now, before you switch on alerting. Store the approved values as a lookup table keyed on access key ID.
Two values need care. When an AWS service makes a call for you, AWS documents that sourceIPAddress shows the service DNS name (for example ec2.amazonaws.com) or AWS Internal/#, and userIdentity.invokedBy names the service. Exclude these from location logic rather than treating them as new locations. Calls made through a VPC endpoint carry vpcEndpointId and vpcEndpointAccountId; record those in the baseline too, because a key that normally arrives through your endpoint and suddenly arrives from the internet has changed location even if the country is the same.
Step 3: Include the sessions each key creates
A long-term key that calls AssumeRole, GetSessionToken or GetFederationToken receives a temporary ASIA key, recorded in responseElements of the STS event. Later calls show the ASIA key, not the original. Join the two so that activity from the child session is attributed back to the parent key and checked against the same baseline. The AWS STS and AssumeRole Guide explains the session fields.
Step 4: Write the alert rules
| Rule | Logic | Use for | Suggested priority |
|---|---|---|---|
| Off-list source | Key in the fixed-origin class, sourceipaddress not in its approved CIDRs, not a service DNS name or AWS Internal | Fixed-origin keys | High, page on first event |
| First-seen ASN | Network owner never seen for this key in the baseline window | All keys | Medium; High if the ASN is a hosting or VPN provider |
| Endpoint to internet switch | Key normally seen with vpcendpointid set, now seen without it | AWS-hosted keys | High |
| Concurrent distant use | Same key from two ASNs or countries within a short window (for example 30 minutes) where travel is impossible | Variable-origin keys | High |
| New user agent with new IP | useragent family (SDK, CLI, tool name) and IP both first seen for the key | All keys | Medium |
| Validation call from new IP | GetCallerIdentity or ListBuckets as the first call from a first-seen IP | All keys | High |
| Denied burst from new IP | Several AccessDenied or UnauthorizedOperation errors from a first-seen IP in a short window | All keys | High |
The validation-call rule matters because key testers start by checking that a key works. In the TruffleNet campaign, stolen keys were tested at scale with GetCallerIdentity before Amazon SES was abused. The time windows and priorities above are our suggested starting points; tune them to your environment. For general impossible-travel logic and its false positives, see Impossible Travel and Anomalous Sign-In Detection.
Step 5: Route GuardDuty findings into the same queue
GuardDuty findings are published to Amazon EventBridge with source aws.guardduty and detail type GuardDuty Finding. Match the IAM finding types in the table above and send them to the same triage queue as your baseline rules, so one key produces one case. Upload your own list of known-bad addresses as a GuardDuty threat list to get the MaliciousIPCaller.Custom findings, and add your approved egress ranges as a trusted IP list where that fits your setup.
Step 6: Add an IP guardrail for fixed-origin keys
For keys with fixed origins, detection alone leaves the attacker a window. AWS documents an identity policy pattern that denies all actions when the request does not come from listed ranges: "Effect": "Deny", "Action": "*", with a NotIpAddress condition on aws:SourceIp. Attach it to the IAM user (or its group) alongside the policies that grant access. Read the AWS caveats before you deploy it:
- Public addresses only:
aws:SourceIpcan only be used for public IP ranges. - VPC endpoints: the key is not present when the request comes through a VPC endpoint, so a key used from inside a VPC needs
aws:SourceVpce,aws:SourceVpcoraws:VpcSourceIpconditions instead. - Calls by AWS services: for service-to-service calls AWS redacts some network context, and recommends excluding service principals from
Denystatements withaws:PrincipalIsAWSServiceset tofalse. AWS notes that its example policy does not deny requests made by services through forward access sessions, because the original caller's IP is preserved.
Test the policy with the IAM policy simulator and then from a non-approved address in a sandbox account. Roll back by detaching the policy; nothing else changes. At organisation scale, the network perimeter conditions in the AWS Resource Control Policies and Data Perimeter Guide apply the same idea through SCPs and RCPs.
Step 7: Triage and respond
- Confirm with the key owner whether the system moved (new NAT, new CI pool, vendor change). Update the baseline if it did, and record who approved it.
- If the owner cannot confirm, deactivate the key (do not delete it, so evidence and attribution remain) and revoke sessions it created. The Cloud Access Key Leak Playbook covers containment.
- Scope what the key and its child sessions did from the unusual address, including persistence such as
CreateAccessKey(T1098.001 Additional Cloud Credentials), using the CloudTrail investigation how-to linked above. - Find where the key leaked from, then replace it, preferably with a role or federation rather than a new long-term key.
Decision points
| Situation | Choose | Why |
|---|---|---|
| Few keys, small team, no SIEM | GuardDuty plus the aws:SourceIp deny policy on each fixed-origin key, and a weekly Athena baseline review | Prevention needs no 24x7 monitoring; denied calls show up in GuardDuty and CloudTrail |
| Many accounts, central SOC | Baseline lookup tables in the SIEM, rules from Step 4, GuardDuty delegated administrator | Consistent rules across hundreds of keys, one case per key |
| Vendor holds the key | Ask for the vendor's egress list and apply the deny policy; alert on any off-list use | Third-party breaches are a common source of leaked keys, and you cannot monitor their hosts |
| Developer laptop keys | Replace with IAM Identity Center; meanwhile, first-seen ASN and concurrent-use rules only | Home and travel networks make allow-lists unworkable and produce noise |
| Key used from inside AWS | Convert to a role; if not yet possible, require the VPC endpoint with aws:SourceVpce | Role credentials get GuardDuty's credential exfiltration findings for free |
Verify it worked
- Controlled test: in a sandbox account, create a test user and key, add it to the baseline with one approved address, then call
sts get-caller-identityfrom a different network. Confirm the off-list and validation-call rules fire and reach the queue. - Guardrail test: with the deny policy attached, confirm the call from the non-approved address returns
AccessDeniedand that the denied-burst rule fires. - Service path test: exercise a workflow where AWS calls another service for you (for example a CloudFormation stack) and confirm it is neither blocked nor alerted as a new location.
- GuardDuty path: generate sample findings in GuardDuty and confirm the EventBridge rule delivers them.
Common failure patterns
- Relying on GuardDuty alone. A key abused from a clean hosting IP to call routine APIs may never meet a finding's threshold. In the AWS crypto-mining campaign, compromised IAM credentials had miners running within minutes, so a daily review is too slow.
- Treating AWS Internal and service DNS names as new locations. This floods the queue and teaches analysts to ignore the rule. Filter on
invokedByand the documented internal values first. - Watching the AKIA key but not its ASIA sessions. The attacker's real activity runs under the temporary key, and the parent key shows only one STS call.
- Country-only rules. Attackers rent cloud hosts in your own country. In LLMjacking cases, stolen keys were used to test and resell model access, activity that looks ordinary unless the network owner is checked.
- Allow-lists with no owner. Approved ranges drift when NAT gateways or vendors change. Each list needs an owner and a review date, or the guardrail breaks production and gets removed.
- No data events on the buckets that matter. In the Codefinger S3 ransomware attacks, stolen keys encrypted objects with
CopyObject; without S3 data events those calls never reach your location rules.
Measuring it
- Baseline coverage: percentage of active
AKIAkeys with an owner, origin class and approved values (target 100%). - Guardrail coverage: percentage of fixed-origin keys with an
aws:SourceIpor VPC endpoint deny policy attached. - Time to alert: median minutes from an off-list call in CloudTrail to a case in the queue, measured with the controlled test.
- False positive rate: share of location alerts closed as legitimate change; a rising rate means baselines are stale.
- Long-term key count: number of active
AKIAkeys, falling quarter on quarter as roles and federation replace them.
Framework mapping
| Framework | Reference | How this guide applies |
|---|---|---|
| NIST SP 800-53 Rev 5 | AC-2(12) Account Monitoring for Atypical Usage | Per-key baselines and first-seen location rules |
| NIST SP 800-53 Rev 5 | SI-4 System Monitoring; AU-6 Audit Record Review, Analysis, and Reporting | CloudTrail queries, GuardDuty routing and triage |
| NIST SP 800-53 Rev 5 | AC-3 Access Enforcement; IA-5 Authenticator Management | aws:SourceIp guardrail; key register and replacement |
| NIST CSF 2.0 | DE.CM-03; DE.AE-02 | Monitoring technology usage; analysing potentially adverse events |
| NIST CSF 2.0 | PR.AA-05 | Access permissions enforced with network conditions |
| MITRE ATT&CK v20 | T1078.004; T1552.001; T1552.005; T1090.003; T1098.001 | The behaviour being detected and the persistence to check for |
Practitioner checklist
- Organisation trail in all Regions, including us-east-1, with S3 data events on sensitive buckets.
- GuardDuty enabled organisation-wide with IAM findings routed through EventBridge to the SOC queue.
- Register of every active
AKIAkey with owner, system and origin class. - 30 to 90 day baseline of
sourceIPAddress, ASN,userAgentand VPC endpoint per key, reviewed with owners. - Service DNS names,
AWS InternalandinvokedBycalls excluded from location rules. - Child
ASIAsessions joined to their parent key. - Off-list, first-seen ASN, endpoint switch, validation-call and denied-burst rules live and tested.
aws:SourceIpor VPC endpoint deny policy on every fixed-origin key, with an owner for each allow-list.- Response steps agreed: confirm with owner, deactivate, scope, replace with a role or federation.
Standards and references
- AWS: GuardDuty IAM finding types
- AWS: CloudTrail record contents
- AWS: IAM global condition context keys
- AWS: Deny access to AWS based on the source IP
- AWS: Working with CloudTrail Insights
- AWS: Logging IAM and AWS STS API calls with CloudTrail
- MITRE ATT&CK: T1078.004 Valid Accounts: Cloud Accounts
- NIST: SP 800-53 Rev 5 Security and Privacy Controls
Related NHI Mgmt Group resources: AWS Access Keys Security Guide, How to Investigate CloudTrail for a Compromised Access Key, Cloud Access Key Leak Playbook, Amazon GuardDuty Guide and Impossible Travel and Anomalous Sign-In Detection.