Skip to content

Custom Rules Reference

Auto-generated reference for the JSON Schema that the SaaS frontend and the scanner runtime accept. Both surfaces are covered below.

Shared models (AlertModel, Compliance, CustomRuleID, ...) are documented once in the API reference; the WebApp reference lists only WebApp-specific models and links back to the API section for shared definitions.

API reference

APICustomRule

Property Type Default Description
alert* AlertModel The alert to raise if the detection conditions are met. See Alerting
detect* List[APILogicalAndDetector,APILogicalNotDetector,APILogicalOrDetector,FingerprintCountDetector,FingerprintsSameDetector,HelpersRequestCrudDetector,HelpersResponseIsSuccessfulDetector,JSONMatchesAllDetector,JSONMatchesCountDetector,RegexMatchesAllDetector,RegexMatchesCountDetector,RequestBodyJSONDetector,RequestBodyTextDetector,RequestHeadersDetector,RequestIsAuthenticatedDetector,RequestMethodDetector,RequestObjectDetector,RequestUserDetector,ResponseBodyJSONDetector,ResponseBodyTextDetector,ResponseDurationDetector,ResponseHeadersDetector,ResponseObjectDetector,ResponseStatusCodeDetector,ScanTypeDetector,SchemaNeedAuthenticationDetector,SchemaPathRefDetector,SchemaUrlDetector,VariableDefinedDetector] The conditions to trigger the alert. See Detectors
extractors* List[APIExtractor] The extractors to extract the data from the response. See Extractors
id* CustomRuleID The unique identifier of the custom rule. It is provided by Escape, do not set it manually.
seed List[CurlSeeder,HTTPRAWSeeder,RESTSeeder] A list of requests to seed the scan. See Seeders
transform Middleware null Defines lists of triggers and mutators (combined with AND operators). See Mutators
type* Const[API] API The type of the custom rule. It is provided by Escape, do not set it manually.

Objects

APIExtractor

Property Type Default Description
extract* List[RequestArgument,RequestCookieExtractor,ResponseBodyJSONExtractor,ResponseBodyTextExtractor,ResponseCookieExtractor,ResponseDurationExtractor,ResponseHeaderExtractor,ResponseStatusCodeExtractor,SchemaUrlExtractor] The extractions to apply to the request/response. (See below)
trigger* List[APILogicalAndDetector,APILogicalNotDetector,APILogicalOrDetector,FingerprintCountDetector,FingerprintsSameDetector,HelpersRequestCrudDetector,HelpersResponseIsSuccessfulDetector,JSONMatchesAllDetector,JSONMatchesCountDetector,RegexMatchesAllDetector,RegexMatchesCountDetector,RequestBodyJSONDetector,RequestBodyTextDetector,RequestHeadersDetector,RequestIsAuthenticatedDetector,RequestMethodDetector,RequestObjectDetector,RequestUserDetector,ResponseBodyJSONDetector,ResponseBodyTextDetector,ResponseDurationDetector,ResponseHeadersDetector,ResponseObjectDetector,ResponseStatusCodeDetector,ScanTypeDetector,SchemaNeedAuthenticationDetector,SchemaPathRefDetector,SchemaUrlDetector,VariableDefinedDetector] The detectors to trigger the extraction on the request or response. Detectors

APILogicalAndDetector

Property Type Default Description
and* List[APILogicalAndDetector,APILogicalNotDetector,APILogicalOrDetector,FingerprintCountDetector,FingerprintsSameDetector,HelpersRequestCrudDetector,HelpersResponseIsSuccessfulDetector,JSONMatchesAllDetector,JSONMatchesCountDetector,RegexMatchesAllDetector,RegexMatchesCountDetector,RequestBodyJSONDetector,RequestBodyTextDetector,RequestHeadersDetector,RequestIsAuthenticatedDetector,RequestMethodDetector,RequestObjectDetector,RequestUserDetector,ResponseBodyJSONDetector,ResponseBodyTextDetector,ResponseDurationDetector,ResponseHeadersDetector,ResponseObjectDetector,ResponseStatusCodeDetector,ScanTypeDetector,SchemaNeedAuthenticationDetector,SchemaPathRefDetector,SchemaUrlDetector,VariableDefinedDetector] Logical and on a list of detectors
if* Const[and] and Use this to apply a logical and on a list of detectors.### Exampleyamldetect: - if: and and: - if: helpers.request.crud in: - CREATE - UPDATE - if: response.status_code is: 200

APILogicalNotDetector

Property Type Default Description
if* Const[not] not Use this to apply a logical not on a detector.### Exampleyamldetect: - if: not not: if: response.status_code is: 200
not APILogicalAndDetector, APILogicalNotDetector, APILogicalOrDetector, FingerprintCountDetector, FingerprintsSameDetector, HelpersRequestCrudDetector, HelpersResponseIsSuccessfulDetector, JSONMatchesAllDetector, JSONMatchesCountDetector, RegexMatchesAllDetector, RegexMatchesCountDetector, RequestBodyJSONDetector, RequestBodyTextDetector, RequestHeadersDetector, RequestIsAuthenticatedDetector, RequestMethodDetector, RequestObjectDetector, RequestUserDetector, ResponseBodyJSONDetector, ResponseBodyTextDetector, ResponseDurationDetector, ResponseHeadersDetector, ResponseObjectDetector, ResponseStatusCodeDetector, ScanTypeDetector, SchemaNeedAuthenticationDetector, SchemaPathRefDetector, SchemaUrlDetector, VariableDefinedDetector null Logical not of a detector

