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

# Define advanced traffic rules with a YAML policy file

> Use a YAML policy file to define named network groups from ASNs, CIDRs, or remote URLs and write flexible, expressive per-rule conditions.

The YAML policy file gives you more advanced traffic control than inline rules or the plain-text rules file. It lets you define named network groups — built from ASNs, CIDR ranges, remote URLs, or local files — and then write rules with flexible boolean conditions that reference those groups. To enable it, set `policy_file` in `config.json` to the path of your YAML file. When `policy_file` is set, it takes priority over `rules_file` and any inline `rules` defined in `config.json`.

## Structure of a policy file

A policy file has up to three top-level keys:

* **`networks`** — named groups of IP ranges (required if your rules use `remoteAddress.network(...)`).
* **`conditions`** — reusable named condition groups (optional).
* **`rules`** — the ordered list of rules to evaluate (required).

Below is the example policy from `examples/policy.yaml`:

```yaml theme={null}
networks:
  huawei-cloud:
    - asn: 136907
    - asn: 55990
    - asn: 138915

  alibaba-cloud:
    - asn: 24429
    - asn: 134963

  cloudflare-v4:
    - url: https://www.cloudflare.com/ips-v4

  private-nets:
    - prefixes:
        - 10.0.0.0/8
        - 172.16.0.0/12
        - 192.168.0.0/16

rules:
  - name: allow-home
    conditions:
      - 'path == "/"'
    action: allow

  - name: allow-private
    conditions:
      - 'remoteAddress.network("private-nets")'
    action: pass

  - name: undesired-networks
    conditions:
      - 'remoteAddress.network("huawei-cloud") || remoteAddress.network("alibaba-cloud") || remoteAddress.network("cloudflare-v4")'
    action: drop

  - name: always-protected
    conditions:
      - 'path == "/sensitive-path"'
    action: check
```

## Defining networks

The `networks` section maps a name to one or more source entries. verifyngo resolves all entries at startup and builds a fast lookup table for checking IPs at request time. You can mix source types within the same group.

| Source                  | Description                                                          |
| ----------------------- | -------------------------------------------------------------------- |
| `asn: <number>`         | Fetches the ASN's announced prefixes from RADb whois at startup.     |
| `cidr: "x.x.x.x/y"`     | Adds a single explicit CIDR range (or bare IP).                      |
| `prefixes: [...]`       | Adds a list of explicit CIDR ranges.                                 |
| `url: "https://..."`    | Fetches a list of CIDRs from a remote URL (one per line by default). |
| `file: "/path/to/file"` | Reads CIDRs from a local file (one per line).                        |

For `url` and `file` sources you can add a `jq-path` key to extract CIDRs from a JSON response, or a `regex` key with a named capture group called `prefix` to extract CIDRs from arbitrary text.

```yaml theme={null}
networks:
  cloudflare-v4:
    - url: https://www.cloudflare.com/ips-v4

  bad-asns:
    - asn: 136907
    - asn: 55990

  private-nets:
    - prefixes:
        - 10.0.0.0/8
        - 172.16.0.0/12
        - 192.168.0.0/16
```

## Writing rules

Each rule in the `rules` list has a `name`, one or more `conditions`, and an `action`. Rules are evaluated top to bottom; the first matching rule wins. If no rule matches, `default_action` from `config.json` applies.

Available actions are `allow` / `pass` (let the request through), `challenge` / `check` (serve a CAPTCHA), and `deny` / `drop` (block immediately). The paired words are aliases for the same behaviour.

```yaml theme={null}
rules:
  - name: allow-private
    conditions:
      - 'remoteAddress.network("private-nets")'
    action: pass

  - name: block-bad-asns
    conditions:
      - 'remoteAddress.network("bad-asns")'
    action: deny

  - name: always-challenge-sensitive
    conditions:
      - 'path == "/admin"'
    action: challenge
```

When a rule has more than one condition, **all** conditions must be true for the rule to match (logical AND).

### Condition variables

These variables are available in every condition expression:

* **`path`** — the request URL path string (e.g. `"/blog/post-1"`).
* **`userAgent`** — the value of the `User-Agent` request header.
* **`method`** — the HTTP method (`GET`, `POST`, etc.).
* **`ip`** — the client IP address as a string.
* **`headers["Header-Name"]`** — the value of any request header, accessed by its exact name.
* **`remoteAddress.network("name")`** — evaluates to `true` if the client IP is a member of the named network group.

### Condition functions

Three helper functions are available for string matching:

* **`contains(str, substr)`** — true if `str` contains `substr`.
* **`startsWith(str, prefix)`** — true if `str` starts with `prefix`.
* **`matches(str, regex)`** — true if `str` matches the regular expression `regex`.

You can also call these as methods: `userAgent.contains("bot")`, `path.startsWith("/api")`, `path.matches("^/post/[0-9]+")`.

## Named condition groups

If the same set of conditions appears in multiple rules, define it once under `conditions` and reference it in rules using `($name)`. When a rule contains a `($name)` reference, verifyngo expands the group inline and OR-joins the individual expressions within it before applying the rule's AND logic.

```yaml theme={null}
conditions:
  is-bot-ua:
    - 'userAgent.contains("bot")'
    - 'userAgent.contains("crawler")'

rules:
  - name: block-bots
    conditions:
      - '($is-bot-ua)'
    action: deny
```

<Note>
  Hot-reload applies to the policy file too. verifyngo watches the file and picks up any changes within about 10 seconds — no restart required.
</Note>
