Guide · AWS WAF
AWS WAF Managed Rules for MCP Servers
AWS Managed Rule Groups are pre-built WAF rule sets maintained by AWS that protect against common web attacks without requiring you to write individual rules. The critical complication for MCP servers: the CommonRuleSet was designed for traditional web applications, not JSON-RPC APIs. Three rules in CommonRuleSet cause false positives on valid MCP traffic: SizeRestrictions_BODY blocks request bodies over 8KB (MCP tool calls with document context routinely exceed this), CrossSiteScripting_BODY blocks JSON payloads containing HTML content as tool arguments, and GenericRFI_BODY flags JSON strings containing URL-like values in tool call arguments. Before attaching any managed rule group to a production MCP endpoint, always add the problematic rules to excludedRules and run in COUNT mode for one week.
TL;DR
Attach AWSManagedRulesCommonRuleSet (exclude SizeRestrictions_BODY, CrossSiteScripting_BODY, GenericRFI_BODY), AWSManagedRulesKnownBadInputsRuleSet (no exclusions needed for MCP), and AWSManagedRulesAmazonIpReputationList. Total WCU consumption: ~770. Always use overrideAction: { count: {} } for the first week, then switch to overrideAction: { none: {} } after reviewing CloudWatch logs for false positives.
WebACL Capacity Units (WCU) budget
Every WAF rule consumes Web ACL Capacity Units (WCU). A WebACL has a default maximum of 5,000 WCU. AWS Managed Rule Groups consume a fixed WCU allocation regardless of how many rules they contain.
| Managed rule group | WCU cost | Cost (USD/month) | MCP recommendation |
|---|---|---|---|
| AWSManagedRulesCommonRuleSet | 700 | Free | Yes — exclude 3 rules |
| AWSManagedRulesKnownBadInputsRuleSet | 200 | Free | Yes — no exclusions needed |
| AWSManagedRulesAmazonIpReputationList | 25 | Free | Yes — no exclusions needed |
| AWSManagedRulesAnonymousIpList | 50 | Free | Optional — blocks Tor/VPN exit nodes (tradeoff: legitimate users on VPN get blocked) |
| AWSManagedRulesBotControlRuleSet (Common) | 50 | $10/month + $1/1M requests | Use only for browser-based MCP clients; breaks API MCP clients |
| AWSManagedRulesBotControlRuleSet (Targeted) | 100 | $10/month + $1/1M requests | Higher bot signal accuracy; same caveats as Common |
| AWSManagedRulesATPRuleSet (account takeover) | 50 | $10/month + $1/1M requests | Only for MCP servers with login/token endpoints |
| Custom rate rules (2 rules) | ~20 | Included in WebACL cost | Yes — add POST and GET rate limits |
Running all three free managed rule groups plus two custom rate rules totals ~945 WCU — well within the 5,000 cap and leaving budget for custom rules.
CommonRuleSet: which rules to exclude for MCP
The AWSManagedRulesCommonRuleSet contains 28 rules covering XSS, SQLi, path traversal, size restrictions, and remote file inclusion. Most rules are safe for MCP servers, but three create false positives:
| Rule name | What it blocks | Why it fails on MCP | Recommendation |
|---|---|---|---|
| SizeRestrictions_BODY | Request bodies over 8KB | MCP tools/call with document context, base64 files, or large JSON tool arguments regularly exceed 8KB | Exclude (add to excludedRules) |
| CrossSiteScripting_BODY | Request bodies with XSS patterns (<script>, javascript:, etc.) | Tool arguments that process HTML content (sanitization tools, web scraping tools) pass HTML fragments as inputs, triggering the XSS rule | Exclude; add custom rule that only inspects non-tool-argument fields if needed |
| GenericRFI_BODY | Request bodies with remote file inclusion patterns (URL-like values, file:// references) | MCP tool arguments for web-fetch or file tools contain URLs as values, triggering the RFI rule | Exclude; the MCP server should validate URL destinations at the application layer instead |
| SizeRestrictions_QUERYSTRING | Query strings over 2KB | MCP servers using HTTP GET for tool dispatch (non-standard) may hit this | Exclude only if using GET-based MCP transport |
// CDK: CommonRuleSet with MCP-specific exclusions
const commonRuleSet: wafv2.CfnWebACL.RuleProperty = {
name: "AWSCommonRules",
priority: 10,
// overrideAction: { count: {} }, // use during rollout; switch to none: {} after baselining
overrideAction: { none: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWSCommonRules",
sampledRequestsEnabled: true,
},
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesCommonRuleSet",
excludedRules: [
// These three rules cause false positives on MCP JSON-RPC payloads
{ name: "SizeRestrictions_BODY" }, // blocks tool calls with large context
{ name: "CrossSiteScripting_BODY" }, // blocks HTML-content tool arguments
{ name: "GenericRFI_BODY" }, // blocks URL-value tool arguments
],
// Optional: pin to a specific version to avoid rule updates changing behavior
// managedRuleGroupConfigs and version fields available in newer CDK versions
},
},
};
KnownBadInputsRuleSet: safe for MCP with no exclusions
The AWSManagedRulesKnownBadInputsRuleSet blocks exploitation attempts for specific CVEs and known bad input patterns: Log4j JNDI injection (${jndi:), SSRF payloads, and path traversal sequences. None of these patterns appear in normal MCP JSON-RPC traffic, making this rule group safe to attach without exclusions.
// CDK: KnownBadInputsRuleSet — no exclusions needed for MCP
const knownBadInputs: wafv2.CfnWebACL.RuleProperty = {
name: "KnownBadInputs",
priority: 11,
overrideAction: { none: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "KnownBadInputs",
sampledRequestsEnabled: true,
},
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesKnownBadInputsRuleSet",
// No excludedRules needed — these patterns don't appear in valid MCP traffic
},
},
};
Rules in this set that are particularly relevant to MCP servers:
| Rule | What it catches |
|---|---|
| Log4JRCE | Log4j JNDI injection in any part of the request — protects if MCP server uses Java logging |
| PROPFIND_METHOD | Blocks WebDAV PROPFIND method scanning — not MCP-relevant but zero cost to keep active |
| ExploitablePaths | Blocks requests to known vulnerable paths (e.g., .env, wp-admin) — stops automated scanner noise |
| JavaDeserializationRCE | Java deserialization attack payloads in request body — relevant if MCP server runs on JVM |
IP Reputation List
The AWSManagedRulesAmazonIpReputationList is AWS's own threat intelligence feed of IPs associated with botnets, malware C2, and scanning activity. It costs 25 WCU and is free. Attach it at the highest priority (lowest priority number) so known-bad IPs are blocked before consuming capacity in other rule evaluations.
// CDK: IP Reputation List — attach at priority 5 (before all other rules)
const ipReputationList: wafv2.CfnWebACL.RuleProperty = {
name: "AWSIpReputationList",
priority: 5,
overrideAction: { none: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "AWSIpReputationList",
sampledRequestsEnabled: true,
},
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesAmazonIpReputationList",
},
},
};
Complete WebACL with all three managed rule groups
// CDK: Complete WebACL for MCP server with three free managed rule groups
import * as wafv2 from "aws-cdk-lib/aws-wafv2";
const webAcl = new wafv2.CfnWebACL(this, "McpWebAcl", {
scope: "REGIONAL", // CLOUDFRONT in us-east-1 for CloudFront-fronted servers
defaultAction: { allow: {} },
visibilityConfig: {
cloudWatchMetricsEnabled: true,
metricName: "McpWebAclMetrics",
sampledRequestsEnabled: true,
},
rules: [
// Priority 1: Block known-bad IPs immediately (25 WCU)
{
name: "AWSIpReputationList",
priority: 1,
overrideAction: { none: {} },
visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "IpReputation", sampledRequestsEnabled: true },
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesAmazonIpReputationList",
},
},
},
// Priority 2: Rate limit POST requests (protects against tool-call abuse)
{
name: "McpPostRateLimit",
priority: 2,
action: { block: {} },
visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "McpPostRateLimit", sampledRequestsEnabled: true },
statement: {
rateBasedStatement: {
limit: 1000,
aggregateKeyType: "IP",
scopeDownStatement: {
andStatement: {
statements: [
{ byteMatchStatement: { fieldToMatch: { uriPath: {} }, searchString: "/mcp", positionalConstraint: "STARTS_WITH", textTransformations: [{ priority: 0, type: "LOWERCASE" }] } },
{ byteMatchStatement: { fieldToMatch: { method: {} }, searchString: "POST", positionalConstraint: "EXACTLY", textTransformations: [{ priority: 0, type: "UPPERCASE" }] } },
],
},
},
},
},
},
// Priority 10: Known bad inputs (200 WCU) — Log4j, SSRF, path traversal
{
name: "KnownBadInputs",
priority: 10,
overrideAction: { none: {} },
visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "KnownBadInputs", sampledRequestsEnabled: true },
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesKnownBadInputsRuleSet",
},
},
},
// Priority 20: Common rule set with MCP exclusions (700 WCU)
{
name: "AWSCommonRules",
priority: 20,
overrideAction: { none: {} },
visibilityConfig: { cloudWatchMetricsEnabled: true, metricName: "AWSCommonRules", sampledRequestsEnabled: true },
statement: {
managedRuleGroupStatement: {
vendorName: "AWS",
name: "AWSManagedRulesCommonRuleSet",
excludedRules: [
{ name: "SizeRestrictions_BODY" },
{ name: "CrossSiteScripting_BODY" },
{ name: "GenericRFI_BODY" },
],
},
},
},
],
// Total WCU: 25 + ~20 (rate rule) + 200 + 700 = ~945 / 5000 cap
});
Monitoring managed rule false positives
Even with the exclusions above, monitor for unexpected blocks during the first week in production. The CloudWatch metric BlockedRequests per rule name identifies which managed rule is triggering.
# CLI: List which managed rule sub-rules are triggering (past 24 hours)
aws cloudwatch get-metric-statistics \
--namespace AWS/WAFV2 \
--metric-name BlockedRequests \
--dimensions Name=WebACL,Value=McpWebAcl Name=Region,Value=us-east-1 Name=Rule,Value=AWSCommonRules \
--start-time $(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 3600 \
--statistics Sum \
--output table
# For sub-rule detail, query WAF logs in Athena:
-- SELECT terminatingruleid, terminatingrulematchdetails, COUNT(*) as count
-- FROM waf_logs
-- WHERE webaclname = 'McpWebAcl'
-- AND action = 'BLOCK'
-- AND terminatingruleid LIKE 'AWS%'
-- GROUP BY terminatingruleid, terminatingrulematchdetails
-- ORDER BY count DESC
Common failure modes
| Symptom | Cause | Fix |
|---|---|---|
| Large tool call payloads return 403 with no app error log | SizeRestrictions_BODY not excluded; blocking bodies over 8KB | Add to excludedRules in CommonRuleSet |
| Tool calls with HTML input return 403 | CrossSiteScripting_BODY detecting HTML content in JSON tool arguments | Add to excludedRules; validate HTML content at application layer |
| Managed rule group update breaks existing traffic | AWS auto-updated the managed rule group version and a new rule matches your traffic | Pin to a specific version: add version: "Version_1.0" to managedRuleGroupStatement; subscribe to AWS Security Bulletins for managed rule changes |
| WCU limit exceeded when adding new rules | All managed rule groups added; 5,000 WCU cap reached | Request WCU limit increase via Service Quotas (up to 10,000 WCU); remove rules with low hit count |
| IP reputation list blocking known-good scraper or CI IP | CI/CD provider IP in AWS threat intel feed (common for some cloud exit IPs) | Add an IP set allow-rule at priority 0 to pre-emptively allow specific CIDRs before reputation list evaluation |