Exceptions
Look up the fields of a WAF exception, its fifteen match zones, the Tuning screen, and the API and CLI that manage it.
An exception exempts one part of a request from one Web Application Firewall (WAF) internal rule, or from all of them, so a pattern that rule would score stops being scored there. The rest of the rule keeps working: everywhere the exception does not match, the rule fires as before. Azion Console calls the object an allowed rule and lists it on the Allowed Rules tab of a rule set; the Azion API and the Azion CLI call the same object an exception.
An exception belongs to one rule set and runs where that rule set runs, which is a Firewall with the WAF module active. It can be written from a request WAF already marked as a threat, or created ahead of time for a test.
This page lists the fields of an exception, the fifteen match zones its conditions take, the Tuning screen that produces exceptions in bulk, and the API and CLI surfaces that manage them.
Fields
An exception carries six fields a request can set. Three more are set by the platform and are returned in a response, never sent.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | integer | Read-only | — | The identifier every later call uses |
rule_id | integer | No | 0 | The internal rule the exception applies to. 0 means all rules. The Console renders it as the Rule ID dropdown |
name | string, 1 to 255 characters | Yes | — | Free text saying what the exception is for. The Console renders it as the Description field |
path | string, up to 255 characters, nullable | No | — | Restricts the exception to one request path. An exception with no path applies wherever its conditions match |
conditions | array of objects, at least 1 entry | Yes | — | The parts of the request the exception covers. The Console renders one entry as the Condition field |
operator | string | No | contains | How the strings in the exception are compared: contains or regex |
active | boolean | No | true | Whether the exception is in force. An inactive exception stays on the rule set and stops exempting anything |
last_editor | string | Read-only | — | The email of the account that last changed the exception |
last_modified | date-time | Read-only | — | When the exception last changed |
The smallest exception the API accepts carries a name and one condition. It then applies to all rules, on every path, with contains, and is active.
rule_id takes one identifier from the internal rule set, and 0 stands for every rule in it. For the list of identifiers and what each rule detects, refer to WAF Rule Sets. The same identifiers name the rows on the Tuning screen, and the Console shows each rule’s own description beside its identifier.
last_editor and last_modified are returned by the API and the CLI. The Console form does not show them.
Conditions
A condition names one match zone, and where the zone is specific, the string that zone applies to. The shape of a condition is fixed by its match value, and there are three.
| Shape | Keys | Match zones that take it |
|---|---|---|
| Generic | match | The nine zones whose name carries no specific |
| Specific on a name | match and name | specific_body_form_field_name, specific_http_header_name, specific_query_string_name |
| Specific on a value | match and value | specific_body_form_field_value, specific_http_header_value, specific_query_string_value |
A condition’s name or value holds between 1 and 255 characters. A specific_* condition sent with neither returns 400 with 10059 Required Field, and one sent with an empty string returns 400 with 10018 Blank Field.
One exception carries more than one condition, and every entry is kept.
Operator
operator decides how the strings in the exception are compared, and it governs two places at once: the exception’s path and the condition’s name or value.
Under the default contains, both are substrings and neither is read as a pattern.
Under regex, both are regular expressions, and each is validated separately. A malformed path returns 400 with 26008 Invalid Regex Value and a source pointer of /data/path; a malformed condition name returns the same code with a pointer of /data/conditions/0/name. There is no way to make one a regular expression and the other a literal — regex widens both together, so an exception that needs a pattern in its condition also needs one in its path.
The Console renders the choice as the Operator dropdown, with the placeholder Select an operator. There is no separate regex switch anywhere in the interface.
Match zones
A match zone is the part of a request a condition compares. The API takes it as conditions[].match. The Azion Console labels the field Condition, not match zone, and seeds it with Any HTTP Header Value.
There are fifteen, listed below in the order the Console offers them. An option beginning Specific reveals an extra Name or Value field, and which of the two appears is fixed by the option.
| Console option | API value | Condition shape | What it compares |
|---|---|---|---|
| Any HTTP Header Value | any_http_header_value | Generic | The value of every request header, such as Mozilla/5.0 or application/json |
| Any HTTP Header Name | any_http_header_name | Generic | The name of every request header, such as User-Agent or Authorization |
| Specific HTTP Header Value | specific_http_header_value | Specific on a value | The value in the condition, against the value of every request header. It carries no header name |
| Specific HTTP Header Name | specific_http_header_name | Specific on a name | The name in the condition, such as cookie, against the name of a request header |
| Any Query String Value | any_query_string_value | Generic | The value of every query string parameter. In ?id=123&user=admin, 123 and admin |
| Any Query String Name | any_query_string_name | Generic | The name of every query string parameter. In ?id=123&user=admin, id and user |
| Specific Query String Value | specific_query_string_value | Specific on a value | The value in the condition, against the value of every query string parameter |
| Specific Query String Name | specific_query_string_name | Specific on a name | The name in the condition, such as token, against the name of a query string parameter |
| Body Form Field Value | body_form_field_value | Generic | The value of every form field in the request body |
| Body Form Field Name | body_form_field_name | Generic | The name of every form field in the request body |
| Specific Body Form Field Value | specific_body_form_field_value | Specific on a value | The value in the condition, against the value of every form field in the body |
| Specific Body Form Field Name | specific_body_form_field_name | Specific on a name | The name in the condition, such as api_key, against the name of a form field in the body |
| Any URL | any_url | Generic | The request URL |
| Raw Body | raw_body | Generic | The uninterpreted request body, such as a JSON or an XML payload |
| File Extension | file_extension | Generic | The file extension in the request, such as .php, .exe or .sh |
A value outside this set returns 400 with 10039 Invalid Choice and a source pointer naming the condition, such as /data/conditions/0/match.
specific_http_header_value does not name a header. Its two keys are match and value, so the string it carries is compared against header values, not against a header name. A condition of {"match": "specific_http_header_value", "value": "Cookie"} therefore matches any header whose value contains Cookie, which is not the same thing as an exception for the Cookie header.
specific_http_header_name with a name is the only shape that scopes an exception to one named header. The API stores the string exactly as it is sent, so cookie, Cookie, and HTTP_COOKIE are all accepted and all read back unchanged.
Tuning
Tuning is a tab on a rule set. It lists the requests each internal rule matched over a chosen window, grouped by rule ID, and turns selected records into exceptions in bulk. It is where a false positive is found before it is allowed.
A query needs a domain. The other filters narrow it.
| Filter | What it narrows |
|---|---|
| Domain | The workload or domain whose requests are read. Required |
| Time Range | The window the requests are read over. The options are Last 1 hour, Last 3 hours, Last 6 hours, Last 12 hours, Last day, Last 2 days and Last 3 days. Last 3 days is the longest window available |
| Network Lists | The requests whose source address a Network Lists entry holds |
| IP Address | The requests from one or more source addresses |
| Country | The country the request came from |
Azion Console refuses two combinations: IP Address with an IP/CIDR network list filter, and Country with a Countries network list filter.
The result is one row per internal rule that matched, carrying the columns Rule ID, Hits, Paths, IPs, Countries, Top 10 IP Addresses and Top 10 Countries, above a count of the records found.
Selecting a row opens More Details, which lists the occurrences behind that rule ID and narrows them further by network list, country, IP address, and Path.
Selecting rows and choosing Allow Rules writes them to the rule set’s allowed rules. The confirmation asks for the reason these rules are being allowed, and states that a separate rule is created for each possible attack on each URI, so one selection produces several exceptions. The Allowed Rules tab carries a Create from Tuning button back into this screen.
For the procedure, refer to Tune a WAF rule set. For why tuning is repeated rather than done once, refer to Scoring and modes.
API
Every operation is authenticated and sits under https://api.azion.com/v4/workspace/wafs/{waf_id}/exceptions. A request carries a token from Personal Tokens in the Authorization header under the Token scheme, and a request with a body also carries Content-Type: application/json. The API manages exceptions independently of the Console, so one can be created, changed, or removed from a script.
| Operation | Method and path |
|---|---|
| Create an exception | POST /wafs/{waf_id}/exceptions |
| List exceptions | GET /wafs/{waf_id}/exceptions |
| Retrieve an exception | GET /wafs/{waf_id}/exceptions/{exception_id} |
| Replace an exception | PUT /wafs/{waf_id}/exceptions/{exception_id} |
| Update part of an exception | PATCH /wafs/{waf_id}/exceptions/{exception_id} |
| Delete an exception | DELETE /wafs/{waf_id}/exceptions/{exception_id} |
A create, a replace, a partial update, and a delete answer 202. A read answers 200.
The operations above are ordinary API calls, so exception management belongs in a delivery pipeline like any other configuration change. The evidence an exception is written from comes from a different surface: Tuning is an Azion Console screen, and the API does not expose it. A script reads that evidence from Real-Time Events, where wafMatch names the internal rules a request matched and wafScore reports the score each threat family reached. It then creates an exception for each false positive those records confirm. The Azion CLI is the other surface, and its --conditions flag does not work on 4.23.0, so a script uses --file or the API directly.
Create an exception
The response carries 202, and not 201:
The state of pending says the exception was accepted, and the id in data is the handle every later call uses. last_editor carries the email of the account that last changed the exception, and the value above is a placeholder.
The same call with the body below creates an exception scoped to the query string parameter named q. The response carries 202, a new id of 123457, and the condition echoed unchanged.
The body below scopes the same exception to a query string value instead. It answers 202 with an id of 123458.
List exceptions
The response carries 200 and the collection envelope below, with one exception per entry under results, each in the shape the create response carries.
| Field | What it carries |
|---|---|
count | Exceptions the rule set holds |
total_pages | Pages the result divides into, at the current page_size |
page | The page this response carries |
page_size | Exceptions per page. Defaults to 10 |
next | The URL of the following page, or null |
previous | The URL of the preceding page, or null |
results | One exception per entry, carrying the fields listed in Fields |
The endpoint accepts created_at__gte, created_at__lte, description, fields, id, last_editor, last_modified__gte, last_modified__lte, ordering, page, page_size, path, and search as query parameters. page_size runs from 1 to 100 and defaults to 10. A value above 100 returns 400 with 10097 Invalid Page Size.
Retrieve, update, and delete an exception
GET /wafs/{waf_id}/exceptions/{exception_id} answers 200 and returns the exception under data, with no state key.
PUT /wafs/{waf_id}/exceptions/{exception_id} replaces an exception and takes the same body as a create. It answers 202 with a state of pending. The conditions array is replaced rather than merged, so a replace sends every condition the exception is to keep.
PATCH /wafs/{waf_id}/exceptions/{exception_id} takes only the fields sent and answers 202. A body of {"active": false} deactivates an exception and leaves the rest of it as it was.
DELETE /wafs/{waf_id}/exceptions/{exception_id} answers 202 with an empty body.
CLI
The Azion CLI manages exceptions under the waf-exceptions noun. The flags below are the ones Azion CLI 4.23.0 carries, without the global flags every command takes.
| Command | What it does |
|---|---|
azion create waf-exceptions | Creates an exception on a rule set |
azion list waf-exceptions | Lists the exceptions a rule set holds |
azion describe waf-exceptions | Returns one exception |
azion update waf-exceptions | Changes an exception |
azion delete waf-exceptions | Removes an exception |
azion create waf-exceptions and azion update waf-exceptions share their flags, except that --exception-id belongs to the update.
| Flag | What it sets |
|---|---|
--waf-id | The rule set the exception belongs to |
--name | The exception name |
--rule-id | The internal rule the exception applies to |
--path | The path the exception is restricted to |
--operator | regex or contains |
--active | true or false. Defaults to true |
--conditions | The conditions, in JSON. Refer to the caution below before using it |
--file | A JSON file carrying the body, or - to read the body from standard input |
--exception-id | The exception to change. azion update waf-exceptions only |
azion list waf-exceptions takes --waf-id, --details, --filter to filter by name, --order-by, --page with a default of 1, and --page-size with a default of 50. azion describe waf-exceptions and azion delete waf-exceptions each take --waf-id and --exception-id.
Write the body to a file:
Then create the exception from it:
azion describe waf-exceptions returns one exception, with the keys sorted by the CLI:
Errors
A rejected request returns an errors array. Each entry carries a code, a title, a detail, the status, and a source pointer naming the field the rejection is about. For a condition the pointer indexes into the array, so /data/conditions/0/name names the first condition:
| Code | Title | Status | What causes it | What to do |
|---|---|---|---|---|
10004 | Not Found | 404 | A waf_id or an exception_id that does not exist | Check every segment of the path |
10009 | Unsupported Media Type | 415 | A write sent with a Content-Type other than application/json | Send Content-Type: application/json |
10018 | Blank Field | 400 | A condition name or value that is an empty string | Send 1 to 255 characters |
10039 | Invalid Choice | 400 | A value outside an enum: rule_id, operator, or a condition’s match | Send one of the values listed in Fields and Match zones. The source pointer names the field |
10046 | Max Length | 400 | A name longer than 255 characters | Shorten the name |
10049 | Min Length List Field | 400 | An empty conditions array, which is what azion create waf-exceptions --conditions always sends | Send at least one condition, through the API or azion create waf-exceptions --file |
10059 | Required Field | 400 | A required field is absent, such as a specific_* condition with no name | Add the field the source pointer names |
10097 | Invalid Page Size | 400 | A page_size above 100 on a list request | Ask for 100 or fewer |
26008 | Invalid Regex Value | 400 | operator is regex and the exception’s path, or a condition’s name or value, is not a valid pattern | Fix the pattern the source pointer names. The entry’s meta.regex_value repeats the value that failed |
A rule_id of 0 is not an error. It is the legal value meaning all rules, and it answers 202 like any other.