Skip to content

Detectors

Detectors are conditions matched against requests, responses, or (on WebApp) the rendered page. They appear in the detect: block of every rule, and in the trigger: block of an API transform: or extractors: entry.

Both surfaces share the matcher grammar (is, is_not, in, contains, regex, gt, lt, jq, ...) but expose different if: keys: API rules inspect HTTP-level data, WebApp rules inspect the browser state.

Detectors available on API

Scan type detector

if: scan.type

Match against the type of scan being performed.

Matcher Type: ScanTypeMatcher · Operators: is, is_not, in · Valid values: REST, GRAPHQL

detect:
  - if: scan.type
    is: REST

# OR
detect:
  - if: scan.type
    in: [REST, GRAPHQL]

Properties

  • is: The scan type is exactly this
  • is_not: The scan type is not this type
  • in: The scan type is in this list

CRUD detector

if: helpers.request.crud

Use this to select against the detected CRUD operation of the request.

Example

detect:
  - if: helpers.request.crud
    in:
      - CREATE
      - UPDATE

Properties

  • is: Condition is the request is this CRUD operation
  • is_not: Condition is the request is not this CRUD operation
  • in: Condition is the request is in this list of CRUD operations (exact match)

Response status detector

if: response.status_code

Match against the HTTP response status code.

Matcher type: IntegerMatcher · Operators: is, is_not, gt, lt, in

detect:
  - if: response.status_code
    in: [200, 206]

For simple success/failure checks prefer helpers.response.is_successful (it covers the full 2xx range and is more readable).

In context — partial-content trick for SQL dump probing:

Exposed SQL dumps reachable over HTTP

Probe a curated list of common SQL dump filenames at the root of the target and alert if the server returns the file with a SQL DDL/DML signature in the body.

Backups left in the web root are one of the highest-impact and cheapest-to-find exposures in API testing. This rule probes a curated list of common backup filenames (backup.sql, dump.sql, db.sql, mysqldump.sql, ...) using a Range: bytes=0-3000 header so the download stays small even if the file is multi-gigabyte. Extend the seed: list with additional paths to suit your target.

Detection requires both a 200/206 status and a body containing typical SQL DDL/DML keywords (DROP TABLE, CREATE TABLE, INSERT INTO, LOCK TABLE). The status code and body regex are AND-combined so a generic 200 page with no SQL content does not fire.

Sourced from the in-tree exposed_sql_dumps simplecheck — promoted here as a canonical example of the seed-+-detect pattern.

When to use: Run against every public host. Especially useful immediately after deployments, when build artifacts are sometimes left in /public by accident.

OWASP: A05:2021 Security Misconfiguration · CWE: CWE-540

Severity rationale: HIGH — exposed dumps regularly contain credentials, password hashes, and full PII.

Features used: seed, detect, rest seeder with custom Range header, response.body.text regex, response.status_code in

rule:
  id: example-api-exposed-sql-dumps
  type: API
  alert:
    name: Exposed SQL dump
    context: |
      A request to a common SQL dump filename returned a 200/206 with
      DDL/DML keywords in the body, indicating a database backup
      reachable over HTTP.
    severity: HIGH
    category: INFORMATION_DISCLOSURE
  seed:
  - protocol: rest
    method: GET
    path: /backup.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /database.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /dump.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /db.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /mysqldump.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /db_backup.sql
    headers:
      Range: bytes=0-3000
  - protocol: rest
    method: GET
    path: /wp-content/uploads/dump.sql
    headers:
      Range: bytes=0-3000
  detect:
  - if: response.body.text
    regex: .*((DROP|CREATE|(?:UN)?LOCK) TABLE|INSERT INTO).*
  - if: response.status_code
    in:
    - 200
    - 206

References:

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer

Response duration detector

if: response.duration_ms

Use this to compare the duration of the request in milliseconds.

Example

detect:
  - if: response.duration_ms
    gt: 200

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer

Schema authentication detector

if: schema.need_authentication

Use this to select whether or not the schema requires authentication.

Example

detect:
  - if: schema.need_authentication
    is: false

Properties

  • is: Condition is true
  • is_not: Condition is false

Request authentication detector

if: request.is_authenticated

Use this to select whether the request is authenticated.

Example

detect:
  - if: request.is_authenticated
    is: true

Properties

  • is: Condition is true
  • is_not: Condition is false

Schema path reference detector

if: schema.path_ref

Match the operation name (GraphQL) or the path (REST) of the request.

Matcher type: StringMatcher · Operators: is, is_not, contains, regex, in

detect:
  - if: schema.path_ref
    contains: /admin/

# Or regex over a versioned path:
detect:
  - if: schema.path_ref
    regex: /api/v[0-9]+/admin/.*

In context — anonymous admin route detection:

Anonymous request to /admin succeeded

Flag any successful response on a path containing /admin/ issued without authentication — the cheapest deterministic check for an accidentally-public administrative surface.

