Guide · AWS GuardDuty · Setup
AWS GuardDuty for MCP Servers — Threat Detection, Detector Setup, Finding Export
GuardDuty is a regional threat-detection service that analyzes CloudTrail management events, VPC flow logs, DNS query logs, and S3 data-plane events to surface security findings without requiring you to configure or manage any log forwarding. For MCP server operators the critical enablement detail is the finding-publishing frequency: the default is SIX_HOURS, which means a compromised MCP server could exfiltrate credentials for six hours before you see the finding. Change this to FIFTEEN_MINUTES when enabling the detector. A second common mistake is not configuring suppression rules for expected activities — if your MCP server scans its own infrastructure or probes external endpoints as part of monitoring, GuardDuty will generate Recon:EC2/PortProbeUnprotectedPort findings on every scan cycle. Set up a CreateFilter suppression rule immediately after enabling to prevent noise from drowning out real threats.
TL;DR
Enable GuardDuty with --finding-publishing-frequency FIFTEEN_MINUTES (not the default SIX_HOURS). Create an EventBridge rule on aws.guardduty findings immediately — GuardDuty findings are deleted after 90 days and EventBridge is the only real-time alerting path. Add suppression filters for known-good activities before your first scan cycle. If running multiple AWS accounts, designate a security account as delegated administrator via Organizations before enabling in member accounts. See the findings API guide for querying and archiving, the EKS runtime monitoring guide for Kubernetes-specific findings, and the automated remediation guide for EventBridge-driven response.
Enable a GuardDuty detector
GuardDuty operates per-account per-region. Enabling in one region does not cover other regions — you must enable a detector in every region where your MCP infrastructure runs. The detector ID returned by CreateDetector is used in all subsequent API calls.
# Enable GuardDuty with 15-minute finding publishing frequency
aws guardduty create-detector \
--enable \
--finding-publishing-frequency FIFTEEN_MINUTES \
--data-sources '{
"S3Logs": {"Enable": true},
"Kubernetes": {"AuditLogs": {"Enable": true}},
"MalwareProtection": {"ScanEc2InstanceWithFindings": {"EbsVolumes": {"Enable": true}}}
}' \
--tags "Environment=production,Service=mcp-platform"
# Output: {"DetectorId": "abc1234567890abcdef1234567890"}
# Save this — used in all API calls for this region
# Verify detector status
aws guardduty get-detector --detector-id abc1234567890abcdef1234567890
# Check: Status = ENABLED, FindingPublishingFrequency = FIFTEEN_MINUTES
# List all detectors in region (should be exactly one per account per region)
aws guardduty list-detectors
Data source pricing is separate from the base GuardDuty price. The three core data sources (CloudTrail management events, VPC flow logs, DNS logs) are included in the base price. S3 data-plane events, Kubernetes audit logs, EKS runtime monitoring, Lambda protection, and EBS malware scanning each add to the cost. For a typical MCP server deployment running on ECS or Lambda, enable S3 data-plane events and Kubernetes audit logs only if you actually use those services — enabling them on an account with no S3 data-plane activity or no EKS clusters adds cost without findings.
| Data source | Enabled by default | Cost basis | Enable for MCP if |
|---|---|---|---|
| CloudTrail management events | Yes (included) | Per event volume | Always — covers IAM, API calls, resource changes |
| VPC flow logs | Yes (included) | Per GB analyzed | Always — covers network-based threats for ECS/EC2 MCPs |
| DNS query logs | Yes (included) | Per query volume | Always — covers C2 communication, crypto mining detection |
| S3 data-plane events | Yes (free first 30 days) | Per object operations | If MCP accesses S3 buckets with sensitive data |
| Kubernetes audit logs (EKS) | No | Per EKS vCPU-hour | If deploying MCP servers on EKS — see EKS guide |
| Lambda network activity | No | Per 1M invocations | If using Lambda-based MCP functions — see Lambda guide |
| EBS malware scanning | No | Per GB scanned | If running stateful MCP servers on EC2 with EBS volumes |
Finding types and severity levels
GuardDuty finding types follow a structured naming convention: ThreatPurpose:ResourceTypeAffected/ThreatFamilyName.DetectionMechanism!Artifact. Understanding this structure helps you write EventBridge rules and suppression filters that target specific threat categories without matching unrelated findings.
# Finding type examples for MCP server infrastructure:
# UnauthorizedAccess:EC2/SSHBruteForce
# → ThreatPurpose: UnauthorizedAccess (gaining access without permission)
# → ResourceType: EC2 (the affected resource is an EC2 instance)
# → ThreatFamily: SSHBruteForce (brute force SSH login attempts)
#
# InstanceCredentialExfiltration:EC2/NoInstanceProfile
# → EC2 instance credentials used from outside AWS
# → Most critical for MCP servers: means IAM role credentials leaked
#
# CryptoCurrency:Lambda/BitcoinTool
# → Lambda function making DNS/network calls to mining pools
# → Likely means code injection in Lambda-based MCP
#
# Backdoor:EC2/C&CActivity.B!DNS
# → EC2 instance making DNS queries to known C2 domains
# → Indicates malware running on an MCP server host
Severity is a floating-point number from 0.1 to 8.9 mapped to three labels. The label thresholds matter because most organizations set up alerting by label, not exact value:
| Severity range | Label | Typical action | MCP server examples |
|---|---|---|---|
| 7.0–8.9 | High | Page on-call immediately | InstanceCredentialExfiltration, C&CActivity, UnauthorizedAccess:IAMUser |
| 4.0–6.9 | Medium | Create ticket within 4 hours | SSHBruteForce, PortProbeUnprotectedPort (from external source), Recon findings |
| 0.1–3.9 | Low | Review weekly | Sample findings, policy violations, low-confidence recon |
# List findings by severity — High findings first
aws guardduty list-findings \
--detector-id abc1234567890abcdef1234567890 \
--finding-criteria '{
"Criterion": {
"severity": {"Gte": 7}
}
}' \
--sort-criteria '{"AttributeName": "updatedAt", "OrderBy": "DESC"}' \
--max-results 50
# Get finding details (up to 50 IDs per call)
aws guardduty get-findings \
--detector-id abc1234567890abcdef1234567890 \
--finding-ids "finding-id-1" "finding-id-2"
Configuring suppression rules for MCP monitoring activity
AliveMCP's core function — probing MCP server endpoints — generates network traffic that GuardDuty may flag as reconnaissance. Without suppression rules in place before your first scan cycle, you will be flooded with Recon:EC2/PortProbeUnprotectedPort and Recon:EC2/Portscan findings for your own monitoring infrastructure.
# Create a suppression rule (filter with ARCHIVE action) for monitoring probes
# This prevents findings from ever appearing — more effective than archiving after creation
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "monitoring-probe-suppression" \
--action ARCHIVE \
--rank 1 \
--description "Suppress port probe findings from AliveMCP monitoring infrastructure" \
--finding-criteria '{
"Criterion": {
"type": {
"Equals": ["Recon:EC2/PortProbeUnprotectedPort", "Recon:EC2/Portscan"]
},
"resource.instanceDetails.tags.key": {
"Equals": ["Service"]
},
"resource.instanceDetails.tags.value": {
"Equals": ["mcp-monitoring"]
}
}
}'
# Suppress sample findings (auto-generated by GuardDuty for testing)
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "sample-findings-suppression" \
--action ARCHIVE \
--rank 2 \
--finding-criteria '{
"Criterion": {
"type": {"Equals": ["SampleFinding"]}
}
}'
# Suppress known-safe external vulnerability scanner (e.g., Qualys, Tenable)
# Replace with your scanner's IP range
aws guardduty create-filter \
--detector-id abc1234567890abcdef1234567890 \
--name "scanner-suppression" \
--action ARCHIVE \
--rank 3 \
--finding-criteria '{
"Criterion": {
"type": {"Prefix": ["Recon:EC2/"]},
"service.action.portProbeAction.portProbeDetails.remoteIpDetails.ipAddressV4": {
"Equals": ["203.0.113.100"]
}
}
}'
Suppression filters with ARCHIVE action run at finding generation time — findings matching the filter are immediately archived and never appear in the active findings list. This is different from archiving findings after they appear: active filters prevent future findings of the same type/source from populating the dashboard and triggering EventBridge events. Always prefer CreateFilter over retroactive archiving for known false-positive patterns.
Export findings to S3 for SIEM integration
GuardDuty retains findings for 90 days in the detector. To feed findings into a SIEM (Splunk, Elasticsearch, Security Lake) or archive them beyond 90 days, configure a publishing destination to export findings to S3 in JSONL format. The export requires a KMS CMK — GuardDuty cannot write to an unencrypted S3 bucket or a bucket using AWS-managed keys (SSE-S3).
# Step 1: Create KMS key for findings export (GuardDuty needs specific key policy)
# Key policy must allow guardduty.amazonaws.com to call GenerateDataKey and Decrypt
FINDINGS_KEY_ARN=$(aws kms create-key \
--description "GuardDuty findings export encryption" \
--key-policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:root"},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow GuardDuty to use the key",
"Effect": "Allow",
"Principal": {"Service": "guardduty.amazonaws.com"},
"Action": ["kms:GenerateDataKey", "kms:Decrypt"],
"Resource": "*"
}
]
}' \
--query 'KeyMetadata.Arn' --output text)
# Step 2: Create S3 bucket with bucket policy allowing GuardDuty to write
# Bucket policy must allow guardduty.amazonaws.com PutObject
aws s3api create-bucket \
--bucket my-guardduty-findings-bucket \
--region us-east-1
aws s3api put-bucket-policy \
--bucket my-guardduty-findings-bucket \
--policy '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGuardDutyFindingsExport",
"Effect": "Allow",
"Principal": {"Service": "guardduty.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-guardduty-findings-bucket/*",
"Condition": {
"StringEquals": {"aws:SourceAccount": "123456789012"}
}
}]
}'
# Step 3: Create the publishing destination
aws guardduty create-publishing-destination \
--detector-id abc1234567890abcdef1234567890 \
--destination-type S3 \
--destination-properties \
DestinationArn=arn:aws:s3:::my-guardduty-findings-bucket,KmsKeyArn=$FINDINGS_KEY_ARN
# Verify destination is PUBLISHING (not PENDING_VERIFICATION)
aws guardduty list-publishing-destinations \
--detector-id abc1234567890abcdef1234567890
S3 findings are exported in gzipped JSONL format, partitioned by AWSLogs/account-id/GuardDuty/region/YYYY/MM/DD/. Each file contains multiple finding records. The S3 export runs on the same frequency as the detector's FindingPublishingFrequency (FIFTEEN_MINUTES in the configuration above). New findings are exported on each cycle; updated findings (same finding with incremented count) overwrite the previous export of that finding ID.
For AWS Security Lake integration (preferred over raw S3 export for large multi-account deployments): GuardDuty integrates natively with Security Lake as a source — findings are automatically ingested in OCSF format without a custom S3 bucket or KMS key setup. Enable via the Security Lake console after enabling GuardDuty.
Multi-account setup with AWS Organizations
Running MCP infrastructure across multiple AWS accounts (common pattern: prod/staging/dev accounts, or one account per customer) requires a delegated administrator setup. Without it, each account has an independent GuardDuty detector and findings are not centrally visible — a compromised member account's finding only appears in that account's detector.
# Step 1: Enable GuardDuty as a trusted service in Organizations
# (run in management account)
aws organizations enable-aws-service-access \
--service-principal guardduty.amazonaws.com
# Step 2: Designate the security/audit account as GuardDuty admin
# (run in management account)
aws guardduty enable-organization-admin-account \
--admin-account-id 111111111111
# Step 3: Configure auto-enrollment for new accounts
# (run in the designated admin account 111111111111)
aws guardduty update-organization-configuration \
--detector-id \
--auto-enable ALL \
--features '[
{"Name":"S3_DATA_EVENTS","AutoEnable":"NEW"},
{"Name":"LAMBDA_NETWORK_LOGS","AutoEnable":"NEW"},
{"Name":"RUNTIME_MONITORING","AutoEnable":"NEW"}
]'
# Step 4: List all member accounts and their GuardDuty status
aws guardduty list-members \
--detector-id \
--query 'Members[*].{Account:AccountId,Email:Email,Status:RelationshipStatus}'
With delegated administrator setup: all member account findings are visible in the admin account's GuardDuty console and API. EventBridge rules in the admin account receive findings from all member accounts — the finding's accountId field identifies the source account. This enables centralized alerting and automated remediation across the entire organization from a single Lambda function. See the automated remediation guide for multi-account response patterns.
Failure modes reference
| Failure | Symptom | Fix |
|---|---|---|
| Findings not appearing for 6 hours | Compromise happened but no finding notification | CreateDetector used default FindingPublishingFrequency; update detector to FIFTEEN_MINUTES via UpdateDetector |
| S3 export stuck in PENDING_VERIFICATION | Publishing destination never reaches PUBLISHING state | KMS key policy missing GuardDuty service principal, or S3 bucket policy missing PutObject for guardduty.amazonaws.com; check both policies |
| False positive flood from monitoring activity | Recon:EC2/PortProbeUnprotectedPort for every scan cycle | Create ARCHIVE suppression filter before first scan; tag monitoring instances and filter by resource tag |
| Findings missing from member accounts | Admin account shows no findings from member | Member account not enrolled via Organizations; check list-members for DISABLED status; re-invite or check auto-enable config |
| GuardDuty enabled but no EC2 findings | EC2 instances running, zero findings ever generated | VPC flow logs may not be published (GuardDuty uses its own copy, not your CloudWatch Logs VPC flow logs, but VPC must exist and instances must be generating traffic); verify region matches detector region |
| Sample finding generation fails | CreateSampleFindings returns error | CreateSampleFindings rate-limited to one call per hour; use for initial testing only, not for CI/CD verification |