# Examples for APIs (43)

This page lists the API custom rule examples and templates shipped with Escape (43 total), grouped by the vulnerability category they target. Use them as starting points. Copy the `rule:` block, edit hostnames and users to your scan target, and ship.

## Categories

- [Access Control](<#category-access-control>) (9)
- [Configuration](<#category-configuration>) (6)
- [Information Disclosure](<#category-information-disclosure>) (10)
- [Injection](<#category-injection>) (8)
- [Protocol](<#category-protocol>) (2)
- [Request Forgery](<#category-request-forgery>) (2)
- [Resource Limitation](<#category-resource-limitation>) (2)
- [Sensitive Data](<#category-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 doesn't 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](<https://cwe.mitre.org/data/definitions/352.html>)

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

```yaml
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://owasp.org/Top10/A01_2021-Broken_Access_Control/>)
- [https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site\_Request\_Forgery\_Prevention\_Cheat\_Sheet.html](<https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html>)
- [https://cwe.mitre.org/data/definitions/352.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 doesn't 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](<https://cwe.mitre.org/data/definitions/639.html>)

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

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

---

### BOLA Via User Swap with Fingerprint Comparison

Replay every successful authenticated request as a different user and flag responses that succeed AND return the same body fingerprint: classic Broken Object Level Authorization.

Broken Object Level Authorization (BOLA / IDOR) is the [\#1](<https://github.com/icarossio/escape-technologies/issues/1> "GitHub Issue: icarossio/escape-technologies #1") API risk in the OWASP API Top 10. The pattern is straightforward: endpoint `X` correctly authenticates `tester@example.com` and returns its own data, but never re-checks ownership when a different user calls the same URL: so `attacker@example.com` gets the same response.

This rule encodes the test deterministically:

1. `transform.trigger` selects every successful request issued by `tester@example.com`.
2. `transform.mutate` re-issues that request with `request.user` set to `attacker@example.com`.
3. `detect` fires when the replayed response is also successful AND its body fingerprint matches the original: meaning the API returned the first user's data to the second user.

**When to use:** Any REST or GraphQL endpoint that returns user-scoped data. Pair with your authentication configuration in the Escape platform: define at least two users with disjoint resources for the fingerprint check to be meaningful.

**OWASP:** API1:2023 BOLA · **CWE:** [CWE-639](<https://cwe.mitre.org/data/definitions/639.html>)

**Severity rationale:** HIGH: cross-tenant data leakage is typically a reportable security incident and a frequent cause of GDPR / SOC 2 findings.

**Features used:** `transform`, `detect`, `request.user mutator`, `helpers.fingerprints.same`, `helpers.response.is_successful`

```yaml
rule:
  id: example-api-bola-user-swap-fingerprint
  type: API
  alert:
    name: Possible BOLA via user swap
    context: |
      A successful authenticated response was returned with an identical
      body fingerprint when the same request was replayed as a different
      user. The endpoint is likely missing an object-level authorization
      check.
    severity: HIGH
    category: ACCESS_CONTROL
  transform:
    trigger:
    - if: helpers.response.is_successful
      is: true
    - if: request.user
      is: tester@example.com
    mutate:
    - key: request.user
      value: attacker@example.com
  detect:
  - if: helpers.response.is_successful
    is: true
  - if: helpers.fingerprints.same
    is: true
```

**References:**

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

---

### Horizontal Privilege Escalation Via User Swap on a Write

Re-issue every successful CREATE / UPDATE / DELETE issued by `tester@example.com` as `attacker@example.com` and alert if the write still succeeds: the endpoint is missing horizontal authorization.

Horizontal authorization failures appear when user A can perform a state-changing operation on a resource that belongs to user B. Unlike BOLA (which is read-side), this rule targets writes: delete-someone-else's-record, update-someone-else's-profile, cancel-someone-else's-order.

The trigger picks successful writes by `tester@example.com`. The mutator switches `request.user` to `attacker@example.com`. If the replayed write is still successful, the API never re-checked ownership of the targeted resource against the new caller.

**When to use:** Any API where users can mutate records keyed by a path/body id. Pair with the platform's authentication configuration: define `tester@example.com` and `attacker@example.com` with disjoint resources before enabling this rule.

**OWASP:** API1:2023 BOLA · **CWE:** [CWE-639](<https://cwe.mitre.org/data/definitions/639.html>)

**Severity rationale:** HIGH: horizontal write escalation typically lets attackers delete or alter records belonging to other tenants and can be weaponized for denial-of-service or sabotage.

**Features used:** `transform`, `detect`, `helpers.request.crud`, `request.user mutator`

```yaml
rule:
  id: example-api-horizontal-privilege-escalation-write
  type: API
  alert:
    name: Horizontal privilege escalation on write
    context: |
      A successful write issued by `tester@example.com` was replayed as
      `attacker@example.com` and still succeeded. The endpoint is
      missing a horizontal authorization check.
    severity: HIGH
    category: ACCESS_CONTROL
  transform:
    trigger:
    - if: helpers.response.is_successful
      is: true
    - if: helpers.request.crud
      in:
      - CREATE
      - UPDATE
      - DELETE
    - if: request.user
      is: tester@example.com
    mutate:
    - key: request.user
      value: attacker@example.com
  detect:
  - if: helpers.response.is_successful
    is: true
```

**References:**

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

---

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

1. The `extractors` block listens for any successful response from `tester@example.com` on a write endpoint and captures `.id` from the JSON body into the `victim_resource_id` variable.
2. The `transform` block triggers on subsequent successful reads by `attacker@example.com` whose URL contains `/{{victim_resource_id}}`. It rewrites the request user back to `attacker@example.com` (a no-op here, but kept so the same shape can be extended to swap users when needed).
3. `detect` fires when `attacker@example.com` successfully 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](<https://cwe.mitre.org/data/definitions/639.html>)

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

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

---

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

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/>)
- [https://cwe.mitre.org/data/definitions/347.html](<https://cwe.mitre.org/data/definitions/347.html>)
- [https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/](<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 isn't 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](<https://cwe.mitre.org/data/definitions/306.html>)

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa2-broken-authentication/>)
- [https://cwe.mitre.org/data/definitions/306.html](<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 a server-side data model without a field allow-list. An attacker can then pass extra fields (`is_admin`, `role`, `verified`, `subscription_tier`, ...) and elevate their own state.

The deterministic test:

1. `transform.trigger` selects every successful CREATE / UPDATE issued by `tester@example.com` whose JSON body doesn't yet carry an admin marker.
2. `transform.mutate` adds `is_admin: true` and `role: admin` using a JQ expression.
3. `detect` fires when the augmented request is still successful: proving the server accepted the privileged fields.

**When to use:** Any REST or GraphQL endpoint that accepts a JSON object describing a user-owned resource (profile, account, organization, settings). Adjust the JQ expression to the field names your API uses.

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

**Severity rationale:** HIGH: successful mass assignment typically grants full admin impersonation or unbounded subscription upgrades.

**Features used:** `transform`, `detect`, `request.body.json mutator (jq)`, `helpers.request.crud`

```yaml
rule:
  id: example-api-mass-assignment-role-injection
  type: API
  alert:
    name: Mass assignment accepted privileged fields
    context: |
      A request augmented with `is_admin: true` and `role: admin` was
      accepted by the API. The endpoint deserializes user-supplied
      fields into a server-side data model without a field allow-list.
    severity: HIGH
    category: ACCESS_CONTROL
  transform:
    trigger:
    - if: helpers.response.is_successful
      is: true
    - if: helpers.request.crud
      in:
      - CREATE
      - UPDATE
    - if: request.user
      is: tester@example.com
    mutate:
    - key: request.body.json
      jq: '. + {"is_admin": true, "role": "admin"}'
  detect:
  - if: helpers.response.is_successful
    is: true
```

**References:**

- [https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/](<https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/915.html](<https://cwe.mitre.org/data/definitions/915.html>)
- [https://cheatsheetseries.owasp.org/cheatsheets/Mass\_Assignment\_Cheat\_Sheet.html](<https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html>)

---

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

**Severity rationale:** HIGH: anonymous admin access is reliably exploitable and one of the most common high-severity misconfigurations in real-world API security reports.

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

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

---

## Configuration (6)

### Missing Strict-Transport-Security Header

Alert on any successful HTTPS response that doesn't carry an HSTS header: the browser isn't 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](<https://cwe.mitre.org/data/definitions/319.html>)

**Severity rationale:** LOW: the attack requires network-path adversary positioning.

**Features used:** `detect`, `schema.url`, `response.headers`, `logical not`

```yaml
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://owasp.org/www-project-secure-headers/#http-strict-transport-security>)
- [https://datatracker.ietf.org/doc/html/rfc6797](<https://datatracker.ietf.org/doc/html/rfc6797>)
- [https://cwe.mitre.org/data/definitions/319.html](<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:

1. `transform.trigger` selects every successful authenticated request that carries an `Origin` header (that is browser-issued).
2. `transform.mutate` rewrites `Origin` to `https://attacker.example`.
3. `detect` fires when the response carries `Access-Control-Allow-Origin: https://attacker.example` AND `Access-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](<https://cwe.mitre.org/data/definitions/942.html>)

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

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

- [https://portswigger.net/web-security/cors](<https://portswigger.net/web-security/cors>)
- [https://cwe.mitre.org/data/definitions/942.html](<https://cwe.mitre.org/data/definitions/942.html>)

---

### 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 aren't 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](<https://cwe.mitre.org/data/definitions/942.html>)

**Severity rationale:** MEDIUM: straightforward exfiltration of authenticated response bodies from any web origin.

**Features used:** `detect`, `request.is_authenticated`, `response.headers`

```yaml
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://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny>)
- [https://cwe.mitre.org/data/definitions/942.html](<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](<https://cwe.mitre.org/data/definitions/538.html>)

**Severity rationale:** HIGH: exposed env files are a direct credential leak.

**Features used:** `seed`, `detect`, `response.body.text contains`, `logical or`

```yaml
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://owasp.org/Top10/A05_2021-Security_Misconfiguration/>)
- [https://cwe.mitre.org/data/definitions/538.html](<https://cwe.mitre.org/data/definitions/538.html>)

---

### Missing X-Frame-Options on HTML Responses

Alert on every HTML response that doesn't 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](<https://cwe.mitre.org/data/definitions/1021.html>)

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

```yaml
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://owasp.org/www-community/attacks/Clickjacking>)
- [https://cwe.mitre.org/data/definitions/1021.html](<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](<https://cwe.mitre.org/data/definitions/672.html>)

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa9-improper-inventory-management/>)
- [https://cwe.mitre.org/data/definitions/672.html](<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](<https://cwe.mitre.org/data/definitions/200.html>)

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

```yaml
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://www.php.net/manual/en/function.phpinfo.php>)
- [https://owasp.org/Top10/A05\_2021-Security\_Misconfiguration/](<https://owasp.org/Top10/A05_2021-Security_Misconfiguration/>)
- [https://cwe.mitre.org/data/definitions/200.html](<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 doesn't fire.

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

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

```yaml
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://owasp.org/Top10/A05_2021-Security_Misconfiguration/>)
- [https://cwe.mitre.org/data/definitions/540.html](<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')`.

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

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

```yaml
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://wordpress.org/documentation/article/wordpress-xml-rpc-protocol/>)
- [https://owasp.org/www-community/attacks/Brute\_force\_attack](<https://owasp.org/www-community/attacks/Brute_force_attack>)
- [https://cwe.mitre.org/data/definitions/307.html](<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.

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

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

```yaml
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://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/>)
- [https://developer.wordpress.org/rest-api/reference/users/](<https://developer.wordpress.org/rest-api/reference/users/>)
- [https://cwe.mitre.org/data/definitions/200.html](<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](<https://cwe.mitre.org/data/definitions/209.html>)

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

```yaml
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://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/>)
- [https://cwe.mitre.org/data/definitions/209.html](<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](<https://cwe.mitre.org/data/definitions/200.html>)

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

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

1. `transform` injects the magic header into every successful non-`/graphql` request.
2. `detect` fires when the response carries `Cgp-Route-Ams-Application-Name`.
3. `extractors` capture the leaked value into `show_headers_value` so 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](<https://cwe.mitre.org/data/definitions/200.html>)

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/>)
- [https://cwe.mitre.org/data/definitions/200.html](<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's 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](<https://cwe.mitre.org/data/definitions/200.html>)

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/>)
- [https://graphql.org/learn/introspection/](<https://graphql.org/learn/introspection/>)
- [https://cwe.mitre.org/data/definitions/200.html](<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.

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

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/>)
- [https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html](<https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html>)
- [https://cwe.mitre.org/data/definitions/200.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.

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

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/>)
- [https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/\#heapdump](<https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/#heapdump>)
- [https://cwe.mitre.org/data/definitions/200.html](<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 the [error-disclosure SQLi rule](<https://docs.escape.tech/documentation/dast/custom-rules/examples-api/#example-api-injection-sql-injection-error-disclosure>) for text-encoded request bodies.

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

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

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

```yaml
rule:
  id: example-api-sql-injection-jq-string-fields
  type: API
  alert:
    name: SQL injection via JSON string field
    context: |
      A SQL-breaking suffix appended to every string value in the
      request body caused the API to leak a DBMS error string in the
      response.
    severity: HIGH
    category: INJECTION
  transform:
    trigger:
    - if: helpers.response.is_successful
      is: true
    - if: request.body.json
      jq: any(.. | strings)
    mutate:
    - key: request.body.json
      jq: (.. | strings) |= . + "' OR 1=1--"
  detect:
  - if: response.body.text
    regex: .*(syntax error|sql error|ORA-[0-9]+|mysql_fetch|psql:|sqlite3.OperationalError|SQLSTATE\[).*
```

**References:**

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

---

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

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

```yaml
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://owasp.org/Top10/A03_2021-Injection/>)
- [https://cwe.mitre.org/data/definitions/78.html](<https://cwe.mitre.org/data/definitions/78.html>)
- [https://cheatsheetseries.owasp.org/cheatsheets/OS\_Command\_Injection\_Defense\_Cheat\_Sheet.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 isn't 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](<https://cwe.mitre.org/data/definitions/113.html>)

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

```yaml
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://owasp.org/www-community/attacks/HTTP_Response_Splitting>)
- [https://cwe.mitre.org/data/definitions/113.html](<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](<https://cwe.mitre.org/data/definitions/943.html>)

**Severity rationale:** HIGH: typical impacts are authentication bypass, full collection enumeration, or arbitrary record updates.

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

```yaml
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://owasp.org/Top10/A03_2021-Injection/>)
- [https://cwe.mitre.org/data/definitions/943.html](<https://cwe.mitre.org/data/definitions/943.html>)
- [https://cheatsheetseries.owasp.org/cheatsheets/NoSQL\_Database\_Injection\_Prevention\_Cheat\_Sheet.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](<https://cwe.mitre.org/data/definitions/1336.html>)

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

```yaml
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://portswigger.net/research/server-side-template-injection>)
- [https://cwe.mitre.org/data/definitions/1336.html](<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 don't produce false positives.

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

**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're sure of the consumer.

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

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

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

---

### 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](<https://docs.escape.tech/documentation/dast/custom-rules/examples-api/#example-api-injection-sql-injection-jq-string-fields>).

**OWASP:** API10:2023 Unsafe Consumption of APIs (SQLi) · **CWE:** [CWE-89](<https://cwe.mitre.org/data/definitions/89.html>)

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

```yaml
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://owasp.org/Top10/A03_2021-Injection/>)
- [https://cwe.mitre.org/data/definitions/89.html](<https://cwe.mitre.org/data/definitions/89.html>)
- [https://cheatsheetseries.owasp.org/cheatsheets/SQL\_Injection\_Prevention\_Cheat\_Sheet.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](<https://cwe.mitre.org/data/definitions/611.html>)

**Severity rationale:** HIGH: XXE typically escalates to local file disclosure and blind SSRF.

**Features used:** `transform`, `detect`, `request.body.text mutator (value)`, `request.headers`

```yaml
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://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing>)
- [https://cwe.mitre.org/data/definitions/611.html](<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 doesn't 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](<https://cwe.mitre.org/data/definitions/319.html>)

**Severity rationale:** HIGH: plaintext credential transmission is direct, deterministic compromise on a network adversary path.

**Features used:** `detect`, `schema.url regex`, `request.is_authenticated`

```yaml
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://owasp.org/Top10/A02_2021-Cryptographic_Failures/>)
- [https://cwe.mitre.org/data/definitions/319.html](<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](<https://cwe.mitre.org/data/definitions/16.html>)

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

```yaml
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://owasp.org/Top10/A05_2021-Security_Misconfiguration/>)
- [https://datatracker.ietf.org/doc/html/rfc7231\#section-4.3.8](<https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.8>)
- [https://cwe.mitre.org/data/definitions/16.html](<https://cwe.mitre.org/data/definitions/16.html>)

---

## Request Forgery (2)

### Environment Isolation for Private Hosts

Use the raw HTTP seeder to call a private host (`internal.example.com`) and alert on a successful response when the production environment isn't 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 this from the same scanning environment you want to validate. Replace `internal.example.com` with the actual hostname you want to assert is unreachable.

**OWASP:** A05:2021 Security Misconfiguration · **CWE:** [CWE-918](<https://cwe.mitre.org/data/definitions/918.html>)

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

```yaml
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://owasp.org/www-community/attacks/Server_Side_Request_Forgery>)
- [https://cwe.mitre.org/data/definitions/918.html](<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); for blind variants, point the probe at an external callback service and assert on a side-channel signal instead of the response body.

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

**Severity rationale:** HIGH: SSRF is reliably weaponized against cloud metadata endpoints and internal services.

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/>)
- [https://cwe.mitre.org/data/definitions/918.html](<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 doesn't 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's 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](<https://cwe.mitre.org/data/definitions/770.html>)

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

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/>)
- [https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/](<https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/>)
- [https://cwe.mitre.org/data/definitions/770.html](<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 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](<https://cwe.mitre.org/data/definitions/405.html>)

**Severity rationale:** LOW: observability finding; aggregate severity rises with request volume on the slow endpoint.

**Features used:** `detect`, `response.duration_ms`

```yaml
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://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/>)
- [https://cwe.mitre.org/data/definitions/405.html](<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](<https://cwe.mitre.org/data/definitions/598.html>)

**Severity rationale:** MEDIUM: exposure surface is broad (logs, history) but exploitation requires reading those logs.

**Features used:** `detect`, `schema.url regex`

```yaml
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://datatracker.ietf.org/doc/html/rfc6750#section-5.3>)
- [https://cwe.mitre.org/data/definitions/598.html](<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 (for example `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](<https://cwe.mitre.org/data/definitions/359.html>)

**Severity rationale:** HIGH: full PAN exposure has direct compliance impact (PCI DSS).

**Features used:** `detect`, `response.body.text regex`

```yaml
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://www.pcisecuritystandards.org/>)
- [https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/](<https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/>)
- [https://cwe.mitre.org/data/definitions/359.html](<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 the [sensitive-fields jq rule](<https://docs.escape.tech/documentation/dast/custom-rules/examples-api/#example-api-information-disclosure-sensitive-fields-jq>) for full coverage.

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

**Severity rationale:** HIGH: leaked hashes are crackable offline at scale, defeating most password policies.

**Features used:** `detect`, `response.body.text regex`

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

**Severity rationale:** MEDIUM: leaked email lists fuel credential stuffing and targeted phishing.

**Features used:** `detect`, `request.is_authenticated`, `helpers.regex_matches.count`

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

---
