How incident response differs on AWS
Incident response on AWS is faster to execute and easier to get wrong than on-premises response, because both the attacker and the responder work through the same APIs. Containment is an API call. So is destroying evidence.
Four differences drive the rest of this article:

  • Shared responsibility sets your scope. AWS secures the infrastructure. You respond to what happens with your identities, configurations, data and workloads.
  • Identity comes first. Most AWS incidents begin with valid credentials. Containing a principal usually matters more than containing a host.
  • Resources are short-lived. An instance in an Auto Scaling group or a Fargate task can disappear before you look at it. Evidence capture has to be prepared in advance and quick.
  • Everything can be automated. Any step you can run from the CLI can run from a Lambda function or a Systems Manager runbook, with the same permissions problem if you grant too much.

Two references frame the lifecycle. NIST SP 800-61 Revision 3, published in April 2025, replaced Revision 2 and maps incident response onto the six functions of the Cybersecurity Framework 2.0 (NIST). The AWS Security Incident Response Guide organises the work as preparation, operations and post-incident activity (AWS technical guide).
This article uses the operational phases responders actually work through:

  1. Preparation
  2. Detection and analysis
  3. Containment
  4. Forensic acquisition
  5. Eradication and recovery
  6. Post-incident activity Commands use AWS CLI v2. Resource names and IDs are placeholders. Rehearse every command in a sandbox account before you need it in production.

Preparation
Preparation decides the outcome: every minute spent creating a role or a bucket during an incident is a minute the attacker keeps access. Build the following before you need it.

incident response architecture · four accounts and the flows between them
A finding in a workload account triggers the workflow in the security account, which contains the resource through the incident response role; evidence goes to the forensics account and logs are queried from the log archive.

A trust policy for the incident response role, requiring MFA and a named source role:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/SOC-Responder" }, "Action": "sts:AssumeRole", "Condition": { "Bool": { "aws:MultiFactorAuthPresent": "true" } } } ] } 

Give the role read access to everything, plus a short list of write actions needed for containment: changing security groups, creating snapshots, deactivating access keys, attaching policies and tagging. Do not grant delete or terminate permissions.

AWS Security Incident Response
AWS Security Incident Response is a managed service worth enabling as part of preparation. It triages findings from GuardDuty and from third-party tools through Security Hub, provides a case console and gives 24/7 access to the AWS Customer Incident Response Team. Since 21 November 2025 it has used metered pricing with the first 10,000 ingested findings per month free. It also includes an AI investigative agent that gathers evidence from CloudTrail, IAM and EC2 and builds a timeline when a case opens.
Containment by the service is off by default. Decide in advance whether you want AWS to take containment actions for you and record that decision in your playbooks.

Detection and analysis
Analysis has one goal: establish scope quickly enough to contain the right things. Answer five questions in order, and start containment as soon as the first two are clear.

  1. Which principal or resource is involved? An access key, a role session, an instance, a bucket.
  2. Is the activity still going on? Look at the last 15 minutes before the last 15 days.
  3. What did it do? Separate read calls from calls that changed something.
  4. What else did it touch? Roles it assumed, resources it created, other accounts.
  5. How did it get in? Root cause can wait until after containment, but not until after recovery.

Set severity from impact and confidence, not from the alert's own label.

Scoping with the CLI
When you have nothing else, CloudTrail Event history answers the first questions. It covers management events for 90 days in one Region at a time.

aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \ --start-time 2026-11-01T00:00:00Z \ --query 'Events[].{time:EventTime,event:EventName,source:EventSource,user:Username}' \ --output table 

Scoping with Athena on Security Lake
For anything beyond one Region and one key, query Security Lake. Replace the Region in the table name.
Everything a credential did, with the changes first:

SELECT time_dt, cloud.region, api.service.name AS service, api.operation, src_endpoint.ip, http_request.user_agent, api.response.error FROM amazon_security_lake_glue_db_eu_west_1.amazon_security_lake_table_eu_west_1_cloud_trail_mgmt_2_0 WHERE time_dt > current_timestamp - interval '14' day AND actor.user.credential_uid = 'AKIAIOSFODNN7EXAMPLE' ORDER BY regexp_like(api.operation, '^(Get|List|Describe|Head)') ASC, time_dt ASC; Roles the credential assumed, so you can follow each session: SELECT time_dt, api.operation, src_endpoint.ip, api.request.data FROM amazon_security_lake_glue_db_eu_west_1.amazon_security_lake_table_eu_west_1_cloud_trail_mgmt_2_0 WHERE time_dt > current_timestamp - interval '14' day AND actor.user.credential_uid = 'AKIAIOSFODNN7EXAMPLE' AND api.operation LIKE 'AssumeRole%'; 

