Skip to content
This page is a translation of the authoritative Chinese source and may lag behind.View the original

Configure Header ACL access rules ​

Access rules perform admission control based on HTTP request headers, with three scopes: global, key group, and access key. Rules are matched in ascending sort_order; the first match decides allow/deny.

Entry point: Console → Access Keys → Access Rules tab.

Note: access rules apply to both the inference routes (/v1/chat/completions, /v1/messages, etc.) and the model list routes (/v1/models, /v1beta/models). When a header pattern is denied, model-list queries are also blocked. Operators must be aware of this behavior to avoid misconfiguration that prevents clients from fetching the model list.

Rule fields ​

FieldDescription
Rule NameUnique identifier for the rule, globally unique. Use a scope prefix (e.g. key:mykey__rule1) to avoid naming collisions. Cannot be changed after creation.
OrderSort weight, matched in ascending order; smaller numbers are evaluated first. Rules with the same order are evaluated in scope order Global → Key Group → Access Key (i.e. under the same order a global rule matches first); therefore a key-level or group-level rule that wants to override a same-order global rule must use a smaller order.
Header NameThe HTTP request header name to match (e.g. User-Agent, X-Forwarded-For). Case-insensitive. Leave blank when Match Type is any (that type does not check a request header).
Match TypeThe match method, see the table below.
Match ValueThe value to match. Not needed for the exists / absent / any types.
Actionallow or deny (deny returns 403).
ScopeThe scope: Global (all requests), Key (one or more access keys), Group (one or more key groups). Key and Group are mutually exclusive, and both support multi-select.
Scope valueWhen Scope is Key or Group, select the target key or key group from the dropdown.
EnabledThe rule's on/off switch. A disabled rule is ignored at load time and does not affect other rules.

The table above shows console UI labels; the configuration examples below use the underlying field names, which map as: Header Name = condition_key, Match Type = condition_type, Match Value = condition_value, Action = action, Scope = scope, Order = sort_order.

Match types ​