Almost no application intentionally exposes admin functionality to anonymous callers. When an endpoint matching /admin/ returns a 2xx to a public request, the most likely explanations are:

  • missing authentication middleware,
  • a route that bypasses the global auth filter,
  • a debug / staging route accidentally promoted to production.

The rule combines three signals (schema.path_ref contains /admin, request is unauthenticated, response is successful) so it fires only when all three hold.

When to use: Any HTTP API. Equally useful as a regression test in CI and as a routine production scan rule. Tighten contains: /admin to a more specific path if your product legitimately exposes anonymous admin metadata pages.

OWASP: API5:2023 Broken Function Level Authorization · CWE: CWE-862

Severity rationale: HIGH — anonymous admin access is reliably exploitable and one of the most common high-severity misconfigurations in API bug-bounty reports.

Features used: detect, schema.path_ref, request.is_authenticated, helpers.response.is_successful

rule:
  id: example-api-public-admin-route
  type: API
  alert:
    name: Public administrative route
    context: |
      An unauthenticated request to a path containing `/admin/`
      returned a 2xx response. The route is missing an authentication
      check.
    severity: HIGH
    category: ACCESS_CONTROL
  detect:
  - if: schema.path_ref
    contains: /admin/
  - if: request.is_authenticated
    is: false
  - if: helpers.response.is_successful
    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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)

Response success detector

if: helpers.response.is_successful

True when the response status code is in the 2xx range.

Matcher type: BooleanMatcher · Operators: is

detect:
  - if: helpers.response.is_successful
    is: true

In context — combine with helpers.request.crud and request.is_authenticated to flag anonymous mutations:

Unauthenticated mutation succeeded

Detect any successful CREATE / UPDATE / DELETE issued without an authenticated user.

Most APIs gate state-changing operations behind authentication. When a public (unauthenticated) request manages to perform a CREATE / UPDATE / DELETE and receives a 2xx response, the endpoint is missing an authn or authz check.

The rule combines three deterministic signals: the inferred CRUD operation is not READ, the request was issued by the public pseudo-user (Escape's representation of an anonymous caller), and the response is in the 2xx range.

When to use: Apply on every API. This is one of the highest-signal rules in the library and rarely produces false positives because all three conditions must hold simultaneously.

OWASP: API2:2023 Broken Authentication · CWE: CWE-306

Severity rationale: HIGH — anonymous mutations typically allow unauthorized data writes, deletion of resources, or trivial denial-of-service via mass creation.

Features used: detect, helpers.request.crud, request.is_authenticated, helpers.response.is_successful

rule:
  id: example-api-unauthenticated-mutation
  type: API
  alert:
    name: Unauthenticated mutation succeeded
    context: |
      A CREATE / UPDATE / DELETE request issued without authentication
      returned a 2xx response. The endpoint is missing an authentication
      or authorization check.
    severity: HIGH
    category: ACCESS_CONTROL
  detect:
  - if: helpers.request.crud
    is_not: READ
  - if: request.is_authenticated
    is: false
  - if: helpers.response.is_successful
    is: true

References:

Properties

  • is: Condition is true
  • is_not: Condition is false

Schema URL detector

if: schema.url

Use this to string compare the URL of the request.

Example

detect:
  - if: schema.url
    regex: .*(internal|private).*

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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)

Request user detector

if: request.user

Match the user name attached to the request. The value must match a user defined in your scan authentication settings (the special user public represents an unauthenticated caller).

Matcher type: StringMatcher · Operators: is, is_not, contains, regex, in

detect:
  - if: request.user
    is: tester@example.com

In context — BOLA detection by replaying as a different user:

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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)

Request headers detector

if: request.headers

Use that to select and compare the request headers in a key value dictionary.

Example

detect:
  - if: request.headers
    key:
      is: 'X-OPERATION'
    value:
      is: 'PAY'

Properties

  • key: Key to match
  • value: Value to match

Response headers detector

if: response.headers

Use that to select and compare the response headers in a key value dictionary.

Example

detect:
  - if: response.headers
    key:
      is: 'X-RESULT'
    value:
      is: 'PAID'

Properties

  • key: Key to match
  • value: Value to match

Response body JSON detector

if: response.body.json

Use this to select and compare the response body when detected as JSON, using jq-like syntax.

Example 1

detect:
  - if: response.body.json
    is:
      id: 42

Example 2

detect:
  - if: response.body.json
    jq: '.role == admin'

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.
  • is: Condition is this exact JSON
  • is_not: Condition is not this exact JSON
  • in: Condition is in this list of JSON
  • jq: JQ query to match and use as boolean. If use_extraction is True, only this attribute will be parsed (if set).

Request body JSON detector

if: request.body.json

Use this to select and compare the request body when detected as JSON, using jq-like syntax.

Example 1

detect:
  - if: request.body.json
    is:
      id: 42

Example 2