Other principals seen from the same source addresses:

SELECT actor.user.uid AS principal, count(*) AS calls, min(time_dt) AS first_seen, max(time_dt) AS last_seen FROM amazon_security_lake_glue_db_eu_west_1.amazon_security_lake_table_eu_west_1_cloud_trail_mgmt_2_0 WHERE time_dt > current_timestamp - interval '14' day AND src_endpoint.ip IN ('198.51.100.23', '203.0.113.77') GROUP BY actor.user.uid ORDER BY calls DESC; 

Amazon Detective gives the same pivots visually for a single principal or IP address. If you use AWS Security Incident Response, opening a case starts its investigative agent, which gathers the same evidence in parallel with your own work.

Build the timeline as you go
Record every fact in one timeline, in UTC +8, with its source. It becomes the incident report.

Containment
Contain by removing the attacker's access without destroying evidence: isolate, never terminate. The AWS guidance groups containment into three kinds and most incidents need more than one.

IAM user access key
Deactivate the key, then deny everything on the user in case other credentials exist.

aws iam update-access-key --user-name app-user \ --access-key-id AKIAIOSFODNN7EXAMPLE --status Inactive aws iam put-user-policy --user-name app-user --policy-name IR-DenyAll \ --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Deny","Action":"*","Resource":"*"}]}' 

Deactivate first and delete later. An inactive key can be re-enabled if the application breaks; a deleted key cannot.

IAM role sessions
Temporary credentials cannot be deleted, so deny every session issued before now.

aws iam put-role-policy --role-name AppRole --policy-name IR-RevokeOlderSessions \ --policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Deny", "Action": "*", "Resource": "*", "Condition": {"DateLessThan": {"aws:TokenIssueTime": "2026-11-13T05:00:00Z"}} }] }' 

This does not stop anyone who can still assume the role. Fix the trust policy or the upstream credential in the same step. Expect a short outage for legitimate workloads until they obtain fresh credentials.
EC2 instance
Tag, protect, remove from service, snapshot, then isolate.

I=i-0123456789abcdef0 aws ec2 create-tags --resources "$I" \ --tags Key=IR-Status,Value=Quarantined Key=IR-Case,Value=CASE-123 aws ec2 modify-instance-attribute --instance-id "$I" --disable-api-termination aws autoscaling detach-instances --instance-ids "$I" \ --auto-scaling-group-name web-asg --no-should-decrement-desired-capacity aws elbv2 deregister-targets --target-group-arn "$TG_ARN" --targets Id="$I" aws ec2 create-snapshots \ --instance-specification InstanceId="$I",ExcludeBootVolume=false \ --description "IR CASE-123 pre-isolation" 

aws ec2 modify-instance-attribute --instance-id "$I" --groups sg-0isolation0example
Changing security groups does not cut connections that are already tracked. If an established session must end immediately, add a network ACL deny entry for the instance's address as well. Capture memory before isolating if you need Systems Manager to reach the instance, and do not stop or reboot it.

S3 bucket

aws s3api put-public-access-block --bucket example-bucket \ --public-access-block-configuration \ BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true 

Save the current bucket policy to the evidence store, then replace it with one that denies every principal except the incident response role. Check for replication rules, event notifications and access points an attacker may have added.

Lambda function

aws lambda put-function-concurrency --function-name example-fn \ --reserved-concurrent-executions 0 

Setting concurrency to zero stops invocations while keeping code and configuration intact. Also check for function URLs, new triggers and new layers.

Containers

  • EKS: cordon the node with kubectl cordon, label the pod so a deny-all NetworkPolicy selects it, and remove it from its Service by changing its labels. Do not delete the pod.
  • ECS on Fargate: a task cannot be paused. Collect logs and runtime findings, then restrict its security group before you stop it.

Whole account
If you cannot scope the compromise, attach a quarantine SCP to the account that denies every action except those by the incident response role. It is blunt and causes an outage, so agree in advance who may authorise it.

