# Mutators

> [!NOTE]
>
> **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](<https://docs.escape.tech/documentation/dast/custom-rules/seeders/index.md>).

## 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)

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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:

```yaml
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

```yaml
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](<https://docs.escape.tech/documentation/dast/custom-rules/detectors/index.md>)
- `mutate`: The mutations to apply to the request and replay it. See [Mutators](<https://docs.escape.tech/documentation/dast/custom-rules/mutators/index.md>)

## Available Mutators

### Method Mutator

`key: request.method`

Change the HTTP method of the request before resending it.

```yaml
mutate:
  - key: request.method
    value: DELETE

# Multiple methods (one mutated request per value):
mutate:
  - key: request.method
    values: [POST, PUT, DELETE, PATCH]
```

In context: horizontal privilege escalation via user swap 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](<https://cwe.mitre.org/data/definitions/639.html>)

**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`

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/639.html](<https://cwe.mitre.org/data/definitions/639.html>)

#### Properties

- `value`: The value to set.
- `values`: The values to set, generates multiple queries.

---

### 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

**Transformation language:** JQ: see [https://stedolan.github.io/jq/manual/](<https://stedolan.github.io/jq/manual/>).

```yaml
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 a server-side data 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 doesn't 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 API uses.

**OWASP:** API3:2023 BOPLA · **CWE:** [CWE-915](<https://cwe.mitre.org/data/definitions/915.html>)

**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`

```yaml
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 a server-side data 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://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/915.html](<https://cwe.mitre.org/data/definitions/915.html>)
- [https://cheatsheetseries.owasp.org/cheatsheets/Mass\_Assignment\_Cheat\_Sheet.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](<https://docs.escape.tech/documentation/dast/custom-rules/examples-api/#example-api-injection-sql-injection-error-disclosure>) for text-encoded request bodies.

**OWASP:** A03:2021 Injection · **CWE:** [CWE-89](<https://cwe.mitre.org/data/definitions/89.html>)

**Severity rationale:** HIGH: same impact as classic SQLi.

**Features used:** `transform`, `detect`, `request.body.json mutator (jq)`, `response.body.text regex`

```yaml
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:**

- [https://owasp.org/Top10/A03\_2021-Injection/](<https://owasp.org/Top10/A03_2021-Injection/>)
- [https://cwe.mitre.org/data/definitions/89.html](<https://cwe.mitre.org/data/definitions/89.html>)

**Tip:** test your JQ queries at [https://jqplay.org/](<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/](<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

```yaml
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

```yaml
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

```yaml
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.

```yaml
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](<https://cwe.mitre.org/data/definitions/639.html>)

**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`

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/639.html](<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](<https://github.com/icarossio/escape-technologies/issues/1> "GitHub Issue: icarossio/escape-technologies #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](<https://cwe.mitre.org/data/definitions/639.html>)

**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`

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/639.html](<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

```yaml
transform:
    trigger:
      - if: schema.url
        is: 'https://api.example.com/api/v1/tested/route'
    mutate:
      - key: schema.url
        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

```yaml
transform:
    trigger:
      - if: schema.path_ref
        is: '/api/v1/tested/route'
    mutate:
      - key: schema.path_ref
        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.

---