APILogicalOrDetector

Property Type Default Description
if* Const[or] or Use this to apply a logical or on a list of detectors.### Exampleyamldetect: - if: or or: - if: helpers.request.crud in: - CREATE - UPDATE - if: response.status_code is: 200
or* List[APILogicalAndDetector,APILogicalNotDetector,APILogicalOrDetector,FingerprintCountDetector,FingerprintsSameDetector,HelpersRequestCrudDetector,HelpersResponseIsSuccessfulDetector,JSONMatchesAllDetector,JSONMatchesCountDetector,RegexMatchesAllDetector,RegexMatchesCountDetector,RequestBodyJSONDetector,RequestBodyTextDetector,RequestHeadersDetector,RequestIsAuthenticatedDetector,RequestMethodDetector,RequestObjectDetector,RequestUserDetector,ResponseBodyJSONDetector,ResponseBodyTextDetector,ResponseDurationDetector,ResponseHeadersDetector,ResponseObjectDetector,ResponseStatusCodeDetector,ScanTypeDetector,SchemaNeedAuthenticationDetector,SchemaPathRefDetector,SchemaUrlDetector,VariableDefinedDetector] Logical or on a list of detectors

AlertModel

Property Type Default Description
category CustomRuleCategory CUSTOM Category of the alert
compliance Compliance null Compliance standards violated by this alert
context* string Context of the alert
description string null Description of the alert
name* string Name of the alert
remediation string null Remediation of the alert
severity* CustomRuleSeverity Severity of the alert

BaseStringMutate

Property Type Default Description
regex_replace RegexReplace null Regex replace pattern.
use_extraction boolean false 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 string null The value to set.
values List[string] null The values to set, generates multiple queries.

Compliance

Property Type Default Description
cra string null
cwe string null
dora string null
fedramp string null
gdpr string null
hipaa string null
hitrust_csf string null
iec62443 string null
iso27001 string null
mitre_attack string null
nis2 string null
nist string null
owasp string null
owasp_asvs string null
owasp_llm string null
pci-dss string null
psd2 string null
soc2 string null
wasc string null

CurlSeeder

Property Type Default Description
curl* string The curl command to use for the request.
protocol* Const[curl] curl Inject a request specified as a curl command. Useful when youalready have a working curl one-liner from browser DevTools(Network tab → Right-click → Copy as cURL) or Postman, and youwant to drop it in as-is.yamlseed: - protocol: curl user: admin curl: > curl -X POST '[example.com](https://example.com/api/users') -H 'Content-Type: application/json' -d '{"name": "tester"}'For long-lived rules prefer the rest seeder (clearer diff,auto-filled host).
user string null The user to use for the request. If not provided, the request is sent without authentication.

CustomRuleID

Property Type Default Description

FingerprintCountDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[helpers.fingerprints.count] helpers.fingerprints.count Use this to select and compare the count of unique fingerprints of the current and original response.
in List[integer] null Condition is in this list of integers (exact match)
is integer null Condition is this exact integer
is_not integer null Condition is not this exact integer
lt integer null Condition is less than this integer

FingerprintsSameDetector

Property Type Default Description
if* Const[helpers.fingerprints.same] helpers.fingerprints.same Use this to determine whether the current and original responses have the same fingerprint.
is boolean null Condition is true
is_not boolean null Condition is false

HTTPRAWSeeder

Property Type Default Description
protocol* Const[http] http Inject a request at scan start, formatted as a raw HTTP messagewith a @Host directive. Use when you need full control over thewire format, want to call a host outside the scan target, or neednon-standard headers / methods.```yamlseed: - protocol: http raw:

Use the raw HTTP seeder to call an internal-only host (internal.example.com) and alert on a successful response — the production environment is not isolated from internal services.

Multi-environment platforms expect production runtime to be network-isolated from internal/back-office services. When a production-runtime container can resolve and reach internal.example.com, a single SSRF or admin-route mistake becomes a path into the internal network.

The seeder uses the raw HTTP seeder (with the @Host directive) so the request actually leaves the application's normal base_url and goes to the internal hostname.

When to use: Run from your production scanning posture, never from a developer laptop. Replace internal.example.com with the actual hostname you want to assert is unreachable.

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

Severity rationale: HIGH when paired with any user-controlled URL fetch in the API; MEDIUM as a standalone configuration finding.

Features used: seed, detect, http raw seeder with @Host directive

rule:
  id: example-api-environment-isolation-internal-host
  type: API
  alert:
    name: Production environment can reach internal host
    context: |
      A request issued to `internal.example.com` from the production
      scanner returned a successful response. The runtime
      environment is not isolated from internal services.
    severity: HIGH
    category: REQUEST_FORGERY
  seed:
  - protocol: http
    raw: |
      @Host: https://internal.example.com
      GET /api/health HTTP/1.1
      Host: internal.example.com
  detect:
  - if: schema.url
    contains: internal.example.com
  - if: helpers.response.is_successful
    is: true

References:

HelpersRequestCrudDetector

Property Type Default Description
if* Const[helpers.request.crud] helpers.request.crud Use this to select against the detected CRUD operation of the request.### Exampleyamldetect: - if: helpers.request.crud in: - CREATE - UPDATE
in List[CRUD] null Condition is the request is in this list of CRUD operations (exact match)
is CRUD null Condition is the request is this CRUD operation
is_not CRUD null Condition is the request is not this CRUD operation

HelpersResponseIsSuccessfulDetector

Property Type Default Description
if* Const[helpers.response.is_successful] helpers.response.is_successful True when the response status code is in the 2xx range.Matcher type: BooleanMatcher · Operators: is````yamldetect: - if: helpers.response.is_successful is: true```In context — combine withhelpers.request.crudandrequest.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:

JSONMatchesAllDetector

Property Type Default Description
if* Const[helpers.json_matches.all] helpers.json_matches.all Use this to determine whether every the current and original responses contain the same JSON fragment.
is boolean null Condition is true
is_not boolean null Condition is false
jq string Use this to select the exact JSON you want to compare between the current and original response.

JSONMatchesCountDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[helpers.json_matches.count] helpers.json_matches.count Use this to count the number of times a JSON match is in the current and original response.
in List[integer] null Condition is in this list of integers (exact match)
is integer null Condition is this exact integer
is_not integer null Condition is not this exact integer
jq string Use this to select the exact JSON you want to compare between the current and original response.
lt integer null Condition is less than this integer

JsonValue

Property Type Default Description

Middleware

Property Type Default Description
mutate* List[RequestBodyJSONMutator,RequestBodyTextMutator,RequestHeadersMutator,RequestMethodMutator,RequestObjectMutator,RequestUserMutator,SchemaPathRefMutator,SchemaUrlMutator] The mutations to apply to the request and replay it. See Mutators
trigger* List[APILogicalAndDetector,APILogicalNotDetector,APILogicalOrDetector,FingerprintCountDetector,FingerprintsSameDetector,HelpersRequestCrudDetector,HelpersResponseIsSuccessfulDetector,JSONMatchesAllDetector,JSONMatchesCountDetector,RegexMatchesAllDetector,RegexMatchesCountDetector,RequestBodyJSONDetector,RequestBodyTextDetector,RequestHeadersDetector,RequestIsAuthenticatedDetector,RequestMethodDetector,RequestObjectDetector,RequestUserDetector,ResponseBodyJSONDetector,ResponseBodyTextDetector,ResponseDurationDetector,ResponseHeadersDetector,ResponseObjectDetector,ResponseStatusCodeDetector,ScanTypeDetector,SchemaNeedAuthenticationDetector,SchemaPathRefDetector,SchemaUrlDetector,VariableDefinedDetector] The detectors to trigger the transform on the request or response. Detectors

ObjectMatcher

Property Type Default Description
name StringMatcher null Object scalar name to match
type ObjectTypeMatcher null Object scalar type to match
value StringMatcher null Object scalar value to match

ObjectTypeMatcher

Property Type Default Description
in List[OBJECT_TYPE] null Object type is in the following list
is OBJECT_TYPE null Object type is exactly this type
is_not OBJECT_TYPE null Object type is any this type except this one

RESTSeeder

Property Type Default Description
body string null The body to use for the request.
headers Dict[string, string] null The headers to use for the request. The key is the header name and the value is the header value.
method HTTPMethod null The method to use for the request.
params Dict[string, string] null parameters to use for the request. The key is the parameter name and the value is the parameter value.
path string null The path to use for the request.
protocol* Const[rest] rest Inject a REST request at scan start. Host and scheme areautomatically filled from the scan target so you only specify thepath, method, and any headers / body / query parameters.yamlseed: - protocol: rest path: /api/users method: GET user: admin headers: Content-Type: application/json body: '{"name": "tester"}' params: limit: '100'In context — Spring Boot actuator probing:#### Spring Boot actuator /env endpoint exposed

Probe the Spring Boot actuator /env and /beans endpoints (with and without the /actuator prefix) and alert if the body leaks JAVA_HOME — proof the actuator is reachable.

Spring Boot's actuator exposes deep introspection endpoints (/env, /beans, /heapdump, /configprops, /mappings) that in default 1.x configurations were public. They leak environment variables, database connection strings, JWT secrets, S3 credentials, anything passed via -D flags or env vars.

The rule probes the four most common path layouts and asserts a successful response containing JAVA_HOME, which is virtually certain to be present in /env output.

Sourced from the in-tree springboot_actuator_env simplecheck.

When to use: Any backend potentially using Spring Boot. False-positive rate is very low because the JAVA_HOME marker rarely appears in legitimate API responses.

OWASP: API8:2023 Security Misconfiguration · CWE: CWE-200

Severity rationale: HIGH — actuator output usually contains credentials, signing keys, and infrastructure URLs that lead to direct compromise.

Features used: seed, detect, helpers.response.is_successful, response.body.text contains