Forensic acquisition
Collect evidence in order of volatility: memory first, then disk, then configuration and logs. Work on copies in the forensics account, and record a hash and a custody entry for everything you collect.

Memory
Memory holds what disk does not: running processes, injected code, network connections and keys. Capture it before isolating the instance, because Systems Manager needs network access to reach it. The alternative is an isolation security group that still allows HTTPS to your Systems Manager and S3 VPC endpoints.

Stage a capture tool such as AVML for Linux in an S3 bucket in advance, then run it with Run Command. Put the commands in a file to avoid shell quoting problems:

{ "commands": [ "aws s3 cp s3://ir-tools-444455556666/avml /tmp/avml", "chmod +x /tmp/avml", "/tmp/avml /tmp/mem.lime", "sha256sum /tmp/mem.lime | tee /tmp/mem.lime.sha256", "aws s3 cp /tmp/mem.lime s3://ir-evidence-444455556666/CASE-123/memory/", "aws s3 cp /tmp/mem.lime.sha256 s3://ir-evidence-444455556666/CASE-123/memory/" ] } 
ws ssm send-command --instance-ids "$I" \ --document-name AWS-RunShellScript \ --comment "IR CASE-123 memory capture" \ --parameters file://capture.json 

Writing the image to local disk changes the disk. If that matters for your case, snapshot the volumes first. The instance role needs permission to write, and only write, to the evidence prefix.

Disk
Snapshot every attached volume, then share the snapshots with the forensics account.

SNAP=$(aws ec2 create-snapshot --volume-id vol-0123456789abcdef0 \ --description "IR CASE-123" \ --tag-specifications 'ResourceType=snapshot,Tags=[{Key=IR-Case,Value=CASE-123}]' \ --query SnapshotId --output text) aws ec2 wait snapshot-completed --snapshot-ids "$SNAP" aws ec2 modify-snapshot-attribute --snapshot-id "$SNAP" \ --attribute createVolumePermission --operation-type add \ --user-ids 444455556666 

A snapshot encrypted with the default AWS managed EBS key cannot be shared. Copy it with a customer managed key whose policy allows the forensics account, and share the copy.
In the forensics account, copy the snapshot, create a volume from it, attach it to an analysis instance and mount it read-only:

sudo mount -o ro,noexec,nodev /dev/nvme1n1p1 /mnt/evidence 

XFS file systems also need norecovery,nouuid.

Evidence that is not on an instance

Two IAM snapshots are worth taking in every incident:

aws iam generate-credential-report aws iam get-credential-report --query Content --output text | base64 -d > credential-report.csv aws iam get-account-authorization-details > iam-snapshot.json 

Integrity and custody

Evidence is only useful if you can show it has not changed.

  • Store everything in an S3 bucket with Object Lock in compliance mode and versioning on.
  • Record a SHA-256 hash at collection and verify it before analysis.
  • Prove CloudTrail logs are complete and unmodified with log file validation:
aws cloudtrail validate-logs \ --trail-arn arn:aws:cloudtrail:eu-west-1:111122223333:trail/org-trail \ --start-time 2026-11-01T00:00:00Z --end-time 2026-11-13T00:00:00Z 

Keep a custody log from the first item collected.

Eradication and recovery
Eradication removes everything the attacker created or changed; recovery returns the workload to service from a state you trust. Do not start either until the scope from analysis is stable, or you will remove one foothold while the attacker uses another.

Find what the attacker left behind
Start from the timeline: every successful write call by a compromised principal is something to reverse or verify. Then check the places persistence hides, in every Region.

A loop for running instances across all enabled Regions:

for r in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do echo "== $r" aws ec2 describe-instances --region "$r" \ --filters Name=instance-state-name,Values=pending,running \ --query 'Reservations[].Instances[].[InstanceId,InstanceType,LaunchTime]' \ --output text done 

IAM Access Analyzer external access findings list resource policies that grant access outside your organisation, which shortens the resource-policy review.

Remove and rotate

  1. Delete unauthorised resources after capturing their configuration as evidence.
  2. Delete the compromised access keys you deactivated during containment.
  3. Rotate every secret the compromised principal could read: Secrets Manager secrets, Parameter Store values, database passwords, API tokens, SSH keys.
  4. Restore changed policies from version control, not from memory.
  5. Fix the root cause: the leaked key's source, the vulnerable application, the over-broad role.

