Skip to content

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. Detectors
  • mutate: 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/.

mutate:
  - key: request.body.json
    jq: '. + {"is_admin": true, "role": "admin"}'

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:

  1. transform.trigger selects every successful CREATE / UPDATE issued by tester@example.com whose JSON body does not yet carry an admin marker.
  2. transform.mutate adds is_admin: true and role: admin using a JQ expression.
  3. detect fires 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:

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:

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:

  1. transform.trigger selects every successful request issued by tester@example.com.
  2. transform.mutate re-issues that request with request.user set to attacker@example.com.
  3. detect fires 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:

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.