Trying Out AWS WAF's New "Pre-Parse Text Transformation" Feature with the CLI
This page has been translated by machine translation. View original
Introduction
With the update on July 29, 2026, pre-parse text transformations became available in AWS WAF.
Pre-parse text transformations are a feature that inserts normalization before the query string is parsed into key-value pairs. Conventional text transformations, when inspecting query arguments, operate on the values after parsing. Because of this, the handling of delimiters cannot be reflected in the parsing result. The official documentation explains that pre-parse text transformations can be applied only to statements that inspect SingleQueryArgument or AllQueryArguments.
With a rule without any transformation, neither the semicolon-delimited nor the URL-encoded semicolon-delimited query matched. The results after configuring pre-parse text transformations are as follows.
| Query delimiter | Applied pre-parse text transformation (Type) |
|---|---|
| Semicolon-delimited | REPLACE_SEMICOLONS_WITH_AMPERSANDS |
| URL-encoded semicolon-delimited | URL_DECODE → REPLACE_SEMICOLONS_WITH_AMPERSANDS |
Verification Details
Configuring the Count action and rule labels
The verification environment is as follows.
| Item | Value |
|---|---|
| AWS CLI | 2.36.11 |
| Region | ap-northeast-1 |
| Web ACL scope | REGIONAL |
| Backend | ALB with fixed response |
| Log destination | CloudWatch Logs |
So that a block action would not interrupt the evaluation of subsequent rules, I set the test rules to the Count action and attached a WAF label for verification to each rule. WAF log records and HTTP requests were matched up using the query string as the key. Because Count does not block requests, I used labels and nonTerminatingMatchingRules as the basis for judgment instead of the HTTP status. I placed no blocking rules in the test Web ACL and configured only these Count rules for verification.
# Get the LockToken
LOCK_TOKEN=$(aws wafv2 get-web-acl \
--name preparse-test-webacl \
--id 11111111-2222-3333-4444-55555555aaaa \
--scope REGIONAL \
--region ap-northeast-1 \
--query LockToken --output text)
# Set the Count rules and labels
aws wafv2 update-web-acl \
--name preparse-test-webacl \
--id 11111111-2222-3333-4444-55555555aaaa \
--scope REGIONAL \
--default-action '{"Allow":{}}' \
--lock-token "$LOCK_TOKEN" \
--region ap-northeast-1 \
--visibility-config 'SampledRequestsEnabled=true,CloudWatchMetricsEnabled=true,MetricName=preparse-test-webacl' \
--rules file://rules.json
# Check the saved configuration
aws wafv2 get-web-acl \
--name preparse-test-webacl \
--id 11111111-2222-3333-4444-55555555aaaa \
--scope REGIONAL \
--region ap-northeast-1
For comparison, I also added two control rules (ControlNoPreparsePlain, ControlNoPreparseEncoded) that have the same matching conditions but no pre-parse transformation. Below are the two rules with pre-parse transformations, excerpted from the rule configuration passed to --rules.
[
{
"Name": "PreparseSemicolon",
"Priority": 2,
"Statement": {
"ByteMatchStatement": {
"SearchString": "cHJlcGFyc2UtcGxhaW4=",
"FieldToMatch": { "SingleQueryArgument": { "Name": "target_plain" } },
"TextTransformations": [ { "Priority": 0, "Type": "NONE" } ],
"PositionalConstraint": "EXACTLY",
"PreParseTextTransformations": [
{ "Priority": 0, "Type": "REPLACE_SEMICOLONS_WITH_AMPERSANDS" }
]
}
},
"Action": { "Count": {} },
"RuleLabels": [ { "Name": "test:preparse-semicolon" } ]
},
{
"Name": "PreparseUrlDecodeSemicolon",
"Priority": 4,
"Statement": {
"ByteMatchStatement": {
"SearchString": "cHJlcGFyc2UtZW5jb2RlZA==",
"FieldToMatch": { "SingleQueryArgument": { "Name": "target_encoded" } },
"TextTransformations": [ { "Priority": 0, "Type": "NONE" } ],
"PositionalConstraint": "EXACTLY",
"PreParseTextTransformations": [
{ "Priority": 0, "Type": "URL_DECODE" },
{ "Priority": 1, "Type": "REPLACE_SEMICOLONS_WITH_AMPERSANDS" }
]
}
},
"Action": { "Count": {} },
"RuleLabels": [ { "Name": "test:preparse-url-decode-semicolon" } ]
}
]
The SearchString values are Base64-encoded strings. cHJlcGFyc2UtcGxhaW4= corresponds to preparse-plain, and cHJlcGFyc2UtZW5jb2RlZA== corresponds to preparse-encoded. The excerpt omits the VisibilityConfig of each rule. After the update, I confirmed with get-web-acl that PreParseTextTransformations and RuleLabels had been saved.
Checking semicolon-delimited queries
Targeting the rule with REPLACE_SEMICOLONS_WITH_AMPERSANDS configured, I sent semicolon-delimited and ampersand-delimited requests.
| Case | Request query | HTTP result | Labels confirmed in WAF log | Non-terminating matching rules |
|---|---|---|---|---|
| Semicolon-delimited | q=safe;target_plain=preparse-plain |
200 / ALLOW | test:preparse-semicolon |
PreparseSemicolon |
| Normal ampersand-delimited (plain) | q=safe&target_plain=preparse-plain |
200 / ALLOW | test:control-no-preparse-plain, test:preparse-semicolon |
ControlNoPreparsePlain, PreparseSemicolon |
With the semicolon-delimited request, only the rule with the pre-parse transformation matched, and the control rule's label was not attached. With the ampersand-delimited request, both matched, so the labels show that the presence or absence of the transformation made the difference.
Below is an excerpt of the WAF log corresponding to the semicolon-delimited request. The labels value has a prefix that includes the Web ACL name.
{
"action": "ALLOW",
"terminatingRuleId": "Default_Action",
"nonTerminatingMatchingRules": [
{ "ruleId": "PreparseSemicolon", "action": "COUNT", "ruleMatchDetails": [] }
],
"httpRequest": { "args": "q=safe;target_plain=preparse-plain" },
"labels": [
{ "name": "awswaf:123456789012:webacl:preparse-test-webacl:test:preparse-semicolon" }
]
}
Checking URL-encoded semicolons
Targeting the rule that chains URL_DECODE and REPLACE_SEMICOLONS_WITH_AMPERSANDS, I sent requests for the following two cases.
| Case | Request query | HTTP result | Labels confirmed in WAF log | Non-terminating matching rules |
|---|---|---|---|---|
| URL-encoded semicolon-delimited | q=safe%3Btarget_encoded=preparse-encoded |
200 / ALLOW | test:preparse-url-decode-semicolon |
PreparseUrlDecodeSemicolon |
| Normal ampersand-delimited (encoded) | q=safe&target_encoded=preparse-encoded |
200 / ALLOW | test:control-no-preparse-encoded, test:preparse-url-decode-semicolon |
ControlNoPreparseEncoded, PreparseUrlDecodeSemicolon |
With the request containing a URL-encoded semicolon, only the rule chaining decoding and replacement matched, and the control rule's label was not attached. With the ampersand-delimited request, both rules matched.
Summary
If you have been combining regular expressions or multiple rules to handle differences in query string delimiters and encoding, the newly added pre-parse text transformations may let you manage this more simply. Each pre-parse text transformation adds 10 WCU, but as long as the web ACL total is within the default quota of 1,500 WCU, you can use it at no additional charge.
