Policy Behavior
Conditional Chain Configuration
A conditional chain entry runs a control only for requests that match a condition: a path, a header, a model, a team, or a gateway. Use it to add optional checks for part of your traffic. Keep mandatory input protection unconditional.
Unlike a route, which replaces the whole chain, a condition turns one entry on or off inside the chain.
Add one condition
A plain string entry always runs. An object entry has one key, the name of the control, and its conditions inside. In this file, PII redaction always runs, and a term block runs only when the request has x-docs-review: strict:
pack:
name: conditional-check
version: 1.0.0
enabled: true
policies:
chain:
- pii-detector
- dlp-filter:
when:
header:
x-docs-review: strict
policy:
pii-detector:
action: redact
dlp-filter:
blocked_terms: [internal-project-cobalt]
fuzzy_matching: false
action: blockThe header is a preference that the client sets. It does not prove who the client is.
Verify the two branches
Add a test for each branch to the same file:
testing:
suites:
- name: conditional-dlp
cases:
- name: strict-header-blocks-term
input:
headers:
x-docs-review: strict
messages:
- role: user
content: "Summarize internal-project-cobalt."
expected:
verdict: block
reason_code: dlp.blocked
- name: no-header-allows-term
input:
messages:
- role: user
content: "Summarize internal-project-cobalt."
expected:
verdict: allow
reason_code: ok
- name: pii-redacted-without-header
input:
messages:
- role: user
content: "My SSN is 123-45-6789"
expected:
verdict: redact
reason_code: redact.appliedRun verdictan policy test --json. All three cases pass: the term is blocked only with the header, and PII redaction runs without it. See Test Configuration for the case format.
Fields
Chain entry fields
| Field | Type | Default | Description |
|---|---|---|---|
when | object | Conditions that must match before the entry runs. An empty when always matches. | |
stage | string | Default of the control | pre-request, post-request, or pre-response |
parallel | boolean | false | Runs the entry at the same time as the other parallel entries of the stage |
targeting | object | Limits the entry to teams or gateways |
when fields
Every condition that you set must match.
| Field | Type | Description |
|---|---|---|
path | string | Prefix of the request path |
header | map of strings | Each header must have this value. The value comparison ignores case. Write header names in lowercase. |
model | array of strings | The request model must be exactly one of these values. An empty list matches every request. |
Stage values
| Stage | Runs |
|---|---|
pre-request | Before the gateway sends the request upstream |
post-request | After the gateway prepares the request, before final response handling |
pre-response | Before the gateway returns the response to the caller |
Without stage, most controls run in pre-request. These run in pre-response by default and have their own runtime handlers: flagged-review, quality-scorer, human-oversight, citation-verifier, mnpi-filter, financial-compliance, healthcare-compliance, legal-privilege, upl-filter, bias-monitor, response-rewriter, and request-rewriter. request-rewriter still changes the request before it is sent.
The runtime handlers of these controls ignore when and stage. If one of them is in the active chain, it runs. To limit them to part of your traffic, use routes or consumer groups, or targeting, which change the active chain.
A stage override does not teach a control a new phase or request family. Read the page of the control and Runtime Request Families before you change a stage.
targeting fields
| Field | Type | Default | Description |
|---|---|---|---|
scope | string | organization | organization or team |
teams | array of strings | Team slugs. Required with scope: team. The gateway compares them with the authenticated team membership from the API, not with data from the caller. | |
gateways | string, array, or object | All gateways | One exact gateway name, a list of names, "*", or { regex: "<pattern>" }. The comparison ignores case. |
Examples
Path, header, and model predicates
policies:
chain:
- prompt-injection:
when:
path: "/v1/chat"
- dlp-filter:
when:
header:
x-team: "compliance"
x-environment: "prod"
- pii-detector:
when:
model:
- "your-openai-model"
- "your-second-openai-model"Team and gateway targeting
dlp-filter runs only for members of the compliance team, on gateways whose names start with prod-:
policies:
chain:
- audit-logger
- dlp-filter:
targeting:
scope: team
teams: [compliance]
gateways: {regex: "^prod-.*$"}Parallel execution in one stage
Set parallel: true on entries of the same stage that do not depend on each other. The gateway waits for all of them before it continues.
policies:
chain:
- prompt-injection:
stage: pre-request
parallel: true
- pii-detector:
stage: pre-request
parallel: true
- dlp-filter:
stage: pre-request
parallel: falseGateway selector formats
Use one form of targeting.gateways:
| Select | Value |
|---|---|
| One gateway | gateways: prod-east |
| Two gateways | gateways: [prod-east, prod-west] |
| A name pattern | gateways: {regex: "^prod-.*$"} |
A gateway name selects where a rule runs. It does not authenticate anything.
Complete example
The file in Add one condition is a complete pack for local tests. Before you start a gateway with it, add a working target from Provider Configuration. Then send a request for each header branch through a test agent.
For team controls that are mandatory, use targeting.teams, never a team header from the caller. A team-scoped entry does not run for a request without team context, so do not use it for rules that apply to the whole organization.
Limits and failure behavior
- An object entry with more than one key is a load error.
stage: post-responsepassespolicy lintbut fails when the gateway loads the file. Usepre-response.targeting.proxiesis a load error. Usetargeting.gateways.- An empty
gatewayslist, or aregexthat is not valid, is a load error. - A team-scoped entry runs only when the authenticated teams of the request overlap
teams. With no team context, or an emptyteams, it does not run. It never widens to the whole organization. - On connected and authenticated gateways, team context comes only from API membership. Callers cannot set it.
when.modelis an exact string match. Aliases and case differences do not match.whenhas no effect on the output controls that have their own runtime handler, listed under Stage values. Use routes for them.
Conditional entries or routes
| Task | Use |
|---|---|
| Different chains for Chat Completions and Responses | Routes |
| One chain with some input controls only for a path, header, or model | Conditional entries |
Output controls, such as human-oversight, only for some requests | Routes |
| Controls for specific teams or gateways | Conditional entries |
Both select policy controls only. Provider selection belongs in providers.
Next steps
- Routes and Consumer Groups: select a whole chain by path or API key.
- Test Configuration: test each branch of a condition.