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 sourceEnabled by defaultCost basisEnable for MCP if
CloudTrail management eventsYes (included)Per event volumeAlways — covers IAM, API calls, resource changes
VPC flow logsYes (included)Per GB analyzedAlways — covers network-based threats for ECS/EC2 MCPs
DNS query logsYes (included)Per query volumeAlways — covers C2 communication, crypto mining detection
S3 data-plane eventsYes (free first 30 days)Per object operationsIf MCP accesses S3 buckets with sensitive data
Kubernetes audit logs (EKS)NoPer EKS vCPU-hourIf deploying MCP servers on EKS — see EKS guide
Lambda network activityNoPer 1M invocationsIf using Lambda-based MCP functions — see Lambda guide
EBS malware scanningNoPer GB scannedIf 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 rangeLabelTypical actionMCP server examples
7.0–8.9HighPage on-call immediatelyInstanceCredentialExfiltration, C&CActivity, UnauthorizedAccess:IAMUser
4.0–6.9MediumCreate ticket within 4 hoursSSHBruteForce, PortProbeUnprotectedPort (from external source), Recon findings
0.1–3.9LowReview weeklySample 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

FailureSymptomFix
Findings not appearing for 6 hoursCompromise happened but no finding notificationCreateDetector used default FindingPublishingFrequency; update detector to FIFTEEN_MINUTES via UpdateDetector
S3 export stuck in PENDING_VERIFICATIONPublishing destination never reaches PUBLISHING stateKMS key policy missing GuardDuty service principal, or S3 bucket policy missing PutObject for guardduty.amazonaws.com; check both policies
False positive flood from monitoring activityRecon:EC2/PortProbeUnprotectedPort for every scan cycleCreate ARCHIVE suppression filter before first scan; tag monitoring instances and filter by resource tag
Findings missing from member accountsAdmin account shows no findings from memberMember account not enrolled via Organizations; check list-members for DISABLED status; re-invite or check auto-enable config
GuardDuty enabled but no EC2 findingsEC2 instances running, zero findings ever generatedVPC 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 failsCreateSampleFindings returns errorCreateSampleFindings rate-limited to one call per hour; use for initial testing only, not for CI/CD verification