Recover

  1. Rebuild compute from a known-good image and infrastructure as code. Do not return a compromised instance to service after cleaning it.
  2. Restore data from backups taken before the first attacker action in your timeline. AWS Backup vaults with Vault Lock give you copies the attacker could not delete.
  3. Validate before reopening: vulnerability scan, configuration checks, and a review of the new resources' roles.
  4. Lift containment in stages, starting with the least privileged access, and watch each step.
  5. Monitor closely for at least two weeks. Add temporary detections for the indicators and techniques seen in this incident.

Close the incident only when four statements are true: the entry point is fixed, every credential in scope has been rotated, no attacker-created resource remains, and heightened monitoring has been quiet for the agreed period.

Worked playbook: a compromised IAM access key
This playbook walks through the most common AWS incident: a long-term access key for an IAM user has leaked and someone else is using it. The times are targets for a prepared team.
Trigger. Any of: a GuardDuty finding on the user, a decoy or anomaly alert, a notification from AWS, or a report that the key appeared in a public repository. AWS sometimes attaches a managed policy whose name begins with AWSCompromisedKeyQuarantine to a user whose key it found exposed. Treat that as a signal. It blocks only some actions and is not containment.

Step 1: Confirm

aws iam list-access-keys --user-name app-user aws iam get-access-key-last-used --access-key-id AKIAIOSFODNN7EXAMPLE aws iam list-attached-user-policies --user-name app-user aws iam list-user-policies --user-name app-user aws iam list-groups-for-user --user-name app-user 

Step 2: Contain

Run the access-key commands from the containment section. Tell the application owner before or as you do it; the outage is expected and accepted. If the timeline shows AssumeRole calls, apply the session-revocation policy to each of those roles.

Step 3: Scope
Run the three scoping queries from the analysis section for this key. Then answer four questions and record each answer in the timeline.

  1. Did it create credentials or principals? Look for CreateAccessKey, CreateUser, CreateLoginProfile, CreateRole and UpdateAssumeRolePolicy.
  2. Did it assume roles? Each assumed role is a new principal to scope in the same way.
  3. Did it read data? Check S3 data events, GetSecretValue, GetParameter and database snapshot activity. Use Amazon Macie results to judge what the affected buckets contain.
  4. Did it launch anything? Check RunInstances, CreateFunction and model invocation in every Region

Step 4: Eradicate and recover

aws iam delete-access-key --user-name app-user --access-key-id AKIAIOSFODNN7EXAMPLE aws iam delete-user-policy --user-name app-user --policy-name IR-DenyAll 

Remove the deny policy only after the leaked key is deleted and the new credential is in place. Remove the key from wherever it leaked, including repository history, and assume every copy is known to others.
Prefer not to issue another long-term key. Move the workload to an IAM role: an instance profile, an ECS task role, IAM Roles Anywhere for on-premises systems, or OIDC federation for CI pipelines.

Decision points

  • Data was read. Involve legal and privacy leads at once. Notification deadlines may run from the time you became aware.
  • A role with administrator rights was assumed. Treat the incident as a full account compromise and widen eradication accordingly.
  • Activity crossed accounts. Repeat scoping in each account, and consider the quarantine SCP.

Common mistakes

  • Deleting the key before recording what it did.
  • Rotating the key but leaving a second key or a login profile the attacker created.
  • Checking only the home Region.
  • Closing the incident without finding how the key leaked.

Automating response
Automate the steps that are safe, reversible and always the same and keep a human decision in front of anything that can cause an outage. Attack tempo is the reason to do this at all: an intrusion that completes in minutes will not wait for a ticket to be read.