rule:
  id: example-api-spring-boot-actuator-env
  type: API
  alert:
    name: Spring Boot actuator /env exposed
    context: |
      A request to the Spring Boot actuator `/env` endpoint returned
      a successful response containing `JAVA_HOME`, indicating the
      actuator is publicly reachable and leaking environment data.
    severity: HIGH
    category: INFORMATION_DISCLOSURE
  seed:
  - protocol: rest
    method: GET
    path: /actuator/env
    headers: {}
  - protocol: rest
    method: GET
    path: /actuator/beans
    headers: {}
  - protocol: rest
    method: GET
    path: /env
    headers: {}
  - protocol: rest
    method: GET
    path: /beans
    headers: {}
  detect:
  - if: helpers.response.is_successful
    is: true
  - if: response.body.text
    contains: JAVA_HOME

References:

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:

RegexMatchesAllDetector

Property Type Default Description
if* Const[helpers.regex_matches.all] helpers.regex_matches.all Use this to determine whether every the current and original responses match the same regular expression.
is boolean null Condition is true
is_not boolean null Condition is false
regex string Condition is matched on this regex with fullmatch

RegexMatchesCountDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[helpers.regex_matches.count] helpers.regex_matches.count Use this to count the number of times a regex match is in the current and original response.
in List[integer] null Condition is in this list of integers (exact match)
is integer null Condition is this exact integer
is_not integer null Condition is not this exact integer
lt integer null Condition is less than this integer
regex string Condition is matched on this regex with fullmatch

RegexReplace

Property Type Default Description
pattern* string The regex pattern to match.
replacement* string The replacement, use \1, \2, ... to refer capture groups.
use_extraction boolean false 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.

RequestArgument

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
jq string null JQ query to apply to the JSON body. See stedolan.github.io
key* Const[request.argument] request.argument You can use this extractor to extract an argument from request body as a variable.### ExampleThis example will extract the request argument value if it matches the given scalar and store it in the variable arg.yamlextractors: extract: - key: request.argument variable: 'arg' scalars: - id - uuidThis example will extract the user ID sent in a request body JSON and store it in the variable arg.yamlextractors: extract: - key: request.argument variable: 'arg' jq: '.user.id'
scalars List[string] null Scalars the argument has to match to be extracted
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

RequestBodyJSONDetector

Property Type Default Description
if* Const[request.body.json] request.body.json Use this to select and compare the request body when detected as JSON, using jq-like syntax.### Example 1yamldetect: - if: request.body.json is: id: 42### Example 2yamldetect: - if: request.body.json jq: '.role == admin'
in List[JsonValue] null Condition is in this list of JSON
is JsonValue null Condition is this exact JSON
is_not JsonValue null Condition is not this exact JSON
jq string null JQ query to match and use as boolean. If use_extraction is True, only this attribute will be parsed (if set).
use_extraction boolean false 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.

RequestBodyJSONMutator

Property Type Default Description
jq string null JQ query to apply to the JSON body. See stedolan.github.io
key* Const[request.body.json] request.body.json Transform the JSON body of the request using a JQ expression. JQ isthe most powerful mutator in the DSL: it can add fields, removefields, walk every string, restructure objects, etc.Transformation language: JQ — see \<stedolan.github.ioyamlmutate: - key: request.body.json jq: &#39;. + {&#34;is_admin&#34;: true, &#34;role&#34;: &#34;admin&#34;}&#39;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:

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 examples/api/injection/sql-injection-error-disclosure.yaml 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:

RequestBodyTextDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[request.body.text] request.body.text Use this to select and compare the request body as text, using string compare.### Exampleyamldetect: - if: request.body.text contains: &#39;password=&#39;
in List[string] null Condition is in this list (case-insensitive)
is string null Condition is this string (case-insensitive)
is_not string null Condition is not this string (case-insensitive)
regex string null Condition is matched on this regex with fullmatch (case-insensitive)
use_extraction boolean false 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.

RequestBodyTextMutator

Property Type Default Description
key* Const[request.body.text] request.body.text You can use this mutator to change the body (as text) of the request before resending it.### Exampleyamltransform: trigger: - if: request.body.text contains: &#39;hello&#39; mutate: - key: request.body.text values: - &#39;injection 1&#39; - &#39;injection 2&#39; - &#39;injection 3&#39;
regex_replace RegexReplace null Regex replace pattern.
use_extraction boolean false 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 string null The value to set.
values List[string] null The values to set, generates multiple queries.

RequestCookieExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[request.cookies] request.cookies You can use this extractor to extract variables from the request cookies.### ExampleThis example will extract a session cookie sent in request (cookie) and store it in the variable session.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: request.cookies name: &#39;session&#39; variable: &#39;session&#39;
name* string Cookie name to extract from
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

RequestHeadersDetector

Property Type Default Description
if* Const[request.headers] request.headers Use that to select and compare the request headers in a key value dictionary.### Exampleyamldetect: - if: request.headers key: is: &#39;X-OPERATION&#39; value: is: &#39;PAY&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

RequestHeadersMutator

Property Type Default Description
delete boolean null Delete the matched headers.
key* Const[request.headers] 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.### Exampleyamltransform: trigger: - if: schema.url is: &#39;/api/v1/tested/route&#39; mutate: - key: request.headers name: X-API-version value: &#39;APIV2&#39;
name* string The header name to match, supports regex.
regex_replace RegexReplace null Regex replace pattern.
use_extraction boolean false 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 string null The value to set.
values List[string] null The values to set, generates multiple queries.

