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 groupWCU costCost (USD/month)MCP recommendation
AWSManagedRulesCommonRuleSet700FreeYes — exclude 3 rules
AWSManagedRulesKnownBadInputsRuleSet200FreeYes — no exclusions needed
AWSManagedRulesAmazonIpReputationList25FreeYes — no exclusions needed
AWSManagedRulesAnonymousIpList50FreeOptional — blocks Tor/VPN exit nodes (tradeoff: legitimate users on VPN get blocked)
AWSManagedRulesBotControlRuleSet (Common)50$10/month + $1/1M requestsUse only for browser-based MCP clients; breaks API MCP clients
AWSManagedRulesBotControlRuleSet (Targeted)100$10/month + $1/1M requestsHigher bot signal accuracy; same caveats as Common
AWSManagedRulesATPRuleSet (account takeover)50$10/month + $1/1M requestsOnly for MCP servers with login/token endpoints
Custom rate rules (2 rules)~20Included in WebACL costYes — 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 nameWhat it blocksWhy it fails on MCPRecommendation
SizeRestrictions_BODYRequest bodies over 8KBMCP tools/call with document context, base64 files, or large JSON tool arguments regularly exceed 8KBExclude (add to excludedRules)
CrossSiteScripting_BODYRequest 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 ruleExclude; add custom rule that only inspects non-tool-argument fields if needed
GenericRFI_BODYRequest 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 ruleExclude; the MCP server should validate URL destinations at the application layer instead
SizeRestrictions_QUERYSTRINGQuery strings over 2KBMCP servers using HTTP GET for tool dispatch (non-standard) may hit thisExclude 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:

RuleWhat it catches
Log4JRCELog4j JNDI injection in any part of the request — protects if MCP server uses Java logging
PROPFIND_METHODBlocks WebDAV PROPFIND method scanning — not MCP-relevant but zero cost to keep active
ExploitablePathsBlocks requests to known vulnerable paths (e.g., .env, wp-admin) — stops automated scanner noise
JavaDeserializationRCEJava 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

SymptomCauseFix
Large tool call payloads return 403 with no app error logSizeRestrictions_BODY not excluded; blocking bodies over 8KBAdd to excludedRules in CommonRuleSet
Tool calls with HTML input return 403CrossSiteScripting_BODY detecting HTML content in JSON tool argumentsAdd to excludedRules; validate HTML content at application layer
Managed rule group update breaks existing trafficAWS auto-updated the managed rule group version and a new rule matches your trafficPin 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 rulesAll managed rule groups added; 5,000 WCU cap reachedRequest 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 IPCI/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