AWS GuardDuty Threat Detection Tuning for Production
Reducing GuardDuty noise by 60% while maintaining detection of real threats through suppression rules, trusted IP lists, and finding prioritization

Introduction
AWS GuardDuty continuously monitors VPC Flow Logs, CloudTrail events, DNS logs, EKS audit logs, and S3 data events to detect threats using machine learning and threat intelligence. Out of the box, GuardDuty generates significant noise: in a typical 50-account organization, expect 200-500 findings per week, of which 60-80% are benign or informational.
The challenge is not enabling GuardDuty but tuning it so that security teams can focus on genuine threats rather than drowning in false positives. Proper tuning reduces actionable findings by 60% without missing real incidents.
GuardDuty Finding Categories
| Category | Finding Types | Typical Volume | True Positive Rate |
|---|---|---|---|
| Reconnaissance | Port scanning, API enumeration | High (40%) | 15% |
| Instance Compromise | Cryptocurrency, C2 communication | Medium (20%) | 75% |
| Account Compromise | Impossible travel, unusual API calls | Medium (15%) | 45% |
| Bucket Compromise | Public access, unusual S3 access | Low (10%) | 60% |
| Kubernetes | Privileged container, anonymous access | Medium (15%) | 50% |
Most Common Findings (and Their Signal Value)
| Finding Type | Frequency | Action | Typical Cause |
|---|---|---|---|
| Recon:EC2/PortProbeUnprotectedPort | Very High | Suppress (most) | Internet noise |
| UnauthorizedAccess:EC2/SSHBruteForce | High | Suppress if bastion | Known pattern |
| Behavior:EC2/NetworkPortUnusual | Medium | Investigate | Often benign service |
| CryptoCurrency:EC2/BitcoinTool.B | Low | Critical alert | Real compromise |
| Discovery:S3/MaliciousIPCaller | Medium | Investigate | Scan or real probe |
| Impact:EC2/PortSweep | High | Suppress if scanner | Security scanning tools |
| UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration | Low | Critical alert | Credential theft |
Suppression Rules
Suppression rules archive findings that match known benign patterns:
# Suppress port scanning from known security scanners
aws guardduty create-filter \
--detector-id abc123 \
--name "suppress-security-scanners" \
--action ARCHIVE \
--finding-criteria '{
"Criterion": {
"type": {
"Eq": ["Recon:EC2/PortProbeUnprotectedPort", "Recon:EC2/Portscan"]
},
"service.action.portProbeAction.remoteIpDetails.ipAddressV4": {
"Eq": ["203.0.113.10", "203.0.113.11", "198.51.100.50"]
}
}
}' \
--description "Suppress findings from authorized vulnerability scanners"
# Suppress SSH brute force on bastion hosts (expected)
aws guardduty create-filter \
--detector-id abc123 \
--name "suppress-bastion-ssh" \
--action ARCHIVE \
--finding-criteria '{
"Criterion": {
"type": {
"Eq": ["UnauthorizedAccess:EC2/SSHBruteForce"]
},
"resource.instanceDetails.tags.value": {
"Eq": ["bastion"]
}
}
}' \
--description "SSH brute force on bastion hosts is expected traffic"
# Suppress DNS findings for known SaaS services
aws guardduty create-filter \
--detector-id abc123 \
--name "suppress-known-dns" \
--action ARCHIVE \
--finding-criteria '{
"Criterion": {
"type": {
"Eq": ["Trojan:EC2/DNSDataExfiltration"]
},
"service.action.dnsRequestAction.domain": {
"Eq": ["api.segment.io", "events.pendo.io", "dc.services.visualstudio.com"]
}
}
}' \
--description "Known SaaS services that trigger DNS exfiltration findings"
Trusted IP Lists
Trusted IP lists prevent findings from being generated for known-good sources:
# Create trusted IP list for corporate egress IPs
aws guardduty create-ip-set \
--detector-id abc123 \
--name "corporate-egress-ips" \
--format TXT \
--location s3://security-configs/trusted-ips.txt \
--activate
# trusted-ips.txt content:
# 203.0.113.0/24 # Main office
# 198.51.100.0/24 # VPN egress
# 192.0.2.0/24 # Data center
Threat IP Lists (Custom Threat Intelligence)
# Add custom threat intelligence
aws guardduty create-threat-intel-set \
--detector-id abc123 \
--name "internal-threat-feed" \
--format TXT \
--location s3://security-configs/threat-ips.txt \
--activate
Organization-Wide Configuration
import boto3
def configure_guardduty_org(admin_account_id, detector_id):
"""Configure GuardDuty across the organization."""
gd = boto3.client('guardduty')
# Enable all protection plans
gd.update_organization_configuration(
DetectorId=detector_id,
AutoEnable=True,
DataSources={
'S3Logs': {'AutoEnable': True},
'Kubernetes': {'AuditLogs': {'AutoEnable': True}},
'MalwareProtection': {
'ScanEc2InstanceWithFindings': {
'EbsVolumes': {'AutoEnable': True}
}
}
},
Features=[
{
'Name': 'EKS_RUNTIME_MONITORING',
'AutoEnable': 'ALL'
},
{
'Name': 'LAMBDA_NETWORK_LOGS',
'AutoEnable': 'ALL'
},
{
'Name': 'RDS_LOGIN_EVENTS',
'AutoEnable': 'ALL'
}
]
)
# Set publishing frequency to 15 minutes (fastest)
gd.update_detector(
DetectorId=detector_id,
FindingPublishingFrequency='FIFTEEN_MINUTES'
)
Automated Response Pipeline
EventBridge Rules by Severity
{
"source": ["aws.guardduty"],
"detail-type": ["GuardDuty Finding"],
"detail": {
"severity": [{"numeric": [">=", 7]}],
"type": [{
"anything-but": {
"prefix": "Recon:"
}
}]
}
}
Response Lambda
import boto3
import json
def handler(event, context):
"""Automated response to high-severity GuardDuty findings."""
finding = event['detail']
finding_type = finding['type']
severity = finding['severity']
# Critical responses (severity >= 8)
if severity >= 8:
if 'CryptoCurrency' in finding_type:
isolate_instance(finding)
alert_security_team(finding, priority='P1')
elif 'InstanceCredentialExfiltration' in finding_type:
revoke_instance_credentials(finding)
alert_security_team(finding, priority='P1')
elif 'Backdoor' in finding_type:
isolate_instance(finding)
snapshot_for_forensics(finding)
alert_security_team(finding, priority='P1')
# High severity responses (severity 7-8)
elif severity >= 7:
alert_security_team(finding, priority='P2')
create_investigation_ticket(finding)
def isolate_instance(finding):
"""Apply restrictive security group to isolate instance."""
ec2 = boto3.client('ec2')
instance_id = finding['resource']['instanceDetails']['instanceId']
# Create isolation security group (no inbound, no outbound)
vpc_id = finding['resource']['instanceDetails']['networkInterfaces'][0]['vpcId']
sg = ec2.create_security_group(
GroupName=f'isolation-{instance_id}',
Description=f'Isolation SG for compromised {instance_id}',
VpcId=vpc_id
)
# Remove all egress rules
ec2.revoke_security_group_egress(
GroupId=sg['GroupId'],
IpPermissions=[{
'IpProtocol': '-1',
'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
}]
)
# Replace instance security groups with isolation group
ec2.modify_instance_attribute(
InstanceId=instance_id,
Groups=[sg['GroupId']]
)
def revoke_instance_credentials(finding):
"""Revoke compromised instance role credentials."""
iam = boto3.client('iam')
role_name = finding['resource']['accessKeyDetails']['userName']
# Attach deny-all policy to invalidate existing sessions
deny_policy = {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"DateLessThan": {
"aws:TokenIssueTime": finding['service']['eventFirstSeen']
}
}
}]
}
iam.put_role_policy(
RoleName=role_name,
PolicyName='EmergencySessionRevocation',
PolicyDocument=json.dumps(deny_policy)
)
Tuning Metrics
Before and After Tuning
| Metric | Before Tuning | After Tuning | Change |
|---|---|---|---|
| Weekly findings | 450 | 180 | -60% |
| True positives investigated | 35 | 35 | 0% |
| False positives reviewed | 415 | 145 | -65% |
| Time spent on triage | 20 hrs/week | 8 hrs/week | -60% |
| Mean time to investigate (real) | 4 hours | 1.5 hours | -63% |
| Findings auto-remediated | 0 | 15/week | New |
KPI Targets
| KPI | Target | Measurement |
|---|---|---|
| Critical finding MTTR | < 1 hour | Finding to containment |
| High finding MTTR | < 4 hours | Finding to investigation start |
| Suppression ratio | 40-60% | Suppressed / total generated |
| Auto-remediation rate | > 30% | Auto-handled / total actionable |
| False positive rate (post-tuning) | < 40% | False positives / findings reviewed |
Key Takeaways
- Suppress reconnaissance findings from known sources (security scanners, bastion hosts, monitoring tools) to eliminate 40-50% of noise immediately.
- Create trusted IP lists for corporate networks to prevent findings from legitimate employee activity and CI/CD pipelines.
- Automate response for cryptocurrency and credential exfiltration findings since these have 75%+ true positive rates and require immediate containment.
- Set publishing frequency to 15 minutes for faster detection-to-response times on critical findings.
- Enable all protection plans (S3, EKS, Lambda, RDS, Malware) for comprehensive threat coverage across the full AWS surface area.
- Tune iteratively over 4-6 weeks, starting with the highest-volume finding types and validating each suppression rule does not hide real threats.
- Proper tuning reduces security team triage time by 60% while maintaining the same detection rate for genuine threats.
Recommended reading

Per-Team Cost Allocation in Shared Kubernetes Clusters: From Chaos to Clarity
Implementing accurate per-namespace cost allocation in multi-tenant Kubernetes clusters, covering request vs. usage attribution, shared resource amortization, and building showback dashboards that drive accountability.

Measuring and Eliminating Toil: From 40% to 12% of Engineering Time
A systematic approach to identifying, measuring, and automating toil—the repetitive operational work that scales linearly with service growth and prevents engineers from doing creative work.

Serverless Postgres in Production: Branching, Scale-to-Zero, and the End of Database Provisioning
Running Neon serverless Postgres in production for 8 months — covering database branching workflows, scale-to-zero economics, connection pooling, and migration from RDS.

Comments
No comments yet. Be the first to share your thoughts.