RequestIsAuthenticatedDetector

Property Type Default Description
if* Const[request.is_authenticated] request.is_authenticated Use this to select whether the request is authenticated.### Exampleyamldetect: - if: request.is_authenticated is: true
is boolean null Condition is true
is_not boolean null Condition is false

RequestMethodDetector

Property Type Default Description
if* Const[request.method] request.method Use this to select against the request HTTP Method.### Exampleyamldetect: - if: request.method is: OPTIONS
in List[HTTPMethod] null Condition is the request is in this list of CRUD operations (exact match)
is HTTPMethod null Condition is the request is this CRUD operation
is_not HTTPMethod null Condition is the request is not this CRUD operation

RequestMethodMutator

Property Type Default Description
key* Const[request.method] request.method Change the HTTP method of the request before resending it.yamlmutate: - 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

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:

RequestObjectDetector

Property Type Default Description
if* Const[request.object] request.object Use this to select and compare the detected object scalars (including custom scalars) in the request, with their kind, name and value.### Exampleyamldetect: - if: request.object type: in: - email - phone - street_address
name StringMatcher null Object scalar name to match
type ObjectTypeMatcher null Object scalar type to match
value StringMatcher null Object scalar value to match

RequestObjectMutator

Property Type Default Description
key* Const[request.object] request.object The detected object scalars (including custom scalars) in the request, with their kind, name and value.### Exampleyamltransform: trigger: - if: schema.url is: &#39;/api/v1/tested/route&#39; mutate: - key: request.object select: type: is: email name: is: &#39;admin_email&#39; value: regex: .*@escape.tech mutate: regex_replace: pattern: (.*)@escape.tech replacement: \1@attacker.com
mutate* BaseStringMutate
select* ObjectMatcher

RequestUserDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[request.user] request.user Match the user name attached to the request. The value must match auser defined in your scan authentication settings (the special userpublic represents an unauthenticated caller).Matcher type: StringMatcher · Operators: is, is_not, contains, regex, `in````yamldetect: - 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:

  • https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
  • https://cwe.mitre.org/data/definitions/639.html | | in | List[string] | null | Condition is in this list (case-insensitive) | | is | string | null | Condition is this string (case-insensitive) | | is_not | string | null | Condition is not this string (case-insensitive) | | regex | string | null | Condition is matched on this regex with fullmatch (case-insensitive) | | use_extraction | boolean | false | 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. |

RequestUserMutator

Property Type Default Description
drop_user boolean null Remove the user authentication from the request.
key* Const[request.user] request.user Replay the request as a different authenticated user (or as anonymousvia drop_user: true). The user must be defined in your scanauthentication settings.yamlmutate: - key: request.user value: attacker@example.com# Remove authentication entirely:mutate: - key: request.user drop_user: trueIn 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:

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:

ResponseBodyJSONDetector

Property Type Default Description
if* Const[response.body.json] response.body.json Use this to select and compare the response body when detected as JSON, using jq-like syntax.### Example 1yamldetect: - if: response.body.json is: id: 42### Example 2yamldetect: - if: response.body.json jq: &#39;.role == admin&#39;
in List[JsonValue] null Condition is in this list of JSON
is JsonValue null Condition is this exact JSON
is_not JsonValue null Condition is not this exact JSON
jq string null JQ query to match and use as boolean. If use_extraction is True, only this attribute will be parsed (if set).
use_extraction boolean false 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.

ResponseBodyJSONExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
jq* string JQ query to apply to the JSON body. See stedolan.github.io
key* Const[response.body.json] response.body.json You can use this extractor to extract variables from the response body JSON.### ExampleThis example will extract the user ID from the response body JSON and store it in the variable user_id.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: response.body.json jq: &#39;.user.id&#39; variable: &#39;user_id&#39;
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ResponseBodyTextDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[response.body.text] response.body.text Use this to select and compare the response body as text, using string compare.### Exampleyamldetect: - if: request.body.text is_not: &#39;unauthorized&#39;
in List[string] null Condition is in this list (case-insensitive)
is string null Condition is this string (case-insensitive)
is_not string null Condition is not this string (case-insensitive)
regex string null Condition is matched on this regex with fullmatch (case-insensitive)
use_extraction boolean false 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.

ResponseBodyTextExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[response.body.text] response.body.text You can use this extractor to extract variables from the response body text.### ExampleThis example will extract the response body text and store it in the variable body_data.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: response.body.text variable: &#39;body_data&#39;
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ResponseCookieExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[response.cookies] response.cookies You can use this extractor to extract variables from the response cookies.### ExampleThis example will extract a session cookie returned in response (set-cookie) and store it in the variable session.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: response.cookies name: &#39;session&#39; variable: &#39;session&#39;
name* string Cookie name to extract from
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ResponseDurationDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[response.duration_ms] response.duration_ms Use this to compare the duration of the request in milliseconds.### Exampleyamldetect: - if: response.duration_ms gt: 200
in List[integer] null Condition is in this list of integers (exact match)
is integer null Condition is this exact integer
is_not integer null Condition is not this exact integer
lt integer null Condition is less than this integer

ResponseDurationExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[response.duration] response.duration You can use this extractor to extract the response duration as a variable.### ExampleThis example will extract the response duration and store it in the variable duration.yamlextractors: extract: - key: response.duration variable: &#39;duration&#39;
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ResponseHeaderExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[response.headers] response.headers You can use this extractor to extract variables from the response headers.### ExampleThis example will extract a token from the response header X-Token and store it in the variable x_token.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: response.headers name: &#39;X-Token&#39; variable: &#39;x_token&#39;
name* string Header name to extract from
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ResponseHeadersDetector

Property Type Default Description
if* Const[response.headers] response.headers Use that to select and compare the response headers in a key value dictionary.### Exampleyamldetect: - if: response.headers key: is: &#39;X-RESULT&#39; value: is: &#39;PAID&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

ResponseObjectDetector

Property Type Default Description
if* Const[response.object] response.object Use this to select and compare the detected object scalars (including custom scalars) in the response, with their kind, name and value.### Exampleyamldetect: - if: response.object type: in: - email - phone - street_address
name StringMatcher null Object scalar name to match
type ObjectTypeMatcher null Object scalar type to match
value StringMatcher null Object scalar value to match

ResponseStatusCodeDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[response.status_code] response.status_code Match against the HTTP response status code.Matcher type: IntegerMatcher · Operators: is, is_not, gt, lt, in````yamldetect: - if: response.status_code in: [200, 206]```For simple success/failure checks preferhelpers.response.is_successful` (it covers the full 2xx range andis 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:

ResponseStatusCodeExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[response.status_code] response.status_code You can use this extractor to extract the response status code as a variable.### ExampleThis example will extract the response status code and store it in the variable status_code.yamlextractors: extract: - key: response.status_code variable: &#39;status_code&#39;
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

ScanTypeDetector

Property Type Default Description
if* Const[scan.type] scan.type Match against the type of scan being performed.Matcher Type: ScanTypeMatcher · Operators: is, is_not, in · Valid values: REST, `GRAPHQL````yamldetect: - if: scan.type is: REST# ORdetect: - if: scan.type in: [REST, GRAPHQL]```
in List[CustomRuleScanType] null The scan type is in this list
is CustomRuleScanType null The scan type is exactly this
is_not CustomRuleScanType null The scan type is not this type

