GuardDuty threat detection
Agentless findings from CloudTrail, flow, and DNS.
A good security guard doesn't stop every visitor at the front door. They sit in a back room watching a wall of camera feeds, and the second someone starts jiggling the handle on a side door, they radio it in. Amazon GuardDuty is that guard for your AWS account. It's a managed threat-detection service, which just means AWS runs the analysis for you and you switch it on. GuardDuty reads the activity logs your account already produces and raises a 'finding' whenever something looks like an attack. Your effort goes into responding, not into building the detection from scratch.
What it actually reads
GuardDuty watches three log sources, and you install nothing on your servers to feed it. The word for that is 'agentless': no agent, no extra software sitting on the machine. The first source is CloudTrail, AWS's own record of every API call in the account (an API call is just a command sent to AWS, like 'create this server' or 'read this file'), including who made it and from where. The second is VPC flow logs. A VPC, or Virtual Private Cloud, is your own private network inside AWS, and its flow logs are a running list of connections, roughly 'machine A talked to machine B on port 22'. The third is DNS query logs, the domain names your instances look up (DNS, the Domain Name System, is the phone book that turns a name like example.com into a numeric address). GuardDuty checks all of it against two things: a threat-intelligence feed of known-bad addresses and domains, which is a watchlist of known burglars, and a behavioral baseline of what's normal for you. Credentials suddenly used from a country you've never operated in, or a server quietly calling a known crypto-mining pool, turn into a finding.
$ aws guardduty create-detector \--enable \--finding-publishing-frequency FIFTEEN_MINUTES{"DetectorId": "12abc34d567e8f9a0b1c2d3e4f5a6b7c"}$ aws guardduty get-detector --detector-id 12abc34d567e8f9a0b1c2d3e4f5a6b7c{"CreatedAt": "2026-07-16T08:55:03.000Z","Status": "ENABLED","ServiceRole": "arn:aws:iam::111122223333:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty","FindingPublishingFrequency": "FIFTEEN_MINUTES","Features": [{ "Name": "CLOUD_TRAIL", "Status": "ENABLED" },{ "Name": "DNS_LOGS", "Status": "ENABLED" },{ "Name": "FLOW_LOGS", "Status": "ENABLED" },{ "Name": "S3_DATA_EVENTS", "Status": "DISABLED" },{ "Name": "RUNTIME_MONITORING", "Status": "DISABLED" }]}
That one detector covers one account in one region, and nothing else. In an organization with dozens of accounts you don't want to click through each one by hand, so you delegate GuardDuty administration to a single security account and set new members to auto-enable. Every account that exists today, and every one someone spins up next year, gets covered without anyone having to remember. Coverage becomes a property of the organization instead of a line on a checklist that people quietly forget.
Fire-drill it before a real attack
You don't want the first real test of your alerting to be an actual breach. GuardDuty can generate sample findings on demand, one fake but fully-formed finding of whatever type you name, so you can watch it travel through your routing and land on your pager, proving the whole chain works end to end. It's a fire drill. The alarm is real, the fire isn't.
$ aws guardduty create-sample-findings \--detector-id 12abc34d567e8f9a0b1c2d3e4f5a6b7c \--finding-types "UnauthorizedAccess:EC2/SSHBruteForce"$ aws guardduty list-findings \--detector-id 12abc34d567e8f9a0b1c2d3e4f5a6b7c \--finding-criteria '{"Criterion":{"service.additionalInfo.sample":{"Equals":["true"]}}}'{"FindingIds": ["a4b8c2d1e5f6a7b8c9d0e1f2a3b4c5d6"]}
Pull the finding back with get-findings and you get the object your automation will actually parse. Every field earns its place. Type is the machine-readable category, written as ThreatPurpose:ResourceType/ThreatName. Severity is a number from 1.0 to 10.0 that AWS sorts into four buckets, Low, Medium, High, and Critical, and it's the number your routing rules key off. SSH brute force, where an attacker hammers a server's remote login (SSH, the standard way to reach a server's command line) with password guesses, lands at a Low 2, a rattled doorknob rather than a five-alarm fire. The Service block is the evidence. service.action tells you what kind of activity triggered the finding, here an inbound network connection, and the detail nested under it carries the attacker's address, its country and network owner, the local port, and how many times it happened. EventFirstSeen and EventLastSeen mark the start and end of the activity. A real finding and a sample one share this exact schema, which is the whole reason the drill is worth running.
$ aws guardduty get-findings \--detector-id 12abc34d567e8f9a0b1c2d3e4f5a6b7c \--finding-ids a4b8c2d1e5f6a7b8c9d0e1f2a3b4c5d6{"Findings": [{"SchemaVersion": "2.0","AccountId": "111122223333","Region": "eu-west-1","Type": "UnauthorizedAccess:EC2/SSHBruteForce","Severity": 2,"Title": "[SAMPLE] 198.51.100.7 is performing SSH brute force attacks against i-0abcd1234efgh5678.","Description": "198.51.100.7 is performing SSH brute force attacks against i-0abcd1234efgh5678. Brute force attacks are used to gain unauthorized access to your instance.","Resource": {"ResourceType": "Instance","InstanceDetails": { "InstanceId": "i-0abcd1234efgh5678", "AvailabilityZone": "eu-west-1a" }},"Service": {"Action": {"ActionType": "NETWORK_CONNECTION","NetworkConnectionAction": {"ConnectionDirection": "INBOUND","Protocol": "TCP","LocalPortDetails": { "Port": 22, "PortName": "SSH" },"RemoteIpDetails": {"IpAddressV4": "198.51.100.7","Country": { "CountryName": "Russia" },"Organization": { "Asn": "12345", "AsnOrg": "ExampleHostingLtd" }}}},"Count": 27,"EventFirstSeen": "2026-07-16T09:02:11Z","EventLastSeen": "2026-07-16T09:41:52Z"}}]}
Protection plans, and the bill
The three foundational sources are on by default and cheap, priced on how many CloudTrail events you generate and how many gigabytes of flow and DNS logs. Everything past that is a protection plan you opt into, and each is billed separately by volume. S3 Protection watches access to your files in S3, AWS's object storage. Runtime Monitoring puts a lightweight agent inside your running workloads (EKS and ECS are AWS's Kubernetes and container services, EC2 is a plain virtual server) so it can see process and network behavior the logs never capture, billed per vCPU-hour. Malware Protection scans the disk of a suspect server and charges per gigabyte scanned. RDS Protection watches logins to your managed databases, and Lambda Protection watches the network activity of your small on-demand functions. Each one earns its keep in the right place. Switched on blindly across a large organization, they can multiply your GuardDuty bill several times over, so enable them where the workload justifies it and watch the first month's cost.
Left alone, GuardDuty also surfaces things that are real but expected, like a vulnerability scanner you run on purpose or a backup job that reads data in bulk. Those aren't false alarms. They're true findings you've already decided you don't care about. The wrong fix is disabling the finding type, because that blinds you to the genuine article too. The right fix is a suppression rule: a filter that auto-archives findings matching a tight set of criteria, so they're still recorded but kept out of your responders' queue.
$ aws guardduty create-filter \--detector-id 12abc34d567e8f9a0b1c2d3e4f5a6b7c \--name suppress-sample-ssh-bruteforce \--action ARCHIVE \--rank 1 \--finding-criteria '{"Criterion":{"type":{"Equals":["UnauthorizedAccess:EC2/SSHBruteForce"]},"service.additionalInfo.sample":{"Equals":["true"]}}}'{"Name": "suppress-sample-ssh-bruteforce"}
Try this
Work through “Protection plans, and the bill” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.
Takeaway
The trap worth remembering here: a detector guards one region, and plans bill by volume. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.