> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracenow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Rules

> Build policies that challenge or block requests, covering conditions, actions, ordering, and worked examples.

Rules are how you tell tracenow what to do when it detects fraud signals. A **policy** groups
rules for a specific threat and event type. Each **rule** is a set of **conditions** with an
action. The first rule whose conditions all match wins.

<Tip>
  You usually don't start here. The [policy templates](/guides/policy-templates) ship ready-made
  policies you enable in one click. Read this page when you want to tune a template's rules or
  add your own for logic specific to your app.
</Tip>

## The policy → rule → condition model

```
Policy  (threat: account-takeover, event: login)
  └─ Rule 1  "Block credential stuffing"   action: deny   position: 1
       └─ Condition: velocity.ip.failed_count_1h  gt  5
       └─ Condition: event.status           eq  "failed"   (AND)
  └─ Rule 2  "Challenge VPN logins"         action: challenge  position: 2
       └─ Condition: ip.is_vpn              eq  true
  └─ Rule 3  "Challenge new device"         action: challenge  position: 3
       └─ Condition: device.is_new_for_user eq  true
       └─ Condition: event.status           eq  "succeeded"    (AND)
  └─ (implicit allow — nothing matched)
```

* **Policy**: belongs to one threat (fake accounts, account takeover, SMS pumping) and one
  event type (`signup`, `login`, `otp`). Rules inside it apply only to traces of that event.
* **Rule**: has a name, a position, an action, a logic (`AND` / `OR`), and one or more
  conditions.
* **Condition**: a single test: `field  operator  value`. Operators are `eq`, `gt`, `lt`.
* **First-match-wins**: rules are evaluated top to bottom by position. The first rule whose
  conditions are all satisfied determines the verdict. If no rule matches, the verdict is
  `allow` (implicit).

## Opening the rule editor