SchemaNeedAuthenticationDetector

Property Type Default Description
if* Const[schema.need_authentication] schema.need_authentication Use this to select whether or not the schema requires authentication.### Exampleyamldetect: - if: schema.need_authentication is: false
is boolean null Condition is true
is_not boolean null Condition is false

SchemaPathRefDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[schema.path_ref] 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````yamldetect: - 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 | | in | List[string] | null | Condition is in this list (case-insensitive) | | is | string | null | Condition is this string (case-insensitive) | | is_not | string | null | Condition is not this string (case-insensitive) | | regex | string | null | Condition is matched on this regex with fullmatch (case-insensitive) | | use_extraction | boolean | false | 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. |

SchemaPathRefMutator

Property Type Default Description
key* Const[schema.path_ref] 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.### Exampleyamltransform: trigger: - if: schema.path_ref is: &#39;/api/v1/tested/route&#39; mutate: - key: schema.path_ref mutate: value: &#39;/api/v2/tested/route&#39;
regex_replace RegexReplace null Regex replace pattern.
use_extraction boolean false 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 string null The value to set.
values List[string] null The values to set, generates multiple queries.

SchemaUrlDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[schema.url] schema.url Use this to string compare the URL of the request.### Example```yamldetect: - if: schema.url regex: .*(internal
in List[string] null Condition is in this list (case-insensitive)
is string null Condition is this string (case-insensitive)
is_not string null Condition is not this string (case-insensitive)
regex string null Condition is matched on this regex with fullmatch (case-insensitive)
use_extraction boolean false 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.

SchemaUrlExtractor

Property Type Default Description
accept_null boolean false Whether the extractor should accept null values or not.
can_overwrite boolean true Whether the extractor can overwrite the variable if it already exists.
key* Const[schema.url] schema.url You can use this extractor to extract variables from the schema URL.### ExampleThis example will extract the schema URL and store it in the variable request_url.yamlextractors: trigger: - if: response.status_code is: 200 extract: - key: schema.url variable: &#39;request_url&#39;
variable* VariableName The variable name (Case Insensitive) to store the extracted data.

SchemaUrlMutator

Property Type Default Description
key* Const[schema.url] schema.url You can use this mutator to change the URL of the request before resending it.### Exampleyamltransform: trigger: - if: schema.url is: &#39;[api.example.com](https://api.example.com/api/v1/tested/route&#39;) mutate: - key: schema.url mutate: value: &#39;[api2.example.com](https://api2.example.com/api/v2/&#39;)
regex_replace RegexReplace null Regex replace pattern.
use_extraction boolean false 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 string null The value to set.
values List[string] null The values to set, generates multiple queries.

StringMatcher

Property Type Default Description
contains string null Contains this substring (case-insensitive)
in List[string] null Condition is in this list (case-insensitive)
is string null Condition is this string (case-insensitive)
is_not string null Condition is not this string (case-insensitive)
regex string null Condition is matched on this regex with fullmatch (case-insensitive)
use_extraction boolean false 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.

