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
Properties¶
is: The scan type is exactly thisis_not: The scan type is not this typein: 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¶
Properties¶
is: Condition is the request is this CRUD operationis_not: Condition is the request is not this CRUD operationin: 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
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:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/540.html
Properties¶
is: Condition is this exact integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: 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¶
Properties¶
is: Condition is this exact integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: 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¶
Properties¶
is: Condition is trueis_not: Condition is false
Request authentication detector¶
if: request.is_authenticated
Use this to select whether the request is authenticated.
Example¶
Properties¶
is: Condition is trueis_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:
- https://owasp.org/API-Security/editions/2023/en/0xa5-broken-function-level-authorization/
- https://cwe.mitre.org/data/definitions/862.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.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
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:
- https://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/
- https://cwe.mitre.org/data/definitions/306.html
Properties¶
is: Condition is trueis_not: Condition is false
Schema URL detector¶
if: schema.url
Use this to string compare the URL of the request.
Example¶
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
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:
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.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¶
Properties¶
key: Key to matchvalue: Value to match
Response headers detector¶
if: response.headers
Use that to select and compare the response headers in a key value dictionary.
Example¶
Properties¶
key: Key to matchvalue: 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¶
Example 2¶
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 JSONis_not: Condition is not this exact JSONin: Condition is in this list of JSONjq: JQ query to match and use as boolean. Ifuse_extractionis 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¶
Example 2¶
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 JSONis_not: Condition is not this exact JSONin: Condition is in this list of JSONjq: JQ query to match and use as boolean. Ifuse_extractionis 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¶
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¶
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¶
Properties¶
type: Object scalar type to matchname: Object scalar name to matchvalue: 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¶
Properties¶
type: Object scalar type to matchname: Object scalar name to matchvalue: 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 integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: Condition is less than this integerjq: 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 integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: Condition is less than this integerregex: 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 integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: 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 trueis_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 trueis_not: Condition is falsejq: 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 trueis_not: Condition is falseregex: 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¶
Properties¶
command: JavaScript command to execute
Page Selector Detector¶
if: page_selector
Use this to assert that a selector exists in the DOM.
Example¶
Properties¶
contains: Contains this string
Page Text Detector¶
if: page_text
Use this to assert that a text is present in the page.
Example¶
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¶
Properties¶
is: Condition is this exact integeris_not: Condition is not this exact integerin: Condition is in this list of integers (exact match)gt: Condition is greater than this integerlt: 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¶
Properties¶
key: Key to matchvalue: Value to match
Session Storage Detector¶
if: session_storage
Use this to assert that a key is present in the session storage.
Example¶
Properties¶
key: Key to matchvalue: Value to match
Cookie Contains Detector¶
if: cookie
Use this to assert that a cookie is present in the browser.
Example¶
Properties¶
key: Key to matchvalue: Value to match
Header Contains Detector¶
if: header
Use this to assert that a header is present in the request.
Example¶
Properties¶
key: Key to matchvalue: 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¶
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)