detect:
  - if: request.body.json
    jq: '.role == admin'

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.
  • is: Condition is this exact JSON
  • is_not: Condition is not this exact JSON
  • in: Condition is in this list of JSON
  • jq: JQ query to match and use as boolean. If use_extraction is True, only this attribute will be parsed (if set).

Response body text detector

if: response.body.text

Use this to select and compare the response body as text, using string compare.

Example

detect:
  - if: request.body.text
    is_not: 'unauthorized'

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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)

Request body text detector

if: request.body.text

Use this to select and compare the request body as text, using string compare.

Example

detect:
  - if: request.body.text
    contains: 'password='

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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)

Request object detector

if: request.object

Use this to select and compare the detected object scalars (including custom scalars) in the request, with their kind, name and value.

Example

detect:
  - if: request.object
    type:
      in:
        - email
        - phone
        - street_address

Properties

  • type: Object scalar type to match
  • name: Object scalar name to match
  • value: Object scalar value to match

Response object detector

if: response.object

Use this to select and compare the detected object scalars (including custom scalars) in the response, with their kind, name and value.

Example

detect:
  - if: response.object
    type:
      in:
        - email
        - phone
        - street_address

Properties

  • type: Object scalar type to match
  • name: Object scalar name to match
  • value: Object scalar value to match

JSON matches count detector

if: helpers.json_matches.count

Use this to count the number of times a JSON match is in the current and original response.

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer
  • jq: Use this to select the exact JSON you want to compare between the current and original response.

Regex matches count detector

if: helpers.regex_matches.count

Use this to count the number of times a regex match is in the current and original response.

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer
  • regex: Condition is matched on this regex with fullmatch

Fingerprint count detector

if: helpers.fingerprints.count

Use this to select and compare the count of unique fingerprints of the current and original response.

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer

Fingerprint equality detector

if: helpers.fingerprints.same

Use this to determine whether the current and original responses have the same fingerprint.

Properties

  • is: Condition is true
  • is_not: Condition is false

JSON matches all detector

if: helpers.json_matches.all

Use this to determine whether every the current and original responses contain the same JSON fragment.

Properties

  • is: Condition is true
  • is_not: Condition is false
  • jq: Use this to select the exact JSON you want to compare between the current and original response.

Regex matches all detector

if: helpers.regex_matches.all

Use this to determine whether every the current and original responses match the same regular expression.

Properties

  • is: Condition is true
  • is_not: Condition is false
  • regex: Condition is matched on this regex with fullmatch

Detectors available on WebApp

JS Assertion Detector

if: js_assertion

Use this to execute a JavaScript command and assert it returns true.

Example

detect:
  - if: js_assertion
    command: 'return window.isAuthenticated === false;'

Properties

  • command: JavaScript command to execute

Page Selector Detector

if: page_selector

Use this to assert that a selector exists in the DOM.

Example

detect:
  - if: page_selector
    contains: '#my-element'

Properties

  • contains: Contains this string

Page Text Detector

if: page_text

Use this to assert that a text is present in the page.

Example

detect:
  - if: page_text
    contains: 'Hello, world!'

Properties

  • contains: Contains this string

Page Status Code Detector

if: page_status_code

Use this to assert that the page status code is a specific value.

Example

detect:
  - if: page_status_code
    status_code: 200

Properties

  • is: Condition is this exact integer
  • is_not: Condition is not this exact integer
  • in: Condition is in this list of integers (exact match)
  • gt: Condition is greater than this integer
  • lt: Condition is less than this integer

Local Storage Detector

if: local_storage

Use this to assert that a key is present in the local storage.

Example

detect:
  - if: local_storage
    key: 'my-key'
    value: 'pattern'

Properties

  • key: Key to match
  • value: Value to match

Session Storage Detector

if: session_storage

Use this to assert that a key is present in the session storage.

Example

detect:
  - if: session_storage
    key: 'my-key'
    value: 'pattern'

Properties

  • key: Key to match
  • value: Value to match

if: cookie

Use this to assert that a cookie is present in the browser.

Example

detect:
  - if: cookie
    key: 'my-key'
    value: 'pattern'

Properties

  • key: Key to match
  • value: Value to match

Header Contains Detector

if: header

Use this to assert that a header is present in the request.

Example

detect:
  - if: header
    key: 'my-key'
    value: 'pattern'

Properties

  • key: Key to match
  • value: Value to match

Dialog Message Detector

if: dialog.message

Use this to assert that a JavaScript dialog (alert, confirm, prompt) was triggered by the page and inspect its message. Useful for catching reflected XSS payloads that fire alert() once rendered.

Example

detect:
  - if: dialog.message
    contains: 'XSS'

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.
  • is: Condition is this string (case-insensitive)
  • is_not: Condition is not this string (case-insensitive)
  • in: Condition is in this list (case-insensitive)
  • contains: Contains this substring (case-insensitive)
  • regex: Condition is matched on this regex with fullmatch (case-insensitive)