Pattern

  1. A finding reaches EventBridge.
  2. A rule starts a Step Functions state machine.
  3. The workflow enriches the finding, captures evidence, and checks whether the resource is opted in to automatic containment.
  4. If it is, the workflow contains it. If not, it sends an approval request and waits.
  5. The workflow records every action in the case and notifies the on-call analyst. A rule that matches high-severity GuardDuty findings on instances:
{ "source": ["aws.guardduty"], "detail-type": ["GuardDuty Finding"], "detail": { "severity": [{ "numeric": [">=", 7] }], "resource": { "resourceType": ["Instance"] } } } A Lambda function for the containment step. It acts only on instances tagged as opted in, does nothing if the instance is already quarantined, and takes snapshots before changing the network. import os import boto3 ec2 = boto3.client("ec2") ISOLATION_SG = os.environ["ISOLATION_SG_ID"] def handler(event, context): detail = event["detail"] instance_id = detail["resource"]["instanceDetails"]["instanceId"] finding_id = detail["id"] tags = ec2.describe_tags( Filters=[{"Name": "resource-id", "Values": [instance_id]}] )["Tags"] tag_map = {t["Key"]: t["Value"] for t in tags} if tag_map.get("IR-AutoContain") != "allowed": return {"instance": instance_id, "action": "skipped", "reason": "not opted in"} if tag_map.get("IR-Status") == "Quarantined": return {"instance": instance_id, "action": "none", "reason": "already quarantined"} ec2.create_tags( Resources=[instance_id], Tags=[ {"Key": "IR-Status", "Value": "Quarantined"}, {"Key": "IR-Finding", "Value": finding_id}, ], ) ec2.modify_instance_attribute( InstanceId=instance_id, DisableApiTermination={"Value": True} ) volumes = ec2.describe_volumes( Filters=[{"Name": "attachment.instance-id", "Values": [instance_id]}] )["Volumes"] snapshots = [ ec2.create_snapshot( VolumeId=v["VolumeId"], Description=f"IR {finding_id}" )["SnapshotId"] for v in volumes ] ec2.modify_instance_attribute(InstanceId=instance_id, Groups=[ISOLATION_SG]) return {"instance": instance_id, "action": "isolated", "snapshots": snapshots} 

Two limits of this example: the isolation security group must be in the instance's VPC, and an instance with several network interfaces needs each interface changed separately.

Guardrails

  • Least privilege. The function's role can tag, snapshot and change security groups. It cannot terminate or delete.
  • Idempotent. Running twice does no harm.
  • Opt-in by tag or account until you trust the workflow, then widen.
  • No silent failure. Errors go to the on-call channel, and a failed containment is itself an alert.
  • Protect the automation. The workflow's roles are high-value targets. Alert on any change to them.
  • Rehearse. Trigger the workflow with sample findings in a sandbox every month.

AWS also publishes Systems Manager Automation runbooks for containing instances and IAM principals, and AWS Security Incident Response can perform containment for you once you opt in. Either can replace custom code if it fits your process.

Post-incident activity
An incident is only worth its cost if it changes something. Hold a review within five working days, while the timeline is fresh and leave with owned actions.

The review
Keep it blameless and structured around the timeline. Answer these questions:

  1. How did the attacker get in, and what would have prevented it?
  2. When did the first evidence exist, and when did we notice?
  3. What slowed analysis: missing logs, missing access, missing knowledge?
  4. Did containment work the first time? What did it break?
  5. What did we do by hand that should be automated?
  6. What would we do differently?

Metrics
Record the same measurements for every incident so trends are visible.

An incident detected by a third party or by AWS means your own detection missed it. Treat that as a finding in itself.

Turn lessons into controls

NIST SP 800-61 Revision 3 frames this as continuous improvement across governance, identification, protection, detection, response and recovery, not as a final phase.

Exercise the plan

  • Tabletop exercises each quarter with one scenario and the people who would really be involved, including legal and communications.
  • Technical simulations in a sandbox account, using a tool such as Stratus Red Team to perform real techniques, with responders working from real alerts.
  • Access drills each month: assume the incident response role in a random account and run one query.
  • Recovery tests twice a year: restore one workload from backup into a clean account. Rotate who leads. A plan only one person can run is a single point of failure.

References

  • NIST SP 800-61 Revision 3, Incident Response Recommendations and Considerations for Cybersecurity Risk Management, April 2025
  • AWS Security Incident Response Guide, AWS technical guide
  • Containment, AWS Security Incident Response User Guide
  • Accelerate investigations with AWS Security Incident Response AI-powered capabilities, AWS Security Blog
  • GuardDuty Extended Threat Detection, Amazon GuardDuty User Guide
  • Security Lake queries for AWS source version 2 (OCSF 1.1.0), Amazon Security Lake User Guide
  • Threat Technique Catalog for AWS, AWS Customer Incident Response Team Commands were written for AWS CLI v2 **and checked against documentation in November 2026. **Test each one in a sandbox account before adding it to a playbook.