In the [dashboard](https://tracenow.io/dashboard), go to **Guard**. Each policy card shows
its rules. Click **Add rule** to create a new one, or click an existing rule to edit it.

<Frame caption="Rule editor: open with conditions and action">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/tracenow/images/rules-rule-editor.png" alt="Rule editor showing condition fields, operator dropdown, value input, and action selector" />
</Frame>

**📸 Screenshot to add:** the rule editor modal. Show a condition row with the field picker, operator dropdown (`eq / gt / lt`), value field, the AND/OR toggle, and the action buttons (`challenge` / `deny`).

## Creating a rule

<Steps>
  <Step title="Name the rule">
    Give the rule a descriptive name. It appears in the event feed when a trace matches, so
    make it readable: `Block credential stuffing`, `Challenge TOR logins`, `Flag disposable email`.
  </Step>

  <Step title="Add conditions">
    Each condition has three parts:

    | Part         | Description                                                            |
    | ------------ | ---------------------------------------------------------------------- |
    | **Field**    | A signal path, e.g. `velocity.ip.failed_count_1h` or `ip.is_vpn`       |
    | **Operator** | `eq` (equals), `gt` (greater than), `lt` (less than)                   |
    | **Value**    | A boolean (`true` / `false`), number, or string (`"failed"`, `"high"`) |

    See [Signals](/concepts/signals) for the complete list of fields you can use.

    Click **+ Add condition** to add more. Use the **AND / OR** toggle to control logic:

    * **AND**: all conditions must match (default; use for precision).
    * **OR**: any condition is enough (use for catch-all rules like "any automation signal").
  </Step>

  <Step title="Choose an action">
    | Action      | What happens                                                                              |
    | ----------- | ----------------------------------------------------------------------------------------- |
    | `challenge` | Verdict is `challenge`: forwarded to origin; enforce step-up in your app                  |
    | `deny`      | Verdict is `deny`: blocked at the Cloudflare edge (Enforce mode) or logged (Monitor mode) |

    There is no explicit `allow` action. If no rule matches, the request is allowed implicitly.
  </Step>

  <Step title="Set the position">
    Position controls evaluation order. Lower numbers run first. Drag rules to reorder them, or
    type a position number. Put your most specific, high-confidence rules first so they don't
    get shadowed by broad catch-alls.
  </Step>

  <Step title="Save">
    Click **Save rule**. The rule is added to the policy immediately. In Monitor mode you'll
    start seeing matching traces without any user impact.
  </Step>
</Steps>

## Conditions in detail

### Field reference

Conditions reference signal fields from the `/trace` response. See [Signals](/concepts/signals)
for the authoritative list. A few common ones:

| Field                            | Type    | Example value |
| -------------------------------- | ------- | ------------- |
| `velocity.ip.failed_count_1h`    | number  | `12`          |
| `velocity.ip.trace_count_5m`     | number  | `8`           |
| `velocity.user.distinct_ips_24h` | number  | `5`           |
| `ip.is_vpn`                      | boolean | `true`        |
| `ip.is_tor`                      | boolean | `true`        |
| `email.is_disposable`            | boolean | `true`        |
| `email.domain_age_days`          | number  | `3`           |
| `device.is_headless`             | boolean | `true`        |
| `device.is_new_for_user`         | boolean | `true`        |
| `event.status`                   | string  | `"failed"`    |
| `phone.carrier_country_risk`     | string  | `"high"`      |

### Operators

| Operator | Applies to              | Meaning        |
| -------- | ----------------------- | -------------- |
| `eq`     | boolean, string, number | Exactly equals |
| `gt`     | number                  | Greater than   |
| `lt`     | number                  | Less than      |

### AND vs OR logic

A rule's logic applies across **all** its conditions:

```
# AND: both must be true
velocity.ip.failed_count_1h  gt  5
event.status                 eq  "failed"
→ only triggers if there are more than 5 failures AND the current attempt also failed

# OR: either is enough
ip.is_tor    eq  true
ip.is_proxy  eq  true
→ triggers if the IP is TOR or a proxy
```

## First-match-wins ordering

Rules are evaluated top to bottom. The **first rule that fully matches wins**; lower rules are
not evaluated. This means:

* Put narrow, high-confidence `deny` rules **above** broad `challenge` rules.
* A `deny` at position 1 prevents a `challenge` at position 2 from ever running on the same
  request.
* An `allow`-equivalent (no rule) only applies when **every** rule fails to match.

## Monitor vs Enforce mode

Rules themselves don't have a mode. **The Cloudflare integration route rule has the mode**
(configured per route in [Cloudflare → Routes](/guides/cloudflare)). The distinction:

| Mode    | Verdict recorded | Enforcement                                                 |
| ------- | ---------------- | ----------------------------------------------------------- |
| Monitor | Yes              | None (the request always reaches origin)                    |
| Enforce | Yes              | `deny` → blocked at edge; `challenge` / `allow` → forwarded |

Use Monitor to validate your rules against real traffic before switching to Enforce.

## Setting the blocked response

When a route is in Enforce mode and a `deny` verdict is issued, the Worker returns a response
you configure. Set it per-route in the Cloudflare integration settings:

* **Status code** (default `403`)
* **302 redirect**: send users to a `/blocked` page
* **Custom HTML**: a branded error page returned to browsers
* **Custom JSON**: an error object returned to API/XHR clients

See [Cloudflare integration → Set it up](/guides/cloudflare#set-it-up) for full details.

## Worked examples

<AccordionGroup>
  <Accordion title="Challenge VPN logins">
    **Policy:** account-takeover / login

    | Condition   | Operator | Value  |
    | ----------- | -------- | ------ |
    | `ip.is_vpn` | `eq`     | `true` |

    **Action:** `challenge` | **Logic:** AND | **Position:** 1

    Sends any login from a VPN to step-up (MFA/CAPTCHA) in your app. Lower false-positive
    rate than blocking outright, since many legitimate users use VPNs.
  </Accordion>

  <Accordion title="Block credential stuffing">
    **Policy:** account-takeover / login

    | Condition                     | Operator | Value      |
    | ----------------------------- | -------- | ---------- |
    | `velocity.ip.failed_count_1h` | `gt`     | `5`        |
    | `event.status`                | `eq`     | `"failed"` |

    **Action:** `deny` | **Logic:** AND | **Position:** 1

    Blocks an IP that has already failed 5+ login attempts in the last hour and is currently
    failing again. Stops credential stuffing loops cold.

    <Note>
      This uses `event.status eq "failed"` so it only triggers when the current attempt also
      failed, so you don't block a legitimate user whose IP happened to share a /24 with a bot.
      For the outcome status to be accurate, send it in the [server-side call](/guides/server-side).
    </Note>
  </Accordion>

  <Accordion title="Block automation at signup">
    **Policy:** fake-accounts / signup

    | Condition                    | Operator | Value  |
    | ---------------------------- | -------- | ------ |
    | `device.is_headless`         | `eq`     | `true` |
    | `device.automation_detected` | `eq`     | `true` |

    **Action:** `deny` | **Logic:** OR | **Position:** 1

    Blocks signups where the device is a headless browser or shows automation signals. Using
    OR catches both Playwright/Puppeteer runs (`is_headless`) and more subtle scripts
    (`automation_detected`).
  </Accordion>

  <Accordion title="Deny disposable email signups">
    **Policy:** fake-accounts / signup

    | Condition             | Operator | Value  |
    | --------------------- | -------- | ------ |
    | `email.is_disposable` | `eq`     | `true` |

    **Action:** `deny` | **Logic:** AND | **Position:** 2

    Hard-blocks signups with throwaway email addresses. Position 2 because the automation
    rule at position 1 is more severe and should run first.
  </Accordion>

  <Accordion title="Block OTP pumping: rate limit">
    **Policy:** SMS-pumping / otp

    | Condition                    | Operator | Value |
    | ---------------------------- | -------- | ----- |
    | `velocity.ip.trace_count_5m` | `gt`     | `5`   |

    **Action:** `deny` | **Logic:** AND | **Position:** 1

    Blocks any IP that fires more than 5 OTP requests in 5 minutes. This is the telltale pattern of
    a pumping script hammering your OTP send endpoint.
  </Accordion>

  <Accordion title="Challenge new device + successful login">
    **Policy:** account-takeover / login

    | Condition                | Operator | Value         |
    | ------------------------ | -------- | ------------- |
    | `device.is_new_for_user` | `eq`     | `true`        |
    | `event.status`           | `eq`     | `"succeeded"` |

    **Action:** `challenge` | **Logic:** AND | **Position:** 3

    Triggers step-up when a user successfully logs in from a device tracenow has never seen
    for their account. Requires the [server-side call](/guides/server-side) to supply the
    real `subject_user_id` (the edge has no user context).
  </Accordion>
</AccordionGroup>

## Related

<Columns cols={2}>
  <Card title="Signals" icon="signal" href="/concepts/signals">
    Every field you can use in conditions, with types and example values.
  </Card>

  <Card title="Verdicts" icon="circle-check" href="/concepts/verdicts">
    What allow / challenge / deny mean and how to act on them.
  </Card>

  <Card title="Cloudflare integration" icon="cloud" href="/guides/cloudflare">
    Route rules, Monitor vs Enforce mode, and the blocked response editor.
  </Card>

  <Card title="Server-side integration" icon="code" href="/guides/server-side">
    Add the in-app call to unlock per-user signals like new device and impossible travel.
  </Card>
</Columns>