TypeDescriptionExample
exactThe header value exactly equals Match ValueUser-Agent = BadBot/1.0
prefixThe header value starts with Match ValueUser-Agent starts with python-requests/
regexThe header value matches a regular expressionUser-Agent matches `(?i)curl
existsThe header is present (regardless of value)Check whether X-Custom-Header is present
absentThe header is absentCheck whether Authorization is missing
anyThe condition is always true; no header is checked, and the rule only takes effect via its Scope"Allow/deny all" for a key: Match Type=any + Scope=Key, Header Name left blank

See Header ACL rule fields for the full reference on field semantics, match types, regex matching semantics, and evaluation logic.

Common usages ​

  • Block crawlers: condition_key = User-Agent, condition_type = regex, condition_value = (?i)scrapy|crawl|spider, action = deny, scope = Global.
  • Restrict a specific IP range: condition_key = X-Forwarded-For, condition_type = prefix, condition_value = 192.168., action = deny, scope = Global. ⚠️ X-Forwarded-For is a client-forgeable header — it is only reliable when there is a trusted reverse proxy in front of the gateway that overwrites it; otherwise a malicious client can fill in that header arbitrarily to bypass the restriction. For real IP admission control, prefer the real source IP seen directly by the gateway (with TRUSTED_PROXIES) over the forgeable X-Forwarded-For.
  • Only allow requests with a custom header: condition_key = X-Api-Token, condition_type = absent, action = deny, scope = Global (deny when the header is missing).
  • Restrict a specific key group: set scope = Group, select the target key group; the rule only affects keys within that group.
  • Allow/deny all for a key (no header condition): set Match Type = any, Scope = Key and select the target key, leave Header Name blank — the rule applies to all requests from that key. To use it to override a global deny, give it a smaller order than the global rule (under the same order, global wins).

Verifying that a rule takes effect ​

After configuring a rule, verify with paired curl requests. Using the "block crawlers" rule above (User-Agent containing scrapy → deny) as an example:

bash
# Control group: normal UA, expected to pass (200)
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:7890/v1/chat/completions \
  -H "Authorization: Bearer <your access key>" \
  -H "User-Agent: my-app/1.0" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'

# Test group: UA matches, expected to be blocked (403)
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:7890/v1/chat/completions \
  -H "Authorization: Bearer <your access key>" \
  -H "User-Agent: scrapy/2.11" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'

Returning 200 and 403 respectively means the rule works. If the test group still returns 200, check: ① whether the rule's Enabled is on; ② whether it is matched first by an allow rule with a smaller order; ③ whether the scope covers this key.

Complex configuration examples ​

The following examples demonstrate how to combine regex with first-match-wins semantics to build more complex policies. Fields are given as key = value lines; smaller order is evaluated first.

Example 1: one rule expressing "contains A or contains B or starts with C" ​

text
condition_key   = User-Agent
condition_type  = regex
condition_value = aaaa|bbb|^aaa
action          = deny
scope           = Global
  • The aaaa branch: value contains aaaa (un-anchored = contains).
  • The bbb branch: value contains bbb.
  • The ^aaa branch: value starts with aaa (the leading anchor applies only to this branch).
  • The three branches are joined with |; matching any one of them denies. Equivalent to merging three rules into one.

Example 2: case-insensitive multi-keyword containment ​

text
condition_key   = User-Agent
condition_type  = regex
condition_value = (?i:scrapy|crawl|spider|bot)
action          = deny
scope           = Global

(?i:...) makes the alternation inside the parentheses case-insensitive, so Scrapy, CRAWL, and Spider all match. Note that (?i) only affects the range it wraps, and does not affect the rest of the rule.

Example 3: multi-value exact matching (a case exact cannot handle) ​

exact can only compare against a single value at a time. To express "the value is exactly one of v1, v2, v3", use an alternation with leading and trailing anchors:

text
condition_key   = X-Api-Version
condition_type  = regex
condition_value = ^(v1|v2|v3)$
action          = allow
scope           = Global

^...$ tightens the search semantics to a full match: v1, v2, v3 match; v10, xv1x do not.

Example 4: use contains semantics to block values containing a specific substring ​

The engine has no standalone contains type, but an un-anchored regex is itself a contains. To block "values of the X-Debug header that contain the substring debug":

text
condition_key   = X-Debug
condition_type  = regex
condition_value = debug
action          = deny
scope           = Global

For example debug matches debug, my-debug-flag, but does not match DEBUG (value matching is case-sensitive); write (?i)debug if you need case-insensitivity.

Example 5: allowlist mode (default_action deny as fallback + a single allow) ​

Requirement: a key group should only allow clients with a trusted prefix, denying everything else. Use "fall back to default_action when no rule matches":

  1. Set the key group's (or global) acl_default_action to Deny.
  2. Add one allow rule:
text
order           = 10
condition_key   = User-Agent
condition_type  = regex
condition_value = ^trusted-
action          = allow
scope           = Group   (select the target key group)

Effect: value starting with trusted- → matches the rule → allow; other values have no matching rule → fall to default_action → deny. There is no need to write a "deny everything" fallback rule.

Example 6: allow takes precedence over deny (order orchestration) ​

Requirement: deny common crawlers, but allow your own monitoring probe (whose UA also contains bot, which the crawler rule would wrongly hit). With first-match-wins, the allow rule's order must be smaller:

text
# Rule A: allow your own probe first
order           = 10
condition_key   = User-Agent
condition_type  = regex
condition_value = ^health-probe/
action          = allow
scope           = Global

# Rule B: then deny crawlers
order           = 20
condition_key   = User-Agent
condition_type  = regex
condition_value = (?i:scrapy|crawl|spider|bot)
action          = deny
scope           = Global

health-probe/bot-1 is matched by rule A first → allow, rule B is not evaluated; a normal crawler UA does not match A and falls to B → deny. If the two orders are reversed, the probe is blocked by B first.

Example 7: "does not contain a substring" requires reverse orchestration ​

The regex engine does not support lookahead, so "allow only if the value does not contain internal" cannot be written as a single regex. Reverse-orchestrate it:

  1. acl_default_action = Deny.
  2. Add a rule "allow if it contains internal": condition_type = regex, condition_value = internal, action = allow.

The semantics become "contains internal → allow; otherwise (contains other values or nothing) → no match → default deny". If the requirement is "deny when it contains internal, allow the rest", set default_action to Allow and add a single internal → deny rule instead.

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 numbers evaluated first; under the same order, the scope order Global → Key Group → Access Key applies), skipping rules with enabled = false.
  3. Evaluate one by one: match by condition_type (any does not read a request header and is always true; the other types take the value of the request header condition_key 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 client gets 403 but the key is correct? Check whether it is blocked by Header ACL. Rules apply to both inference routes and model-list routes; the client may be blocked as early as the first /v1/models call. Temporarily disable the relevant rule or raise its order to troubleshoot.

Q: The rule changed but doesn't take effect? A rule that fails to compile is skipped and a warning is logged. Check whether condition_value is syntactically valid (lookahead / backreferences are not supported). condition_value has a 1024-byte length limit.

Q: How do I let a key group's rule override a global rule? Under the same order, global matches first. To override, use a smaller order, or set the global rule to enabled = false.

Q: How do I "allow/deny unconditionally" for a specific key? Match Type=any + Scope=Key + Header Name left blank. The rule takes effect only via its scope, applying to all requests from that key.

Q: Are model-list calls blocked too? Yes, rules apply to both /v1/models and /v1beta/models. Operators must be aware of this behavior to avoid misconfiguration that prevents clients from fetching the model list.

Next: Header ACL rule fields for the full reference on regex semantics and expressiveness limits; Access key & key group fields for the key group dimension; Audit & security config for rate limiting and IP blocking.