> Raw Markdown twin (generated at build time from the source Markdown). Rendered page: https://docs.gatellm.io/en/reference/header-acl-rules · Doc index: https://docs.gatellm.io/en/llms.txt


# Header ACL rule fields

Access rules do admission control based on HTTP request headers, supporting three scopes: global, key group, and access key. Rules match in ascending `sort_order`, and the first hit decides allow/deny.

> **Note**: access rules apply to both **inference routes** (`/v1/chat/completions`, `/v1/messages`, etc.) and **model-list routes** (`/v1/models`, `/v1beta/models`). When you deny a header pattern, model-list queries are also blocked. Operators should be aware of this behavior to avoid a misconfiguration preventing clients from fetching the model list.

For task-oriented common usage and examples see [Configure Header ACL access rules](/en/howto/configure-header-acl.md). This chapter is the field/semantics reference.

## Rule fields

| Field | Description |
|------|------|
| **Rule Name** | Unique rule identifier, globally unique. Prefix with the scope (e.g. `key:mykey__rule1`) to avoid name collisions. Immutable after creation. |
| **Order** | Sort weight, ascending match; smaller is evaluated first. Rules with the same order are evaluated by scope **global → key group → access key** (i.e. at the same order, global rules hit first); so a key-level or group-level rule that wants to override a same-order global rule must use a smaller order. |
| **Header Name** | The HTTP request header name to match (e.g. `User-Agent`, `X-Forwarded-For`). Case-insensitive. Leave **empty** when Match Type is `any` (that type doesn't inspect headers). |
| **Match Type** | Match method, see the table below. |
| **Match Value** | The match value. Not needed for `exists` / `absent` / `any`. |
| **Action** | `allow` (permit) or `deny` (reject, returns 403). |
| **Scope** | Scope: `Global` (all requests), `Key` (one or more access keys), `Group` (one or more key groups). Key and Group are mutually exclusive, multi-select supported. |
| **Scope value** | When Scope is Key or Group, select the target key or key group from a dropdown. |
| **Enabled** | Rule switch. Disabled rules are ignored at load time, not affecting other rules. |

## Match types

| Type | Description | Example |
|------|------|------|
| `exact` | Header value equals Match Value exactly | `User-Agent` = `BadBot/1.0` |
| `prefix` | Header value starts with Match Value | `User-Agent` starts with `python-requests/` |
| `regex` | Header value matches a regular expression | `User-Agent` matches `(?i)curl|wget|scrapy` |
| `exists` | Header exists (regardless of value) | check whether `X-Custom-Header` exists |
| `absent` | Header doesn't exist | check whether `Authorization` is missing |
| `any` | Condition always true, no header inspected; the rule acts purely by scope | "allow/deny all" for a key: Match Type=`any` + Scope=`Key`, Header Name empty |

## regex matching semantics

`regex` is the most flexible and most easily misunderstood type; understand these points before using it:

- **Default is "contains", not "full match".** A regular expression does a **search** under standard semantics — it looks for a substring in the header value that satisfies the pattern, and does **not** require matching the whole value. So an unanchored pattern is naturally contains: the pattern `aaaa` matches `aaaa`, `xxaaaayy`, `aaaa123`, and only fails on `aaab`. To require a full match you must **explicitly add start/end anchors** `^...$`, and for prefix matching add the start anchor `^...`.

  > The source of a common misconception: some languages or APIs **implicitly add start/end anchors** to regexes, making them behave as full matches — e.g. Java's `String.matches(...)`, Python's `re.fullmatch(...)`, HTML `<input pattern="...">`. In those environments `aaaa` indeed won't match `xxaaaayy`, leaving the impression that "regex can't do contains". That's implicit anchoring added by those APIs, not regex semantics itself. This engine's regex matching is **contains** semantics and **unanchored**, so `aaaa` means "value contains aaaa".

  | Pattern | value `aaaa` | value `xxaaaayy` | value `aaab` | Actual semantics |
  |------|:---:|:---:|:---:|------|
  | `aaaa` | ✅ | ✅ | ❌ | contains |
  | `^aaaa$` | ✅ | ❌ | ❌ | exact |
  | `^aaa` | ✅ | — | — | starts with `aaa` (prefix) |

- **`|` means "or", and can combine multiple conditions in one rule.** Because one rule holds only one `condition_value`, expressing "A or B or C" in a single rule relies on regex alternation. For example `aaaa|bbb|^aaa` means "contains aaaa, or contains bbb, or starts with aaa". Note that `^` only anchors the branch it appears in.

- **Case: header names are insensitive, header values are sensitive.** Header Names are lowercased at load time, so `User-Agent` and `user-agent` are equivalent; but the Match Value regex matching is **case-sensitive** — `aaaa` doesn't match `AAAA`. To ignore case, wrap with an inline flag: `(?i:aaaa|bbb)`.

- **The regex engine is a linear-time implementation, ReDoS-resistant.** User-supplied regexes are protected at load time by size and complexity caps; malicious or pathological patterns are rejected (rules that fail to compile are skipped and a warning is logged, not affecting other rules). The cost is that **backreferences and lookaround are unsupported** (e.g. `(?=...)`, `(?!...)`, `\1`). Therefore a negative condition like "value does **not** contain substring X" **cannot be expressed in a single regex** — see the expressiveness boundary below.

- **`condition_value` length cap is 1024 bytes.** Split overly long alternation lists into multiple rules.

## Expressiveness boundaries

- **There's no separate `contains` type**: for "contains" semantics use an unanchored `regex` (or fall back to multiple `exact`/`prefix` rules).
- **Negative matching requires rule orchestration**: the regex engine doesn't support lookahead, so "value doesn't contain X" can't be written as a single regex. To express "allow only if it doesn't contain X", orchestrate in reverse — set the default_action to `deny`, then use a rule "`allow` if it contains X" to permit matches; or split into multiple rules.
- **Multi-condition "and" requires splitting into multiple rules**: one rule has only one condition and can't write AND inside it. To require "satisfies both A and B", combine the default_action with multiple rules' first-match-wins semantics.

## Evaluation logic

1. Merge all rules in the current request's scope: global rules + the current key's per-key rules + the current group's per-group rules.
2. Sort by `sort_order` ascending (smaller evaluated first; same order by scope global → key group → access key), skipping `enabled = false` rules.
3. Evaluate one by one: match by `condition_type` (`any` doesn't read headers, condition always true; other types take the `condition_key` header's value to match).
4. The **first matching** rule decides the result: `deny` → immediately return 403; `allow` → pass to the next layer.
5. When no rule matches, fall back along the default_action chain: per-key `acl_default_action` → per-group `acl_default_action` → global `Allow`.

## FAQ

**Q: The rule changed but didn't take effect?**
Rules that fail to compile are skipped and a warning is logged. Check whether `condition_value` conforms to the syntax (lookahead / backreferences unsupported). `condition_value` length cap is 1024 bytes.

**Q: How do I make a key group's rule override a global rule?**
At the same order, global hits first. To override, use a smaller order, or set the global rule to `enabled = false`.

**Q: How do I "unconditionally allow/deny" a key?**
Match Type=`any` + Scope=`Key` + Header Name empty. The rule acts purely by scope, applying to all requests of that key.

**Q: Are model-list calls also blocked?**
Yes, rules apply to both `/v1/models` and `/v1beta/models`. Operators should be aware of this to avoid a misconfiguration preventing clients from fetching the model list.

**Next**: [Configure Header ACL access rules](/en/howto/configure-header-acl.md) for common usage and complex config examples; [Access key and key group fields](/en/reference/access-keys-groups-fields.md) for the key-group dimension; [Audit and security configuration](/en/reference/audit-and-security-config.md) for rate limiting and IP banning.
