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. 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 |
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
aaaamatchesaaaa,xxaaaayy,aaaa123, and only fails onaaab. 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'sre.fullmatch(...), HTML<input pattern="...">. In those environmentsaaaaindeed won't matchxxaaaayy, 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, soaaaameans "value contains aaaa".Pattern value aaaavalue xxaaaayyvalue aaabActual 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 onecondition_value, expressing "A or B or C" in a single rule relies on regex alternation. For exampleaaaa|bbb|^aaameans "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-Agentanduser-agentare equivalent; but the Match Value regex matching is case-sensitive —aaaadoesn't matchAAAA. 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_valuelength cap is 1024 bytes. Split overly long alternation lists into multiple rules.
Expressiveness boundaries
- There's no separate
containstype: for "contains" semantics use an unanchoredregex(or fall back to multipleexact/prefixrules). - 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 "allowif 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
- 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.
- Sort by
sort_orderascending (smaller evaluated first; same order by scope global → key group → access key), skippingenabled = falserules. - Evaluate one by one: match by
condition_type(anydoesn't read headers, condition always true; other types take thecondition_keyheader's value to match). - The first matching rule decides the result:
deny→ immediately return 403;allow→ pass to the next layer. - When no rule matches, fall back along the default_action chain: per-key
acl_default_action→ per-groupacl_default_action→ globalAllow.
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 for common usage and complex config examples; Access key and key group fields for the key-group dimension; Audit and security configuration for rate limiting and IP banning.