VariableDefinedDetector

Property Type Default Description
if* Const[variable.defined] variable.defined Use this to detect if a given variable has been extracted or not yet.### Basic Exampleyamldetect: - if: variable.defined variable_name: user_id### Common Use Cases- Making sure a variable has been already extracted before executing a mutation, a detector or another extraction.
variable_name* string Use this to specify the variable name that has to be defined before proceeding further

VariableName

Property Type Default Description

Enums

CRUD

Value
CREATE
READ
UPDATE
DELETE

CustomRuleCategory

Value
ACCESS_CONTROL
CONFIGURATION
INFORMATION_DISCLOSURE
INJECTION
PROTOCOL
REQUEST_FORGERY
RESOURCE_LIMITATION
SENSITIVE_DATA
SCHEMA
CUSTOM

CustomRuleScanType

Value
GRAPHQL
REST

CustomRuleSeverity

Value
HIGH
MEDIUM
LOW
INFO

HTTPMethod

Value
CONNECT
DELETE
GET
HEAD
OPTIONS
PATCH
POST
PUT
TRACE

OBJECT_TYPE

Value
See Data Types Reference

WebApp reference

FrontendCustomRule

Property Type Default Description
alert* AlertModel The alert to raise if the detection conditions are met. See Alerting
detect* List[CookieDetector,DialogMessageDetector,FrontendLogicalAndDetector,FrontendLogicalNotDetector,FrontendLogicalOrDetector,HeaderDetector,JSAssertionDetector,LocalStorageDetector,PageSelectorDetector,PageStatusCodeDetector,PageTextDetector,SessionStorageDetector] The conditions to trigger the alert. See Detectors
extractors* List[object] Extractors are not currently supported for Frontend custom rules.
id* CustomRuleID The unique identifier of the custom rule. It is provided by Escape, do not set it manually.
seed* List[CheckAction,ClickAction,ClickMailMagicLinkAction,FillAction,FillMailTOTPAction,FillTOTPAction,FocusPageAction,GotoAction,SelectAction,SleepAction,SolveCaptchaAction,WaitElementAction,WaitTextAction] A list of requests to seed the scan. See Seeders
type* Const[WEBAPP] WEBAPP The type of the custom rule. It is provided by Escape, do not set it manually.

Objects

CheckAction

Property Type Default Description
action* Const[check] check
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
locator* string The Playwright Locator to select the checkbox to check
select_first_if_multiple boolean false Whether to select the first element if multiple elements are found. If false, an error will be raised.
timeout integer 30 Timeout in seconds for executing the check action

ClickAction

Property Type Default Description
action* Const[click] click
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
locator* string The Playwright Locator to select the element to click on
select_first_if_multiple boolean false Whether to select the first element if multiple elements are found. If false, an error will be raised.
timeout integer 30 Timeout in seconds for executing the click action

ClickMailMagicLinkAction

Property Type Default Description
action* Const[click_mail_magic_link] click_mail_magic_link
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
email_address* string The email address where the magic link will be sent.\nMust be a valid email address matching this pattern: {any_text}.{organization_id_short}@scan.escape.tech
new_page boolean false Whether to create a new page for the navigation or remain on the current page
timeout integer 60 The timeout (seconds) to wait for the page to load

CookieDetector

Property Type Default Description
if* Const[cookie] cookie Use this to assert that a cookie is present in the browser.### Exampleyamldetect: - if: cookie key: &#39;my-key&#39; value: &#39;pattern&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

DialogMessageDetector

Property Type Default Description
contains string null Contains this substring (case-insensitive)
if* Const[dialog.message] dialog.message Use this to assert that a JavaScript dialog (alert, confirm,prompt) was triggered by the page and inspect its message. Usefulfor catching reflected XSS payloads that fire alert() oncerendered.### Exampleyamldetect: - if: dialog.message contains: &#39;XSS&#39;
in List[string] null Condition is in this list (case-insensitive)
is string null Condition is this string (case-insensitive)
is_not string null Condition is not this string (case-insensitive)
regex string null Condition is matched on this regex with fullmatch (case-insensitive)
use_extraction boolean false 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.

FillAction

Property Type Default Description
action* Const[fill] fill
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
auto_submit boolean false Whether the form should be automatically submitted after the fill action
locator* string The Playwright Locator to select the field to fill
one_by_one boolean false Whether the field should be typed one character by one character, like a TOTP code
select_first_if_multiple boolean false Whether to select the first element if multiple elements are found. If false, an error will be raised.
timeout integer 30 Timeout in seconds for executing the input filling action
value* string The value to fill in the field

FillMailTOTPAction

Property Type Default Description
action* Const[fill_mail_totp] fill_mail_totp
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
auto_submit boolean false Whether the form should be automatically submitted after the fill action
email_address* string The email address where the TOTP code will be sent.\nMust be a valid email address matching this pattern: {any_text}.{organization_id_short}@scan.escape.tech
locator* string The Playwright Locator to select the field to fill
one_by_one boolean false Whether the field should be typed one character by one character, like a TOTP code
select_first_if_multiple boolean false Whether to select the first element if multiple elements are found. If false, an error will be raised.
timeout integer 30 Timeout in seconds for executing the input filling action

