Examples for APIs (43)¶
This page lists every canonical API custom rule example shipped with Escape (43 total), grouped by the vulnerability category it targets. Each example is a complete, anonymized rule that round-trips through the SaaS schema. Copy the rule: block, edit hostnames and users to your scan target, and ship.
Categories¶
- Access Control (9)
- Configuration (6)
- Information Disclosure (10)
- Injection (8)
- Protocol (2)
- Request Forgery (2)
- Resource Limitation (2)
- Sensitive Data (4)
Access Control (9)¶
State-changing request without a CSRF token header¶
Flag any successful CREATE / UPDATE / DELETE that did not carry a CSRF token header, so cookie-authenticated browsers can be coerced into issuing it cross-origin.
Cookie-based authentication remains the dominant mechanism for server-rendered apps. Any state-changing endpoint that does not require a CSRF token (X-CSRF-Token, X-XSRF-Token, X-Requested-With, ...) can be triggered by a malicious origin via <form> POST or <img> GET-with-side-effects.
The rule is purely passive: no transform, no seed. It greps every successful mutation request for a CSRF-style header and alerts when none is present.
When to use: Any web API that accepts cookies for authentication. Adjust the or branch of headers if your platform standardizes on a different header name.
OWASP: A01:2021 Broken Access Control (CSRF) · CWE: CWE-352
Severity rationale: MEDIUM — exploitation requires luring an authenticated victim, but the impact (state change in their account) is direct and deterministic.
Features used: detect, helpers.request.crud, request.headers, request.is_authenticated, logical not, logical or
rule:
id: example-api-missing-csrf-token
type: API
alert:
name: State-changing request missing CSRF token
context: |
A successful CREATE / UPDATE / DELETE was accepted from an
authenticated session without any CSRF-style header. The endpoint
is exposed to cross-site request forgery from cookie-bearing
victims.
severity: MEDIUM
category: REQUEST_FORGERY
detect:
- if: helpers.request.crud
is_not: READ
- if: request.is_authenticated
is: true
- if: helpers.response.is_successful
is: true
- if: not
not:
if: or
or:
- if: request.headers
key:
is: X-CSRF-Token
- if: request.headers
key:
is: X-XSRF-Token
- if: request.headers
key:
is: X-Requested-With
References:
- https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/352.html
BOLA via numeric ID enumeration¶
Detect Broken Object Level Authorization on resources whose URL ends in a numeric ID by replaying every successful GET as the same user with the path id incremented to enumerate someone else's record.
Many APIs expose object-scoped routes such as /api/orders/{id} where {id} is a small monotonically-increasing integer. When the server forgets to verify that the calling user owns the requested object, an attacker can enumerate ?id=1, ?id=2, ... to walk the full resource set.
The deterministic test rewrites the path id to a constant unrelated integer (999999) using a regex replace mutator and asserts the response is still successful. False positives are rare because the target user almost certainly does not own that exact id.
When to use: Any REST endpoint with a numeric integer in the path (/users/123, /orders/456/items/789, ...). Adjust the schema.path_ref regex and the replacement id to your target.
OWASP: API1:2023 BOLA · CWE: CWE-639
Severity rationale: HIGH — successful enumeration usually allows attackers to harvest the full customer dataset, an event that is reportable under most privacy regulations.
Features used: transform, detect, schema.path_ref mutator (regex_replace), helpers.response.is_successful
rule:
id: example-api-bola-numeric-id-swap
type: API
alert:
name: BOLA via numeric ID enumeration
context: |
A successful response was returned when the resource id in the URL
was rewritten to an unrelated integer. The endpoint is missing an
object-level authorization check.
severity: HIGH
category: ACCESS_CONTROL
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: schema.path_ref
regex: .*/[0-9]+(/.*)?
mutate:
- key: schema.path_ref
regex_replace:
pattern: /[0-9]+(/|$)
replacement: /999999\1
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- https://cwe.mitre.org/data/definitions/639.html
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
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:
- https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- https://cwe.mitre.org/data/definitions/639.html
IDOR via extracted ID re-injection across users¶
Extract a resource id created by tester@example.com then re-issue the read of that same id as attacker@example.com. Alert if the second user gets a successful response.
This pattern automates the canonical IDOR test:
- The
extractorsblock listens for any successful response fromtester@example.comon a write endpoint and captures.idfrom the JSON body into thevictim_resource_idvariable. - The
transformblock triggers on subsequent successful reads byattacker@example.comwhose URL contains/{{victim_resource_id}}. It rewrites the request user back toattacker@example.com(a no-op here, but kept so the same shape can be extended to swap users when needed). detectfires whenattacker@example.comsuccessfully reads a resource whose id was originally returned to a different user.
The variable templating relies on use_extraction: true to expand {{victim_resource_id}} at evaluation time.
When to use: APIs whose CREATE responses return the new resource id and whose READ routes accept that id directly in the URL (/orders/{id}, /comments/{id}, ...). This is the most common shape of IDOR in REST APIs.
OWASP: API1:2023 BOLA · CWE: CWE-639
Severity rationale: HIGH — direct cross-user reads of arbitrary objects.
Features used: extractors, transform, detect, response.body.json (jq) extractor, schema.url with use_extraction
rule:
id: example-api-idor-extract-and-replay
type: API
alert:
name: IDOR — cross-user read of an extracted resource id
context: |
`attacker@example.com` successfully read a resource whose id was
originally returned to `tester@example.com`. The endpoint is
missing an object-level authorization check.
severity: HIGH
category: ACCESS_CONTROL
extractors:
- trigger:
- if: request.user
is: tester@example.com
- if: helpers.response.is_successful
is: true
- if: helpers.request.crud
is: CREATE
extract:
- key: response.body.json
jq: .id
variable: victim_resource_id
can_overwrite: true
accept_null: false
detect:
- if: variable.defined
variable_name: victim_resource_id
- if: request.user
is: attacker@example.com
- if: schema.url
use_extraction: true
contains: /{{victim_resource_id}}
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
- https://cwe.mitre.org/data/definitions/639.html
JWT alg=none accepted¶
Replay every successful authenticated request with an unsigned alg: none JWT in Authorization. Alert if the response is still successful — the verifier accepted an unsigned token.
A JWT verifier that accepts alg: none lets any caller forge arbitrary identity claims. The deterministic test substitutes the real bearer token with a hand-crafted unsigned JWT carrying the same subject claim and re-issues the request.
The unsigned token below decodes to {"alg":"none","typ":"JWT"}.{"sub":"tester@example.com"}. (note the trailing dot and empty signature segment).
A successful 2xx with this token proves the server skipped signature validation when alg=none.
When to use: Any REST or GraphQL API that uses JWT bearer tokens. Replace the sub claim with a real user identifier from your authentication configuration to make the test meaningful.
OWASP: API2:2023 Broken Authentication · CWE: CWE-347
Severity rationale: CRITICAL when exploitable — accepting alg=none is full identity forgery and grants instant impersonation of any user.
Features used: transform, detect, request.headers mutator, helpers.response.is_successful
rule:
id: example-api-jwt-alg-none
type: API
alert:
name: JWT alg=none accepted by the API
context: |
The API returned a successful response when the bearer token was
replaced with an unsigned `alg: none` JWT. The verifier is
misconfigured and trusts forged tokens.
severity: HIGH
category: ACCESS_CONTROL
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.is_authenticated
is: true
- if: request.headers
key:
is: Authorization
value:
regex: (?i)^bearer\s.+
mutate:
- key: request.headers
name: Authorization
value: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJ0ZXN0ZXJAZXhhbXBsZS5jb20ifQ.
detect:
- 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/347.html
- https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
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
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:
transform.triggerselects every successful CREATE / UPDATE issued bytester@example.comwhose JSON body does not yet carry an admin marker.transform.mutateaddsis_admin: trueandrole: adminusing a JQ expression.detectfires 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:
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/915.html
- https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
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
Configuration (6)¶
Missing Strict-Transport-Security header¶
Alert on any successful HTTPS response that does not carry an HSTS header — the browser is not pinned to HTTPS for subsequent requests.
HTTP Strict Transport Security (HSTS) instructs browsers to refuse plain-HTTP connections to a host for the configured duration. Without HSTS, an attacker on the network path can SSL-strip the very first request after a cold cache.
The detector requires the request to have used HTTPS (otherwise HSTS would be advisory) and asserts the response carries a Strict-Transport-Security header with at least a one-year max-age.
When to use: Any API or web app served over HTTPS. Adjust the regex to your minimum acceptable max-age (one year is the OWASP-recommended floor).
OWASP: A02:2021 Cryptographic Failures · CWE: CWE-319
Severity rationale: LOW — the attack requires network-path adversary positioning.
Features used: detect, schema.url, response.headers, logical not
rule:
id: example-api-missing-hsts
type: API
alert:
name: Missing HSTS header
context: |
A successful HTTPS response was returned without a valid
`Strict-Transport-Security` header. The browser is not pinned
to HTTPS for subsequent navigations.
severity: LOW
category: CONFIGURATION
detect:
- if: helpers.response.is_successful
is: true
- if: schema.url
regex: ^https://.*
- if: not
not:
if: response.headers
key:
is: Strict-Transport-Security
value:
regex: .*max-age=([3-9][0-9]{6}|[1-9][0-9]{7,}).*
References:
- https://owasp.org/www-project-secure-headers/#http-strict-transport-security
- https://datatracker.ietf.org/doc/html/rfc6797
- https://cwe.mitre.org/data/definitions/319.html
CORS reflects arbitrary origin with credentials¶
Replay every authenticated request with Origin: https://attacker.example and alert if the response echoes the same value in Access-Control-Allow-Origin together with Access-Control-Allow-Credentials: true.
Origin reflection is more dangerous than the static wildcard: the server echoes whatever Origin it sees and pairs it with Allow-Credentials: true, so attacker-controlled pages can read authenticated responses with the victim's cookies attached.
The deterministic test:
transform.triggerselects every successful authenticated request that carries anOriginheader (i.e. browser-issued).transform.mutaterewritesOrigintohttps://attacker.example.detectfires when the response carriesAccess-Control-Allow-Origin: https://attacker.exampleANDAccess-Control-Allow-Credentials: true.
When to use: Any API exposed to browsers with cookie or bearer authentication.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-942
Severity rationale: HIGH — direct cross-origin theft of authenticated response bodies; routinely escalated to full account takeover.
Features used: transform, detect, request.headers mutator, response.headers
rule:
id: example-api-cors-origin-reflection
type: API
alert:
name: CORS reflects arbitrary origin with credentials
context: |
The API echoed an attacker-supplied `Origin` value in
`Access-Control-Allow-Origin` together with
`Access-Control-Allow-Credentials: true`, allowing any origin
to read authenticated response bodies.
severity: HIGH
category: CONFIGURATION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.is_authenticated
is: true
- if: request.headers
key:
is: Origin
mutate:
- key: request.headers
name: Origin
value: https://attacker.example
detect:
- if: response.headers
key:
is: Access-Control-Allow-Origin
value:
is: https://attacker.example
- if: response.headers
key:
is: Access-Control-Allow-Credentials
value:
is: 'true'
References:
CORS Access-Control-Allow-Origin wildcard¶
Alert on any response that returns Access-Control-Allow-Origin: * on an authenticated route — every origin can read the response body.
Access-Control-Allow-Origin: * is acceptable on truly public APIs but lethal anywhere a session cookie or bearer token is used: it lets every origin read the response body via XHR / fetch.
The detector fires only when the request was authenticated AND the response carries the wildcard, which is the unambiguous misconfiguration. Pure unauthenticated APIs that legitimately serve every origin are not flagged.
When to use: Any API. Combine with the cors-origin-reflection.yaml rule for full CORS coverage (reflection is more dangerous because it bypasses the wildcard's withCredentials ban).
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-942
Severity rationale: MEDIUM — straightforward exfiltration of authenticated response bodies from any web origin.
Features used: detect, request.is_authenticated, response.headers
rule:
id: example-api-cors-wildcard
type: API
alert:
name: CORS wildcard on authenticated route
context: |
An authenticated API response was returned with
`Access-Control-Allow-Origin: *`, exposing the response body
to every web origin.
severity: MEDIUM
category: CONFIGURATION
detect:
- if: request.is_authenticated
is: true
- if: helpers.response.is_successful
is: true
- if: response.headers
key:
is: Access-Control-Allow-Origin
value:
is: '*'
References:
- https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny
- https://cwe.mitre.org/data/definitions/942.html
Exposed .env file¶
Probe /.env, /.env.local, /.env.production and alert if the body looks like a dotenv file (KEY=VALUE lines, common keys).
.env files routinely contain DB passwords, OAuth secrets, Stripe keys, and SMTP credentials. They end up in the web root when deploys ship the project source verbatim or when a build step copies the development env file to dist/.
The detector requires both a successful response AND a body containing typical dotenv keys (DB_PASSWORD, DATABASE_URL, SECRET_KEY, AWS_ACCESS_KEY_ID). False positives are essentially impossible.
When to use: Any web property. Run after every deployment.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-538
Severity rationale: HIGH — exposed env files are a direct credential leak.
Features used: seed, detect, response.body.text contains, logical or
rule:
id: example-api-exposed-env-file
type: API
alert:
name: Exposed .env file
context: |
A request to `/.env` (or a known variant) returned a body
containing dotenv markers, indicating environment configuration
with secrets is publicly downloadable.
severity: HIGH
category: CONFIGURATION
seed:
- protocol: rest
method: GET
path: /.env
- protocol: rest
method: GET
path: /.env.local
- protocol: rest
method: GET
path: /.env.production
- protocol: rest
method: GET
path: /.env.development
detect:
- if: helpers.response.is_successful
is: true
- if: or
or:
- if: response.body.text
contains: DB_PASSWORD
- if: response.body.text
contains: DATABASE_URL
- if: response.body.text
contains: SECRET_KEY
- if: response.body.text
contains: AWS_ACCESS_KEY_ID
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/538.html
Missing X-Frame-Options on HTML responses¶
Alert on every HTML response that does not carry an X-Frame-Options (or equivalent CSP frame-ancestors) header — the page is clickjackable.
Pages that lack X-Frame-Options: DENY (or the modern Content-Security-Policy: frame-ancestors 'none') can be embedded in an attacker-controlled iframe and used for clickjacking. The trigger filters for HTML responses (the only ones for which the header is meaningful) and the detector fires when both header families are absent.
When to use: Any web app that returns HTML. Especially important on authenticated pages where clickjacking can trick a logged-in user into clicking a privileged action.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1021
Severity rationale: LOW for unauthenticated pages, MEDIUM when applied to authenticated dashboards. This rule defaults to LOW.
Features used: detect, response.headers, logical not, logical or
rule:
id: example-api-missing-x-frame-options
type: API
alert:
name: Missing X-Frame-Options
context: |
An HTML response was returned without `X-Frame-Options` and
without a `Content-Security-Policy` declaring
`frame-ancestors`. The page can be embedded in a malicious
iframe and used for clickjacking.
severity: LOW
category: CONFIGURATION
detect:
- if: helpers.response.is_successful
is: true
- if: response.headers
key:
is: Content-Type
value:
regex: .*text/html.*
- if: not
not:
if: or
or:
- if: response.headers
key:
is: X-Frame-Options
- if: response.headers
key:
is: Content-Security-Policy
value:
regex: .*frame-ancestors.*
References:
- https://owasp.org/www-community/attacks/Clickjacking
- https://cwe.mitre.org/data/definitions/1021.html
API version drift — V2 endpoint accepts V1 token¶
Replay every successful V2 request with the API version header rewritten to V1 and alert if the response is still successful — the API exposes a deprecated V1 contract on the V2 surface.
Long-lived APIs accumulate version drift: the V1 contract is deprecated in docs but the runtime still routes a request with X-API-Version: V1 to the legacy handler — frequently with weaker authorization.
The transform mutates X-API-Version from V2 to V1; if the response is still successful, the V2 endpoint is silently backwards-compatible with V1 requests, which is almost certainly not the intent.
When to use: APIs that explicitly advertise a X-API-Version (or Accept-Version, Api-Version) header. Tighten the trigger to your routes if your version header is named differently.
OWASP: API9:2023 Improper Inventory Management · CWE: CWE-672
Severity rationale: LOW for inventory hygiene, MEDIUM if the V1 surface bypasses authorization (likely; that's why it was deprecated).
Features used: transform, detect, request.headers mutator
rule:
id: example-api-version-drift-v2-accepts-v1
type: API
alert:
name: V2 endpoint silently accepts V1 token
context: |
A successful V2 request continued to succeed when the
`X-API-Version` header was downgraded to V1, indicating the
deprecated V1 contract is still reachable on the V2 endpoint.
severity: LOW
category: CONFIGURATION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.headers
key:
is: X-API-Version
value:
is: V2
mutate:
- key: request.headers
name: X-API-Version
value: V1
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/
- https://cwe.mitre.org/data/definitions/672.html
Information Disclosure (10)¶
Exposed phpinfo() page¶
Probe /phpinfo.php and friends and alert if the response carries the unmistakable PHP Version heading — a phpinfo dump leaks paths, modules, env vars, and DB credentials.
phpinfo() was historically used to verify a PHP install, then promptly forgotten. A live /phpinfo.php page reveals:
- PHP version + module list (input for known-CVE matching),
- INI directives (open_basedir, disable_functions),
- environment variables (frequently containing DB and S3 credentials),
- filesystem paths (DOCROOT, includes search path).
The detector matches the canonical PHP Version heading. False positives are essentially impossible.
When to use: Any PHP application. Run after deployments since debugging pages are routinely committed during refactors.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-200
Severity rationale: HIGH — phpinfo dumps regularly contain credentials and always speed up exploit chain construction.
Features used: seed, detect, response.body.text contains, helpers.response.is_successful
rule:
id: example-api-exposed-phpinfo
type: API
alert:
name: phpinfo() exposed
context: |
A request to a PHP debug filename returned a page containing
`PHP Version`, indicating a `phpinfo()` dump is publicly
reachable.
severity: HIGH
category: INFORMATION_DISCLOSURE
seed:
- protocol: rest
method: GET
path: /phpinfo.php
- protocol: rest
method: GET
path: /info.php
- protocol: rest
method: GET
path: /test.php
- protocol: rest
method: GET
path: /_phpinfo.php
detect:
- if: helpers.response.is_successful
is: true
- if: response.body.text
regex: .*<title>phpinfo\(\).*</title>.*
References:
- https://www.php.net/manual/en/function.phpinfo.php
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/200.html
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
WordPress XML-RPC endpoint exposed¶
POST a system.listMethods call to /xmlrpc.php and alert if it succeeds — XML-RPC is enabled and supports brute-forceable login methods.
The WordPress XML-RPC endpoint (/xmlrpc.php) is enabled by default and exposes APIs (wp.getUsersBlogs, system.multicall) that can be used to brute-force credentials at thousands of attempts per request, bypassing typical login-page rate limits.
The seeder issues a benign system.listMethods request. A 2xx XML response means the endpoint is live and exploitable; sites should disable it via a firewall rule or add_filter('xmlrpc_enabled', '__return_false').
Sourced from the in-tree wordpress_xmlrpc_php_exposed simplecheck.
When to use: Any WordPress site. Particularly important on multi-author publishing platforms where system.multicall becomes a brute force amplifier.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-307
Severity rationale: MEDIUM — endpoint exposure alone is configuration debt; the HIGH upgrade comes when paired with a successful credential probe.
Features used: seed, detect, helpers.response.is_successful, schema.path_ref
rule:
id: example-api-wordpress-xmlrpc-exposed
type: API
alert:
name: WordPress XML-RPC endpoint exposed
context: |
A `system.listMethods` POST to `/xmlrpc.php` returned a
successful response, indicating the XML-RPC endpoint is enabled
and supports brute-forceable login methods.
severity: MEDIUM
category: INFORMATION_DISCLOSURE
seed:
- protocol: rest
method: POST
path: /xmlrpc.php
headers:
Content-Type: text/xml
body: |
<?xml version="1.0"?>
<methodCall>
<methodName>system.listMethods</methodName>
</methodCall>
detect:
- if: schema.path_ref
is: /xmlrpc.php
- if: helpers.response.is_successful
is: true
References:
- https://wordpress.org/documentation/article/wordpress-xml-rpc-protocol/
- https://owasp.org/www-community/attacks/Brute_force_attack
- https://cwe.mitre.org/data/definitions/307.html
WordPress REST API user enumeration¶
Probe /wp/users and alert on a successful response — the WP REST API leaks the user list (id, slug, name) without authentication by default.
The WordPress REST API endpoint /wp-json/wp/v2/users (or /wp/users after a rewrite) returns the full author list to anonymous callers in default installs. The response includes slug, which is the username an attacker needs for brute force.
Sourced from the in-tree wordpress_rest_api_users_exposed simplecheck.
When to use: Any WordPress site. Disable via add_filter('rest_endpoints', …) or block at the WAF / reverse-proxy.
OWASP: A07:2021 Identification and Authentication Failures · CWE: CWE-200
Severity rationale: LOW alone — but combined with /wp-login.php exposure or xmlrpc.php, it creates a complete brute-force prerequisite chain.
Features used: seed, detect, schema.path_ref, helpers.response.is_successful
rule:
id: example-api-wordpress-rest-users-enum
type: API
alert:
name: WordPress REST API users endpoint exposed
context: |
`/wp/users` returned a successful response without
authentication, leaking the WordPress author list (id, slug,
name) to anonymous callers.
severity: LOW
category: INFORMATION_DISCLOSURE
seed:
- protocol: rest
method: GET
path: /wp/users
detect:
- if: schema.path_ref
is: /wp/users
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/
- https://developer.wordpress.org/rest-api/reference/users/
- https://cwe.mitre.org/data/definitions/200.html
Java stack trace disclosed in API response¶
Alert on any 5xx response whose body contains a Java stack trace fingerprint (at com., Caused by:, org.springframework, java.lang.).
Verbose error responses are an information disclosure mainstay: they reveal the framework (Spring, JPA, Jackson), the package structure, internal hostnames, and frequently the exact SQL/SQL-builder that failed.
The detector grep is conservative: it requires a multi-substring match (the keyword Caused by: together with at least one of the common package prefixes) so legitimate textual mentions of "java.lang" don't fire.
When to use: Any Java-based API. Useful both as a routine prod scan rule and as a CI gate to prevent stack traces from being committed to error-page responses.
OWASP: A09:2021 Security Logging and Monitoring Failures · CWE: CWE-209
Severity rationale: LOW alone — the exposure aids reconnaissance but is rarely directly exploitable. Aggregates with other findings to elevate severity.
Features used: detect, response.status_code, response.body.text contains, logical or
rule:
id: example-api-java-stack-trace
type: API
alert:
name: Java stack trace disclosed
context: |
The API returned a 5xx response containing a Java stack trace,
revealing the framework, package layout, and internal class
names.
severity: LOW
category: INFORMATION_DISCLOSURE
detect:
- if: response.status_code
gt: 499
- if: response.body.text
contains: 'Caused by:'
- if: or
or:
- if: response.body.text
contains: at com.
- if: response.body.text
contains: at org.springframework
- if: response.body.text
contains: java.lang.
References:
- https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/
- https://cwe.mitre.org/data/definitions/209.html
Sensitive field names leaked in JSON response¶
JQ-walk every successful JSON response for keys whose name implies a secret (password, secret, token, api_key, private_key, ssn) and alert when any is present and non-null.
APIs frequently leak server-side fields through over-eager serialization (User.toJson() returning the password hash, S3 presigners returning the AWS secret, OAuth callbacks returning the client secret).
The deterministic test scans every response body with a single JQ query that returns true if any field whose key matches a sensitive name carries a non-null value.
The query uses walk over .. | objects so it finds the field at any nesting depth.
When to use: Any API that returns JSON. Add or remove keys from the regex according to your data dictionary; the rule is most powerful when you tailor it to fields you know should never leave the server.
OWASP: API3:2023 BOPLA · CWE: CWE-200
Severity rationale: HIGH — direct credential / secret leak; treat as if the leaked value had been pasted into a public Slack channel.
Features used: detect, response.body.json (jq)
rule:
id: example-api-sensitive-fields-jq
type: API
alert:
name: Sensitive field present in API response
context: |
The response body contains a non-null value at a key whose name
implies a secret (`password`, `secret`, `token`, `api_key`,
`private_key`, `ssn`). The endpoint is over-serializing
sensitive fields.
severity: HIGH
category: SENSITIVE_DATA
detect:
- if: helpers.response.is_successful
is: true
- if: response.body.json
jq: |
any(.. | objects | to_entries[]?;
(.key | test("(?i)^(password|secret|token|api[_-]?key|private[_-]?key|ssn|client[_-]?secret)$"))
and (.value != null and .value != ""))
References:
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/200.html
Debug headers reinjected and surfaced in alert context¶
Add a Pragma: show-debug-headers header to every successful request and alert when the response leaks an internal application name via Cgp-Route-Ams-Application-Name, embedding the leaked value in the alert context using an extractor.
Some reverse proxies and routing layers honor magic headers (here Pragma: show-debug-headers) and leak internal routing or application metadata. Discovery is two-step:
transforminjects the magic header into every successful non-/graphqlrequest.detectfires when the response carriesCgp-Route-Ams-Application-Name.extractorscapture the leaked value intoshow_headers_valueso the alert context can render the actual exposed name — turning a generic finding into actionable evidence.
The rule is a reduced, anonymized variant of an internal Escape detection that has caught the issue in production scans.
When to use: Any REST API behind a load balancer or service mesh. Replace the header name in transform.mutate and the detect/extract path with the magic header your platform actually honors.
OWASP: API8:2023 Security Misconfiguration · CWE: CWE-200
Severity rationale: INFO by default — the leak rarely allows direct exploitation, but it enables targeted follow-up attacks against the named internal service.
Features used: transform, detect, extractors, request.headers mutator, response.headers detector, alert context templating
rule:
id: example-api-debug-header-application-name
type: API
alert:
name: Debug header exposes internal application name
context: |
The endpoint returned internal routing info via debug headers.
Cgp-Route-Ams-Application-Name = {{show_headers_value}}
severity: INFO
category: INFORMATION_DISCLOSURE
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: schema.url
regex: ^(?!.*/graphql).*$
mutate:
- key: request.headers
name: Pragma
value: show-debug-headers
detect:
- if: response.headers
key:
is: Cgp-Route-Ams-Application-Name
- if: schema.url
regex: ^(?!.*/graphql).*$
extractors:
- trigger:
- if: response.headers
key:
is: Cgp-Route-Ams-Application-Name
extract:
- key: response.headers
name: Cgp-Route-Ams-Application-Name
variable: show_headers_value
can_overwrite: true
References:
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
- https://cwe.mitre.org/data/definitions/200.html
GraphQL introspection enabled in production¶
Send the canonical introspection query to /graphql and alert if it returns a __schema object — introspection is enabled and leaks the full schema.
GraphQL introspection lets clients query the schema itself. It is indispensable in development (powers GraphiQL and codegen) and almost always meant to be disabled in production. A live introspection endpoint hands attackers the entire type graph, including type names that hint at internal admin operations.
The seeder posts the standard introspection query. The detector looks for __schema in the response body, which appears in every introspection result and almost never in any other response.
When to use: Any GraphQL endpoint. False-positive rate is essentially zero.
OWASP: API8:2023 Security Misconfiguration · CWE: CWE-200
Severity rationale: MEDIUM — schema disclosure is reconnaissance, not direct compromise; impact escalates when paired with overly-broad mutations discoverable via the leaked schema.
Features used: seed, detect, response.body.text contains
rule:
id: example-api-graphql-introspection-public
type: API
alert:
name: GraphQL introspection enabled
context: |
`POST /graphql` returned a successful response containing
`__schema`, indicating GraphQL introspection is enabled and the
schema is publicly readable.
severity: MEDIUM
category: INFORMATION_DISCLOSURE
seed:
- protocol: rest
method: POST
path: /graphql
headers:
Content-Type: application/json
body: '{"query":"{ __schema { types { name } } }"}'
detect:
- if: helpers.response.is_successful
is: true
- if: response.body.text
contains: __schema
References:
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
- https://graphql.org/learn/introspection/
- https://cwe.mitre.org/data/definitions/200.html
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:
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
- https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html
- https://cwe.mitre.org/data/definitions/200.html
Spring Boot heapdump downloadable¶
Probe the actuator /heapdump and /mappings endpoints and alert if the response contains heapdump and org.springframework, indicating a downloadable JVM heap dump.
A downloadable heapdump is one of the worst Spring Boot exposures. Heapdumps contain in-memory state — DB connection strings, OAuth secrets, customer PII actively being processed, JWT signing keys, everything the JVM is currently holding.
The detector requires both fingerprint substrings to fire, which suppresses generic 200 responses with no heap content.
Sourced from the in-tree springboot_actuator_heapdump simplecheck.
When to use: Any Spring Boot backend. Run early in a scan because a confirmed heapdump exposure usually moots the rest of the test plan.
OWASP: API8:2023 Security Misconfiguration · CWE: CWE-200
Severity rationale: HIGH — full memory disclosure is reliably weaponized into credential theft and lateral movement.
Features used: seed, detect, helpers.response.is_successful, response.status_code, response.body.text contains
rule:
id: example-api-spring-boot-heapdump
type: API
alert:
name: Spring Boot heapdump downloadable
context: |
A request to the actuator heapdump endpoint returned a
successful response containing JVM heap markers, indicating a
full memory dump is downloadable.
severity: HIGH
category: INFORMATION_DISCLOSURE
seed:
- protocol: rest
method: GET
path: /mappings
headers: {}
- protocol: rest
method: GET
path: /configprops
headers: {}
- protocol: rest
method: GET
path: /actuator/mappings
headers: {}
- protocol: rest
method: GET
path: /actuator/configprops
headers: {}
detect:
- if: helpers.response.is_successful
is: true
- if: response.status_code
is: 200
- if: response.body.text
contains: heapdump
- if: response.body.text
contains: org.springframework
References:
- https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/
- https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#heapdump
- https://cwe.mitre.org/data/definitions/200.html
Injection (8)¶
SQL injection in JSON body via JQ-targeted payloads¶
Walk every string field of a successful JSON request body and append a SQL-breaking payload to its value, then alert on a DBMS error in the response.
REST APIs that consume JSON usually deserialize it into typed objects. The classic concatenation-style SQLi sink is then accessible via any user-controlled string field that is passed unvalidated into a query.
The transform uses a JQ expression that walks the JSON structure and rewrites every string leaf by appending ' OR 1=1--. The detector then looks for the same DBMS error fingerprints used by the text-based SQLi rule.
When to use: Any REST or GraphQL endpoint that accepts JSON. Pair with 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:
OS command injection via shell substitution marker¶
Append ; echo escape-cmd-marker to every successful request body and alert when the marker appears in the response — the input was handed to a shell.
Command injection appears wherever an API passes user input to system, exec, backticks, Runtime.exec, os.system, subprocess.Popen(shell=True), or any equivalent. The deterministic fingerprint is to append a benign-looking shell chain (; echo …, && echo …) and check whether the echo output is reflected anywhere in the response.
The marker escape-cmd-marker is unique enough that any appearance in the response strongly indicates shell evaluation.
When to use: APIs that touch the filesystem, run external tools (image conversion, PDF rendering, archive extraction), or expose admin routes that obviously shell out (/diagnostics/ping, /admin/run, ...).
OWASP: A03:2021 Injection (Command) · CWE: CWE-78
Severity rationale: HIGH — direct OS command execution under the API service account. The Escape severity scale tops out at HIGH; treat confirmed RCE as a SEV-1 in your own incident process.
Features used: transform, detect, request.body.text mutator (values), response.body.text contains
rule:
id: example-api-command-injection-marker
type: API
alert:
name: OS command injection
context: |
A unique marker emitted by `echo escape-cmd-marker` appeared in
the response, indicating that the API passed user input to a
shell.
severity: HIGH
category: INJECTION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.body.text
regex: .+
mutate:
- key: request.body.text
values:
- ; echo escape-cmd-marker
- '`echo escape-cmd-marker`'
- $(echo escape-cmd-marker)
- '| echo escape-cmd-marker'
detect:
- if: response.body.text
contains: escape-cmd-marker
References:
- https://owasp.org/Top10/A03_2021-Injection/
- https://cwe.mitre.org/data/definitions/78.html
- https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html
HTTP header injection via CRLF in user-controlled input¶
Inject a CRLF sequence followed by a marker header into every string field of a successful request and alert if the response carries the marker as a real response header.
HTTP response splitting (a.k.a. CRLF injection) lets an attacker inject arbitrary response headers when user input is concatenated into a Set-Cookie, Location, or any other header. Browsers will then honor the injected header.
The deterministic fingerprint is to append \r\nX-Escape-CRLF: 1 to user-controlled strings and check whether X-Escape-CRLF appears in the response headers. A bare reflection in the body is not enough — the header has to be parsed by the HTTP client, which is exactly the impact we care about.
When to use: Any API that builds redirects, sets cookies, or otherwise constructs response headers from user input. Particularly relevant for legacy CGI / FastCGI backends.
OWASP: A03:2021 Injection (HTTP Response Splitting) · CWE: CWE-113
Severity rationale: HIGH — header injection enables session fixation, cache poisoning, and reflected XSS via injected Content-Type.
Features used: transform, detect, request.body.json mutator (jq), response.headers
rule:
id: example-api-header-injection-crlf
type: API
alert:
name: HTTP header injection via CRLF
context: |
A `\r\nX-Escape-CRLF: 1` suffix injected into user input was
reflected as a real response header, indicating HTTP response
splitting.
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) |= . + "\r\nX-Escape-CRLF: 1"'
detect:
- if: response.headers
key:
is: X-Escape-CRLF
References:
- https://owasp.org/www-community/attacks/HTTP_Response_Splitting
- https://cwe.mitre.org/data/definitions/113.html
NoSQL injection via operator object substitution¶
Replace string fields in a successful JSON request with $ne / $gt operator objects (Mongo-style) and alert if the response is still successful — the API forwards client-supplied operators to the database.
NoSQL backends like MongoDB accept query objects rather than SQL strings. When an API forwards user input as a value into a query document, an attacker can substitute the value with an operator object such as {"$ne": null} to match every record (effectively bypassing authentication or filtering).
The transform replaces every JSON string in the request with {"$ne": null} via JQ. If the API still returns success, it almost certainly passed the operator straight through to the database.
When to use: Any API backed by MongoDB, CouchDB, or another document store that accepts operator objects. The technique also works against PostgreSQL JSONB queries that use jsonb_path_query with user-controlled JSON.
OWASP: A03:2021 Injection (NoSQL) · CWE: CWE-943
Severity rationale: HIGH — typical impacts are authentication bypass, full collection enumeration, or arbitrary record updates.
Features used: transform, detect, request.body.json mutator (jq)
rule:
id: example-api-nosql-injection-operator
type: API
alert:
name: NoSQL operator injection accepted
context: |
A request body in which every string was replaced by
`{"$ne": null}` returned a successful response. The endpoint
forwards client-supplied operator objects to the database.
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) |= {"$ne": null}'
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/Top10/A03_2021-Injection/
- https://cwe.mitre.org/data/definitions/943.html
- https://cheatsheetseries.owasp.org/cheatsheets/NoSQL_Database_Injection_Prevention_Cheat_Sheet.html
Server-side template injection via arithmetic marker¶
Append ${7*7}{{7*7}} to every JSON string field of a successful request and alert if the response contains the rendered result 49 exactly twice — proving server-side template evaluation.
Server-side template injection (SSTI) happens when an API interpolates user input through a template engine such as Jinja2, Twig, ERB, FreeMarker, Velocity, or Smarty. Attackers can quickly progress from arithmetic markers to arbitrary code execution inside the template sandbox — and from there to the host.
The deterministic fingerprint is to inject the union of two common template syntaxes (${} and {{}}) holding the same arithmetic expression. If the response carries 4949 somewhere, at least one engine evaluated the marker.
When to use: Any API that renders user input through a server-side template: notification subjects, PDF generation, email previews, error messages with placeholders. SSTI is rare but devastating; this rule is cheap to leave on by default.
OWASP: A03:2021 Injection (SSTI) · CWE: CWE-1336
Severity rationale: HIGH — SSTI is consistently weaponized into RCE on the host within minutes (sandbox escape via class introspection). The Escape platform tops out at HIGH; treat it as RCE-equivalent in your own incident response.
Features used: transform, detect, request.body.json mutator (jq), response.body.text contains
rule:
id: example-api-ssti-arithmetic-marker
type: API
alert:
name: Server-side template injection
context: |
An arithmetic SSTI marker `${7*7}{{7*7}}` was rendered in the
response body, indicating that user input is interpolated
through a server-side template engine.
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) |= . + "${7*7}{{7*7}}"
detect:
- if: response.body.text
contains: '4949'
References:
- https://portswigger.net/research/server-side-template-injection
- https://cwe.mitre.org/data/definitions/1336.html
Reflected XSS in JSON response body¶
Append a unique XSS marker to every JSON string field in a successful request and alert if the marker is reflected verbatim in the response body.
Many REST APIs return user input back in the response (echoes, confirmations, validation messages). When that input is included in a server-rendered HTML page elsewhere — for example a SPA that interpolates a server-side template — the API becomes a delivery vector for reflected cross-site scripting.
The deterministic test mutates every string in the JSON body to end with <script>console.log("escape")</script> and asserts the exact payload appears in the response text. A 1:1 reflection proves no escaping was applied.
When to use: Any REST or GraphQL endpoint that echoes user input in its response. The test is deliberately conservative — it only fires on a verbatim reflection, so HTML-encoded responses do not produce false positives.
OWASP: A03:2021 Injection (XSS) · CWE: CWE-79
Severity rationale: HIGH when the response feeds an HTML render path; MEDIUM when the response is consumed only by trusted automation. Pick HIGH unless you are sure of the consumer.
Features used: transform, detect, request.body.json mutator (jq), response.body.text contains
rule:
id: example-api-reflected-xss-json-body
type: API
alert:
name: Reflected XSS marker in JSON response body
context: |
A unique XSS marker injected into every JSON string field was
reflected verbatim in the response body, indicating a missing
output-encoding step on the server.
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) |= . + "<script>console.log(\"escape\")</script>"
detect:
- if: response.body.text
contains: <script>console.log("escape")</script>
References:
SQL injection via error message disclosure¶
Replace string parameters in successful requests with classic SQL breaking payloads and alert if the response leaks a SQL syntax / DBMS error string.
Error-based SQL injection is the cheapest deterministic test: send a payload that breaks SQL syntax (', ' OR 1=1--, ") OR (1=1, ...) and look for a DBMS error in the response body. If the error appears, the payload reached an unsafe SQL concatenation.
The transform replays every successful request body with each of five canonical payloads (so it generates five requests per successful original). The detector matches a regex covering MySQL, Postgres, MSSQL, Oracle and SQLite error fingerprints.
When to use: Any REST or GraphQL API that accepts string parameters in request.body.text. For requests that carry JSON, prefer the request.body.json jq variant in examples/api/injection/sql-injection-jq-string-fields.yaml.
OWASP: API10:2023 Unsafe Consumption of APIs (SQLi) · CWE: CWE-89
Severity rationale: HIGH — SQLi typically grants arbitrary read of the underlying database and frequently full host compromise via UDFs / xp_cmdshell.
Features used: transform, detect, request.body.text mutator (values fuzz), response.body.text regex
rule:
id: example-api-sql-injection-error-disclosure
type: API
alert:
name: SQL injection error disclosure
context: |
A SQL-breaking payload caused the API to leak a DBMS error
string in the response body, indicating unsafe string
concatenation in a backend query.
severity: HIGH
category: INJECTION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.body.text
regex: .+
mutate:
- key: request.body.text
values:
- ''' OR 1=1--'
- ''') OR (''1''=''1'
- 1; DROP TABLE escape_test--
- '" OR "1"="1'
- 1' UNION SELECT NULL--
detect:
- if: response.body.text
regex: .*(syntax error|sql error|ORA-[0-9]+|mysql_fetch|psql:|sqlite3.OperationalError|SQLSTATE\[).*
References:
- https://owasp.org/Top10/A03_2021-Injection/
- https://cwe.mitre.org/data/definitions/89.html
- https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
XXE — billion laughs payload accepted¶
Replace XML request bodies with a billion-laughs entity expansion payload and alert if the API processes it without rejection, proving entity processing is enabled.
XML External Entity (XXE) injection happens when the parser expands attacker-controlled DOCTYPE entities. The cheapest deterministic test is the billion-laughs DOS payload: nested entities that expand to a small number of bytes but balloon when materialized.
A robust API rejects the request outright (4xx). A vulnerable one will either return 200 with a successful parse, or 5xx with a parser stack trace. This rule fires on the 200 case, which is the strongest evidence of unsafe entity processing.
When to use: Any API that accepts XML payloads (legacy SOAP, SAML callbacks, RSS submission, OPC UA bridges). For modern JSON-only APIs this rule is a no-op trigger and adds zero overhead.
OWASP: API10:2023 Unsafe Consumption of APIs (XXE) · CWE: CWE-611
Severity rationale: HIGH — XXE typically escalates to local file disclosure and blind SSRF.
Features used: transform, detect, request.body.text mutator (value), request.headers
rule:
id: example-api-xxe-billion-laughs
type: API
alert:
name: XML external entity processing enabled
context: |
The API returned a successful response when the request body was
replaced with a billion-laughs XML entity expansion payload. The
XML parser is configured to expand attacker-controlled entities.
severity: HIGH
category: INJECTION
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.headers
key:
is: Content-Type
value:
regex: .*xml.*
mutate:
- key: request.body.text
value: |
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;">
]>
<lolz>&lol3;</lolz>
detect:
- if: helpers.response.is_successful
is: true
References:
- https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing
- https://cwe.mitre.org/data/definitions/611.html
Protocol (2)¶
Authentication credentials sent over plaintext HTTP¶
Alert when an authenticated request is observed on http:// rather than https:// — credentials are being transmitted in cleartext.
Even one cleartext-HTTP authenticated request is enough for a network-path attacker to capture the bearer token / session cookie. Modern services should refuse plain HTTP outright.
The detector combines two signals: the URL begins with http:// (case-insensitive), AND request.is_authenticated is true. Background scanner traffic on plain HTTP that is deliberately unauthenticated does not fire.
When to use: Any service expected to be HTTPS-only. Useful as a CI gate against forgotten staging URLs.
OWASP: A02:2021 Cryptographic Failures · CWE: CWE-319
Severity rationale: HIGH — plaintext credential transmission is direct, deterministic compromise on a network adversary path.
Features used: detect, schema.url regex, request.is_authenticated
rule:
id: example-api-cleartext-http-credentials
type: API
alert:
name: Authenticated request on plaintext HTTP
context: |
An authenticated request was observed on `http://` rather than
`https://`. The credentials are being transmitted in
cleartext over the network.
severity: HIGH
category: PROTOCOL
detect:
- if: schema.url
regex: ^http://.*
- if: request.is_authenticated
is: true
References:
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
- https://cwe.mitre.org/data/definitions/319.html
OPTIONS preflight leaks unintended HTTP methods¶
Alert if the response to an OPTIONS request advertises TRACE or CONNECT in Allow / Access-Control-Allow-Methods — methods that should never be enabled on a production API.
TRACE and CONNECT are diagnostic HTTP methods routinely abused for cross-site tracing and request smuggling when left enabled. Production APIs should advertise only the methods they actually serve.
The detector triggers on OPTIONS responses and flags any Allow or Access-Control-Allow-Methods value that mentions TRACE or CONNECT.
When to use: Any HTTP API. The check is essentially free — OPTIONS is almost always emitted by the framework router itself.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-16
Severity rationale: LOW — these methods rarely yield direct exploitation today, but their presence indicates a misconfigured router worth fixing.
Features used: detect, request.method, response.headers regex, logical or
rule:
id: example-api-options-leaks-trace-connect
type: API
alert:
name: OPTIONS advertises TRACE / CONNECT
context: |
An `OPTIONS` response advertised `TRACE` or `CONNECT` in
`Allow` or `Access-Control-Allow-Methods`. These methods
should never be enabled on a production API.
severity: LOW
category: PROTOCOL
detect:
- if: request.method
is: OPTIONS
- if: or
or:
- if: response.headers
key:
is: Allow
value:
regex: .*(TRACE|CONNECT).*
- if: response.headers
key:
is: Access-Control-Allow-Methods
value:
regex: .*(TRACE|CONNECT).*
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.8
- https://cwe.mitre.org/data/definitions/16.html
Request Forgery (2)¶
Environment isolation — production reaches internal host¶
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:
- https://owasp.org/www-community/attacks/Server_Side_Request_Forgery
- https://cwe.mitre.org/data/definitions/918.html
SSRF via URL parameter pointing at internal target¶
For every successful request that has any string field, mutate each string to http://internal.example.com/probe and alert if the API made the outbound call (response now contains the probe target's body marker).
SSRF appears wherever an API takes a URL from user input (avatar import, webhook delivery, link unfurling, image proxying) and fetches it server-side. The deterministic test rewrites every string field of a successful request body to point at an internal probe URL and asserts the response body contains the probe's known marker.
The marker escape-ssrf-probe-marker is what the internal target should return in its body when reached. In a realistic setup that is your own controlled OOB endpoint (Burp Collaborator equivalent) — see examples/api/request_forgery/ssrf-blind-callback.yaml for blind detection.
When to use: Any API that takes URLs in payloads. Adjust the probe URL and the marker to your environment.
OWASP: API7:2023 SSRF · CWE: CWE-918
Severity rationale: HIGH — SSRF is reliably weaponized against cloud metadata endpoints and internal services.
Features used: transform, detect, request.body.json mutator (jq)
rule:
id: example-api-ssrf-via-url-parameter
type: API
alert:
name: SSRF via URL parameter
context: |
The API fetched an attacker-supplied internal URL when every
string field of the request body was rewritten to point at
`http://internal.example.com/probe`.
severity: HIGH
category: REQUEST_FORGERY
transform:
trigger:
- if: helpers.response.is_successful
is: true
- if: request.body.json
jq: any(.. | strings)
mutate:
- key: request.body.json
jq: (.. | strings) |= "http://internal.example.com/probe"
detect:
- if: response.body.text
contains: escape-ssrf-probe-marker
References:
- https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/
- https://cwe.mitre.org/data/definitions/918.html
Resource Limitation (2)¶
Missing rate-limit headers on authenticated route¶
Alert on every successful authenticated response that does not advertise any of the standard rate-limit headers — usage caps are either absent or invisible to clients.
Modern APIs surface their rate limits in headers (X-RateLimit-Limit, X-RateLimit-Remaining, RateLimit-Limit, ...). Their absence either means there is no rate limit at all, or the API silently throttles without telling clients — both are bad.
The detector OR's the most common header names so a single advertised limit is enough to satisfy the rule.
When to use: Any authenticated REST or GraphQL API. Particularly important on credential / login endpoints to deter brute-force.
OWASP: API4:2023 Unrestricted Resource Consumption · CWE: CWE-770
Severity rationale: LOW — observability finding rather than direct vulnerability; elevate to MEDIUM on credential-touching endpoints.
Features used: detect, request.is_authenticated, response.headers, logical not, logical or
rule:
id: example-api-missing-rate-limit-header
type: API
alert:
name: Missing rate-limit headers
context: |
An authenticated successful response did not advertise any
`X-RateLimit-*` / `RateLimit-*` / `Retry-After` header. Usage
caps are either absent or invisible to clients.
severity: LOW
category: RESOURCE_LIMITATION
detect:
- if: helpers.response.is_successful
is: true
- if: request.is_authenticated
is: true
- if: not
not:
if: or
or:
- if: response.headers
key:
regex: (?i)x-ratelimit-.*
- if: response.headers
key:
regex: (?i)ratelimit-.*
- if: response.headers
key:
is: Retry-After
References:
- https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/
- https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/
- https://cwe.mitre.org/data/definitions/770.html
Slow endpoint suggests unbounded query¶
Alert on any successful authenticated response whose duration exceeds 5 seconds — likely an unbounded list, missing pagination, or expensive join.
Latency outliers usually mean the request reached an unbounded database operation: SELECT * without LIMIT, missing pagination, recursive resolver in GraphQL, etc. They are both a DoS amplifier (cheap requests with expensive backend cost) and a UX problem.
The detector uses the response.duration_ms integer matcher with gt: 5000. Tighten or relax the threshold to your SLA.
When to use: Any API with a defined latency SLA. Particularly useful as a CI regression rule to flag list endpoints that lose pagination.
OWASP: API4:2023 Unrestricted Resource Consumption · CWE: CWE-405
Severity rationale: LOW — observability finding; aggregate severity rises with request volume on the slow endpoint.
Features used: detect, response.duration_ms
rule:
id: example-api-slow-endpoint
type: API
alert:
name: Slow endpoint
context: |
A successful authenticated response took more than 5 seconds,
indicating a potentially unbounded query, a missing
pagination cap, or an expensive join.
severity: LOW
category: RESOURCE_LIMITATION
detect:
- if: helpers.response.is_successful
is: true
- if: request.is_authenticated
is: true
- if: response.duration_ms
gt: 5000
References:
- https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/
- https://cwe.mitre.org/data/definitions/405.html
Sensitive Data (4)¶
JWT bearer token transmitted in URL query string¶
Alert on any request URL containing a JWT-shaped token in a query parameter — the token will be logged in proxy / CDN / browser history.
Bearer tokens belong in Authorization headers, never in URLs. URLs are persisted in:
- reverse-proxy access logs,
- CDN edge logs,
- browser history,
- Referer headers leaked to third-party scripts on the destination page.
The detector uses a regex against schema.url looking for the classic three-segment base64 JWT shape preceded by an = (any query parameter).
When to use: Any API. Particularly important to enforce in CI for any integration that historically used URL-based tokens (legacy SSO callbacks, magic-link flows).
OWASP: A02:2021 Cryptographic Failures · CWE: CWE-598
Severity rationale: MEDIUM — exposure surface is broad (logs, history) but exploitation requires reading those logs.
Features used: detect, schema.url regex
rule:
id: example-api-jwt-in-url-query
type: API
alert:
name: JWT bearer token transmitted in URL query string
context: |
The request URL contains a JWT-shaped token in a query
parameter, exposing the token to proxy / CDN / browser-history
logging.
severity: MEDIUM
category: SENSITIVE_DATA
detect:
- if: schema.url
regex: .*[?&][A-Za-z_-]+=eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+.*
References:
- https://datatracker.ietf.org/doc/html/rfc6750#section-5.3
- https://cwe.mitre.org/data/definitions/598.html
Credit-card number leaked in response body¶
Alert when a successful response body contains a string passing a basic credit-card pattern + Luhn shape — the API is leaking payment data.
Payment card data should never leave a PCI-scoped subsystem in plaintext. Cards are usually returned as last4 only; a full-PAN response indicates a serialization bug or a forgotten development field.
The detector regex matches Visa / MasterCard / Amex / Discover PAN shapes. Luhn validation is intentionally not performed in YAML — it would require a code-side detector — so this rule may fire on synthetic test PANs (e.g. 4111111111111111); treat matches as worth investigating rather than confirmed leaks.
When to use: Any payment-adjacent API. Particularly important on internal admin endpoints that may accidentally serialize the full card object.
OWASP: API3:2023 BOPLA · CWE: CWE-359
Severity rationale: HIGH — full PAN exposure has direct compliance impact (PCI DSS).
Features used: detect, response.body.text regex
rule:
id: example-api-credit-card-in-response
type: API
alert:
name: Credit-card PAN in response body
context: |
The response body contains a string matching a Visa /
MasterCard / Amex / Discover PAN shape, indicating possible
full-card-number disclosure.
severity: HIGH
category: SENSITIVE_DATA
detect:
- if: helpers.response.is_successful
is: true
- if: response.body.text
regex: .*\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12})\b.*
References:
- https://www.pcisecuritystandards.org/
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/359.html
Password hash leaked in API response¶
Alert when a successful response body contains a value matching a common password-hash format (bcrypt, scrypt, argon2, MD5, SHA-1) — the user object is over-serialized.
Password hashes leave the server only when the API returns the raw user model. This is almost always a bug introduced by a new developer running User.toJson() without an explicit field allow-list.
The detector regex matches the well-known prefixes for bcrypt ($2[ayb]$), scrypt ($s2$), argon2 ($argon2id$), and the canonical hex shapes for MD5 (32 hex chars) and SHA-1 (40 hex chars).
When to use: Any API that returns user objects. This rule pairs with examples/api/information_disclosure/sensitive-fields-jq.yaml for full coverage.
OWASP: API3:2023 BOPLA · CWE: CWE-200
Severity rationale: HIGH — leaked hashes are crackable offline at scale, defeating most password policies.
Features used: detect, response.body.text regex
rule:
id: example-api-password-hash-in-response
type: API
alert:
name: Password hash leaked in response body
context: |
The response body contains a value matching a bcrypt / argon2
/ scrypt / MD5 / SHA-1 hash format. The user object is
over-serializing the password hash field.
severity: HIGH
category: SENSITIVE_DATA
detect:
- if: helpers.response.is_successful
is: true
- if: response.body.text
regex: .*(\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}|\$argon2id?\$v=[0-9]+\$|\$s2\$[0-9]+\$).*
References:
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/200.html
PII (email addresses) leaked in unauthenticated response¶
Alert when an unauthenticated successful response contains more than one email address — the endpoint is leaking user PII to anonymous callers.
Endpoints that returned a single email belonging to the caller are usually intentional (/me, /profile). Endpoints that return multiple emails to an anonymous caller almost never are — they leak the user list (a precursor to credential stuffing).
The detector uses helpers.regex_matches.count against an email regex with gt: 1, requiring strictly more than one unique email match in the response.
When to use: Any public listing endpoint. Tune the threshold (gt: 1) up or down based on your tolerance.
OWASP: API3:2023 BOPLA · CWE: CWE-359
Severity rationale: MEDIUM — leaked email lists fuel credential stuffing and targeted phishing.
Features used: detect, request.is_authenticated, helpers.regex_matches.count
rule:
id: example-api-pii-emails-in-response
type: API
alert:
name: Multiple email addresses leaked to anonymous caller
context: |
An unauthenticated successful response contained more than one
email address, indicating the endpoint is leaking user PII to
anonymous callers.
severity: MEDIUM
category: SENSITIVE_DATA
detect:
- if: request.is_authenticated
is: false
- if: helpers.response.is_successful
is: true
- if: helpers.regex_matches.count
regex: '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
gt: 1
References:
- https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
- https://cwe.mitre.org/data/definitions/359.html