Mutators¶
Available on API custom rules only
Mutators are an API-only feature. WebApp custom rules drive the browser via the seed: block instead — see Seeders.
Format — transform: block¶
Mutators live inside a transform: block, paired with a list of trigger: detectors. The trigger picks which requests to mutate (AND-combined by default), the mutators describe how to mutate them, and the resulting requests are replayed through detection.
Transformations are defined right after the Seeders, and before the detection and alerting.
You can write powerful trigger-then-mutate blocks to trigger the mutation on a specific request or response. They leverage the detectors and mutators that are covered later on this page.
Trigger Logic¶
IMPORTANT: The trigger list uses AND logic by default. All detectors in the list must match for the trigger to activate.
To use OR logic, you must explicitly use the or operator (see examples below).
Basic Example (AND logic)¶
transform:
trigger:
# ALL of these conditions must be true (AND logic)
- if: response.status_code
is: 200
- if: request.headers
key:
is: 'X-Forwarded-For'
value:
is: 'http://company.com'
mutate:
- key: request.headers
name: 'X-Forwarded-For'
value: 'http://localhost'
OR Logic Example¶
To use OR logic, wrap your conditions in an or block:
transform:
trigger:
# ANY of these conditions must be true (OR logic)
- if: or
or:
- if: response.status_code
is: 200
- if: response.status_code
is: 201
- if: response.status_code
is: 204
mutate:
- key: request.method
value: DELETE
Combining AND and OR Logic¶
You can combine AND and OR logic for complex conditions:
transform:
trigger:
# This AND that (OR of those)
- if: helpers.response.is_successful
is: true
- if: or # At least one of these must match
or:
- if: request.headers
key:
is: 'X-API-Version'
value:
is: 'V2'
- if: request.headers
key:
is: 'X-API-Version'
value:
is: 'V3'
mutate:
- key: request.headers
name: 'X-API-Version'
value: 'V1'
NOT Logic Example¶
Use the not operator to negate a condition:
transform:
trigger:
- if: response.status_code
is: 200
- if: not # This condition must NOT be true
not:
if: request.user
is: admin
mutate:
- key: request.user
value: admin
Complex Example¶
Combining AND, OR, and NOT operators:
transform:
trigger:
# Successful response
- if: helpers.response.is_successful
is: true
# AND (CREATE or UPDATE request)
- if: or
or:
- if: helpers.request.crud
is: CREATE
- if: helpers.request.crud
is: UPDATE
# AND NOT an admin user
- if: not
not:
if: request.user
is: admin
# AND specific path
- if: schema.path_ref
is: /users
mutate:
- key: request.method
value: DELETE
Nested OR within AND Example¶
transform:
trigger:
# Must be a POST or PUT request (OR)
- if: or
or:
- if: request.method
is: POST
- if: request.method
is: PUT
# AND must have JSON body with sensitive data (AND)
- if: request.body.json
jq: '.password != null or .ssn != null'
mutate:
- key: request.body.json
jq: 'del(.password, .ssn)'
Properties¶
trigger: The detectors to trigger the transform on the request or response. Detectorsmutate: The mutations to apply to the request and replay it. See Mutators
Available mutators¶
Request Body JSON Mutator¶
key: request.body.json
Transform the JSON body of the request using a JQ expression. JQ is the most powerful mutator in the DSL: it can add fields, remove fields, walk every string, restructure objects, etc.
Transformation language: JQ — see https://stedolan.github.io/jq/manual/.
In context — mass assignment via privileged-field injection:
Mass assignment via role injection in JSON body¶
Inject role: admin (and friends) into every successful JSON write issued by a regular user; alert if the modified request still succeeds — the API failed to filter dangerous fields.
Mass assignment (a.k.a. autobinding, BOPLA) happens when an API deserializes a client JSON payload directly into an internal model without a field allow-list. An attacker can then pass extra fields (is_admin, role, verified, subscription_tier, ...) and elevate their own state.
The deterministic test:
transform.triggerselects every successful CREATE / UPDATE issued bytester@example.comwhose JSON body does not yet carry an admin marker.transform.mutateaddsis_admin: trueandrole: adminusing a JQ expression.detectfires when the augmented request is still successful — proving the server accepted the privileged fields.
When to use: Any REST or GraphQL endpoint that accepts a JSON object describing a user-owned resource (profile, account, organization, settings). Adjust the JQ expression to the field names your model uses.
OWASP: API3:2023 BOPLA · CWE: CWE-915
Severity rationale: HIGH — successful mass assignment typically grants full admin impersonation or unbounded subscription upgrades.
Features used: transform, detect, request.body.json mutator (jq), helpers.request.crud
rule:
id: example-api-mass-assignment-role-injection
type: API
alert:
name: Mass assignment accepted privileged fields
context: |
A request augmented with `is_admin: true` and `role: admin` was
accepted by the API. The endpoint deserializes user-supplied
fields into the internal model without a field allow-list.
severity: HIGH
category: ACCESS_CONTROL
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: helpers.request.crud
in:
- CREATE
- UPDATE
- if: request.user
is: tester@example.com
mutate:
- key: request.body.json
jq: '. + {"is_admin": true, "role": "admin"}'
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/915.html
- https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
In context — fuzz every string in the body with a SQL-breaking suffix:
SQL injection in JSON body via JQ-targeted payloads¶
Walk every string field of a successful JSON request body and append a SQL-breaking payload to its value, then alert on a DBMS error in the response.
REST APIs that consume JSON usually deserialize it into typed objects. The classic concatenation-style SQLi sink is then accessible via any user-controlled string field that is passed unvalidated into a query.
The transform uses a JQ expression that walks the JSON structure and rewrites every string leaf by appending ' OR 1=1--. The detector then looks for the same DBMS error fingerprints used by the text-based SQLi rule.
When to use: Any REST or GraphQL endpoint that accepts JSON. Pair with the error-disclosure SQLi rule for text-encoded request bodies.
OWASP: A03:2021 Injection · CWE: CWE-89
Severity rationale: HIGH — same impact as classic SQLi.
Features used: transform, detect, request.body.json mutator (jq), response.body.text regex
rule:
id: example-api-sql-injection-jq-string-fields
type: API
alert:
name: SQL injection via JSON string field
context: |
A SQL-breaking suffix appended to every string value in the
request body caused the API to leak a DBMS error string in the
response.
severity: HIGH
category: INJECTION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.body.json
jq: any(.. | strings)
mutate:
- key: request.body.json
jq: (.. | strings) |= . + "' OR 1=1--"
detect:
- if: response.body.text
regex: .*(syntax error|sql error|ORA-[0-9]+|mysql_fetch|psql:|sqlite3.OperationalError|SQLSTATE\[).*
References:
Tip: test your JQ queries at https://jqplay.org/ before using them.
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.jq: JQ query to apply to the JSON body. See https://stedolan.github.io/jq/manual/
Request Body Text Mutator¶
key: request.body.text
You can use this mutator to change the body (as text) of the request before resending it.
Example¶
transform:
trigger:
- if: request.body.text
contains: 'hello'
mutate:
- key: request.body.text
values:
- 'injection 1'
- 'injection 2'
- 'injection 3'
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.value: The value to set.values: The values to set, generates multiple queries.regex_replace: Regex replace pattern.
Request Headers Mutator¶
key: request.headers
You can use this mutator to change the headers of the request before resending it. If no header is matched, a new header will be added and set to the value.
Example¶
transform:
trigger:
- if: schema.url
is: '/api/v1/tested/route'
mutate:
- key: request.headers
name: X-API-version
value: 'APIV2'
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.value: The value to set.values: The values to set, generates multiple queries.regex_replace: Regex replace pattern.name: The header name to match, supports regex.delete: Delete the matched headers.
Request Object Mutator¶
key: request.object
The detected object scalars (including custom scalars) in the request, with their kind, name and value.
Example¶
transform:
trigger:
- if: schema.url
is: '/api/v1/tested/route'
mutate:
- key: request.object
select:
type:
is: email
name:
is: 'admin_email'
value:
regex: .*@escape.tech
mutate:
regex_replace:
pattern: (.*)@escape.tech
replacement: \1@attacker.com
Request User Mutator¶
key: request.user
Replay the request as a different authenticated user (or as anonymous via drop_user: true). The user must be defined in your scan authentication settings.
mutate:
- key: request.user
value: attacker@example.com
# Remove authentication entirely:
mutate:
- key: request.user
drop_user: true
In context — horizontal authorization test on a write:
Horizontal privilege escalation via user swap on a write¶
Re-issue every successful CREATE / UPDATE / DELETE issued by tester@example.com as attacker@example.com and alert if the write still succeeds — the endpoint is missing horizontal authorization.
Horizontal authorization failures appear when user A can perform a state-changing operation on a resource that belongs to user B. Unlike BOLA (which is read-side), this rule targets writes: delete-someone-else's-record, update-someone-else's-profile, cancel-someone-else's-order.
The trigger picks successful writes by tester@example.com. The mutator switches request.user to attacker@example.com. If the replayed write is still successful, the API never re-checked ownership of the targeted resource against the new caller.
When to use: Any API where users can mutate records keyed by a path/body id. Pair with the platform's authentication configuration: define tester@example.com and attacker@example.com with disjoint resources before enabling this rule.
OWASP: API1:2023 BOLA · CWE: CWE-639
Severity rationale: HIGH — horizontal write escalation typically lets attackers delete or alter records belonging to other tenants and can be weaponized for denial-of-service or sabotage.
Features used: transform, detect, helpers.request.crud, request.user mutator
rule:
id: example-api-horizontal-privilege-escalation-write
type: API
alert:
name: Horizontal privilege escalation on write
context: |
A successful write issued by `tester@example.com` was replayed as
`attacker@example.com` and still succeeded. The endpoint is
missing a horizontal authorization check.
severity: HIGH
category: ACCESS_CONTROL
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: helpers.request.crud
in:
- CREATE
- UPDATE
- DELETE
- if: request.user
is: tester@example.com
mutate:
- key: request.user
value: attacker@example.com
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- https://cwe.mitre.org/data/definitions/639.html
In context — BOLA via user swap with fingerprint comparison:
BOLA via user swap with fingerprint comparison¶
Replay every successful authenticated request as a different user and flag responses that succeed AND return the same body fingerprint — classic Broken Object Level Authorization.
Broken Object Level Authorization (BOLA / IDOR) is the #1 API risk in the OWASP API Top 10. The pattern is straightforward: endpoint X correctly authenticates tester@example.com and returns its own data, but never re-checks ownership when a different user calls the same URL — so attacker@example.com gets the same response.
This rule encodes the test deterministically:
transform.triggerselects every successful request issued bytester@example.com.transform.mutatere-issues that request withrequest.userset toattacker@example.com.detectfires when the replayed response is also successful AND its body fingerprint matches the original — meaning the API returned the first user's data to the second user.
When to use: Any REST or GraphQL endpoint that returns user-scoped data. Pair with your authentication configuration in the Escape platform: define at least two users with disjoint resources for the fingerprint check to be meaningful.
OWASP: API1:2023 BOLA · CWE: CWE-639
Severity rationale: HIGH — cross-tenant data leakage is typically a reportable security incident and a frequent cause of GDPR / SOC 2 findings.
Features used: transform, detect, request.user mutator, helpers.fingerprints.same, helpers.response.is_successful
rule:
id: example-api-bola-user-swap-fingerprint
type: API
alert:
name: Possible BOLA via user swap
context: |
A successful authenticated response was returned with an identical
body fingerprint when the same request was replayed as a different
user. The endpoint is likely missing an object-level authorization
check.
severity: HIGH
category: ACCESS_CONTROL
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.user
is: tester@example.com
mutate:
- key: request.user
value: attacker@example.com
detect:
- if: helpers.response.is_successful
is: true
- if: helpers.fingerprints.same
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- https://cwe.mitre.org/data/definitions/639.html
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.value: The value to set.values: The values to set, generates multiple queries.regex_replace: Regex replace pattern.drop_user: Remove the user authentication from the request.
Schema URL Mutator¶
key: schema.url
You can use this mutator to change the URL of the request before resending it.
Example¶
transform:
trigger:
- if: schema.url
is: 'https://api.example.com/api/v1/tested/route'
mutate:
- key: schema.url
mutate:
value: 'https://api2.example.com/api/v2/'
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.value: The value to set.values: The values to set, generates multiple queries.regex_replace: Regex replace pattern.
Schema Path Reference Mutator¶
key: schema.path_ref
You can use this mutator to change the operation name in GraphQL or the path in REST (keeping the domain) before resending it.
Example¶
transform:
trigger:
- if: schema.path_ref
is: '/api/v1/tested/route'
mutate:
- key: schema.path_ref
mutate:
value: '/api/v2/tested/route'
Properties¶
use_extraction: If True, variable references between {{ }} will be replace with the extracted value (If exists). The string representation of the variable will be used without any extra-processing.value: The value to set.values: The values to set, generates multiple queries.regex_replace: Regex replace pattern.