FillTOTPAction

Property Type Default Description
action* Const[fill_totp] fill_totp
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
auto_submit boolean false Whether the form should be automatically submitted after the TOTP code is filled
locator* string The Playwright Locator to select the field to fill the TOTP code in
secret* string The secret to generate the TOTP code from

FocusPageAction

Property Type Default Description
action* Const[focus_page] focus_page
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
url_pattern* string The pattern to find in the URL to focus on a page. Should be a regex allowing to match the page using Python re.findall().

FrontendLogicalAndDetector

Property Type Default Description
and* List[CookieDetector,DialogMessageDetector,FrontendLogicalAndDetector,FrontendLogicalNotDetector,FrontendLogicalOrDetector,HeaderDetector,JSAssertionDetector,LocalStorageDetector,PageSelectorDetector,PageStatusCodeDetector,PageTextDetector,SessionStorageDetector] Logical and on a list of detectors
if* Const[and] and

FrontendLogicalNotDetector

Property Type Default Description
if* Const[not] not
not CookieDetector, DialogMessageDetector, FrontendLogicalAndDetector, FrontendLogicalNotDetector, FrontendLogicalOrDetector, HeaderDetector, JSAssertionDetector, LocalStorageDetector, PageSelectorDetector, PageStatusCodeDetector, PageTextDetector, SessionStorageDetector null Logical not of a detector

FrontendLogicalOrDetector

Property Type Default Description
if* Const[or] or
or* List[CookieDetector,DialogMessageDetector,FrontendLogicalAndDetector,FrontendLogicalNotDetector,FrontendLogicalOrDetector,HeaderDetector,JSAssertionDetector,LocalStorageDetector,PageSelectorDetector,PageStatusCodeDetector,PageTextDetector,SessionStorageDetector] Logical or on a list of detectors

GotoAction

Property Type Default Description
action* Const[goto] goto
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
new_page boolean false Whether to create a new page for the navigation or remain on the current page
timeout integer 60 The timeout (seconds) to wait for the page to load
url* string The URL to navigate to

HeaderDetector

Property Type Default Description
if* Const[header] header Use this to assert that a header is present in the request.### Exampleyamldetect: - if: header key: &#39;my-key&#39; value: &#39;pattern&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

JSAssertionDetector

Property Type Default Description
command string null JavaScript command to execute
if* Const[js_assertion] js_assertion Use this to execute a JavaScript command and assert it returns true.### Exampleyamldetect: - if: js_assertion command: &#39;return window.isAuthenticated === false;&#39;

LocalStorageDetector

Property Type Default Description
if* Const[local_storage] local_storage Use this to assert that a key is present in the local storage.### Exampleyamldetect: - if: local_storage key: &#39;my-key&#39; value: &#39;pattern&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

PageSelectorDetector

Property Type Default Description
contains string null Contains this string
if* Const[page_selector] page_selector Use this to assert that a selector exists in the DOM.### Exampleyamldetect: - if: page_selector contains: &#39;#my-element&#39;

PageStatusCodeDetector

Property Type Default Description
gt integer null Condition is greater than this integer
if* Const[page_status_code] page_status_code Use this to assert that the page status code is a specific value.### Exampleyamldetect: - if: page_status_code status_code: 200
in List[integer] null Condition is in this list of integers (exact match)
is integer null Condition is this exact integer
is_not integer null Condition is not this exact integer
lt integer null Condition is less than this integer

PageTextDetector

Property Type Default Description
contains string null Contains this string
if* Const[page_text] page_text Use this to assert that a text is present in the page.### Exampleyamldetect: - if: page_text contains: &#39;Hello, world!&#39;

SelectAction

Property Type Default Description
action* Const[select] select
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
locator* string The Playwright Locator to select the dropdown to select from
select_first_if_multiple boolean false Whether to select the first element if multiple elements are found. If false, an error will be raised.
timeout integer 30 Timeout in seconds for executing the click action
value* string The value to select

SessionStorageDetector

Property Type Default Description
if* Const[session_storage] session_storage Use this to assert that a key is present in the session storage.### Exampleyamldetect: - if: session_storage key: &#39;my-key&#39; value: &#39;pattern&#39;
key StringMatcher null Key to match
value StringMatcher null Value to match

SleepAction

Property Type Default Description
action* Const[sleep] sleep
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
seconds* number The time to sleep in seconds, between 0 and 40 seconds

SolveCaptchaAction

Property Type Default Description
action* Const[solve_captcha] solve_captcha
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
auto_submit boolean false Whether the form should be automatically submitted after the captcha is filled
locator* string The Playwright Locator to select the captcha field to fill

WaitElementAction

Property Type Default Description
action* Const[wait_element] wait_element
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
locator* string The selector to wait for
timeout number 10 The timeout (seconds) to wait for the element to be visible

WaitTextAction

Property Type Default Description
action* Const[wait_text] wait_text
allow_failure boolean false Allow this action to fail without breaking authentication, defaults to False.
timeout number 10 The timeout (seconds) to wait for the text to be visible
value* string The text to wait for until visible, case-insensitive

Enums