Input Controls
Dual-Use Filter
dual-use-filter blocks requests that mention dual-use items, such as centrifuge or maraging steel, before they reach the provider. You supply the terms, or use its built-in list.
A match is a text signal only. It does not classify an item legally, and it does not understand the intent of the request.
Configuration
pack:
name: dual-use-filter-example
version: 1.0.0
enabled: true
policies:
chain:
- dual-use-filter
policy:
dual-use-filter:
blocked_terms:
- centrifuge
- precursor chemical
- maraging steel
- uranium enrichment
action: block
fuzzy_matching: falseVerify exact matches first
Add these tests and run verdictan policy test --json:
testing:
suites:
- name: dual-use-filter
cases:
- name: blocks-centrifuge
input: {messages: [{role: user, content: "Describe a centrifuge"}]}
expected: {verdict: block, reason_code: dual_use.triggered}
- name: allows-schedule
input: {messages: [{role: user, content: "Summarize the public schedule"}]}
expected: {verdict: allow, reason_code: ok}Both cases pass. Matching ignores case and uses word boundaries, so a harmless discussion of a listed item is also blocked. Add real allowed and blocked examples from your users before rollout.
With gateway 0.1.26 or later, action: redact replaces each matched term with [REDACTED:ID] in the request and in the response. An earlier gateway does not remove the term, so keep action: block on it.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
blocked_terms | list of strings | Built-in list | Terms to match. Without the key, the built-in list applies. An empty list matches nothing. |
action | block or redact | block | Verdict on a match |
fuzzy_matching | boolean | false | Also matches words within max_distance edits of a term |
max_distance | integer | 1 | Maximum Levenshtein distance, from 0 to 8 |
Built-in reference terms
Used when blocked_terms is missing:
weapon, explosive, bioweapon, nerve agent, dual-use, dual use, stanag, nato restricted, nato secret, nato confidential, nato unclassified, precursor chemical, cyber surveillance, intrusion software, nuclear material, centrifuge, uranium enrichment, plutonium, heavy water, maraging steel, carbon fibre, carbon fiber, frequency hopping, spread spectrum, quantum cryptography, steganography, scrambler, biological agent, toxin, pathogen, stanag 4586, stanag 4607, stanag 5516, stanag 6001
How it works
- The gateway joins the request messages into one lowercase string.
- Each term must match a whole word or phrase:
weaponmatchesWeaponandweapon.but notweaponry. - If nothing matched and
fuzzy_matchingis on, the control compares each term with word windows of the same length, withinmax_distance. - No match:
allowwithdual_use.clean. - A match:
blockorredact, followingaction, withdual_use.triggered.
The details contain action, matched (the first matching term), fuzzy_matching, and max_distance.
Phase and verdicts
| Item | Value |
|---|---|
| Phase | input, plus an output check on streamed and buffered responses. With action: redact and gateway 0.1.26 or later, also output redaction. |
| Request families | Chat Completions, Completions, Responses, Messages |
| Verdicts | allow, redact, block |
| Reason codes | dual_use.clean, dual_use.triggered |
Examples
The three export control filters together:
pack:
name: export-control
version: 1.0.0
enabled: true
policies:
chain:
- dual-use-filter
- entity-list-filter
- itar-ear-filter
policy:
dual-use-filter:
blocked_terms:
- uranium hexafluoride
- thermal imaging
- night vision
action: block
entity-list-filter:
blocked_entities:
- replace-with-denied-party-name
itar-ear-filter: {}The built-in list with the default block action:
policy:
dual-use-filter: {}Limits and failure behavior
- A block returns HTTP
400with error codecontent_policy_violation, and the provider never gets the request. - With
action: redactand gateway 0.1.26 or later, the gateway masks each matched term in the request before the provider gets it. The match uses whole words and ignores case. Withfuzzy_matching: true, the gateway also masks the words withinmax_distanceedits of a term. The marker follows theredactionsettings ofpii-detector. The same pass also replaces the PII that the shared redactor finds, such as email addresses. - With
action: block, the gateway also checks the response on Chat Completions, Responses, and Messages. This output block check uses exact matching only, and withoutblocked_termsit checks onlyweapon,explosive,bioweapon, andnerve agent. - With
action: block, an output match blocks the response. A buffered block returns HTTP400. A streamed block ends the stream with an error event. - With
action: blockandstream: true, the gateway sends the text while it arrives. With gateway 0.1.26 or later, it holds back the text that can be the start of a term. Thus no part of a matched term gets to the client. See Output blocks in a stream. - With
action: redactand gateway 0.1.26 or later, the gateway redacts the terms in streamed and buffered Chat Completions, Responses, and Messages output. The decision has the verdictredact, the reason coderedact.applied, and an output result fromdual-use-filterwithdual_use.triggered. When the chain has another redacting policy, the output result can name that policy. See Output redaction reason codes. Withoutblocked_terms, output redaction uses the full built-in list. - Fuzzy matching also hits unrelated words: with
max_distance: 2, the built-intoxinmatchestoken. Start with1, and test. - Literal matching does not understand context, negation, or intent.
Next steps
- Entity List Filter: denied party names.
- ITAR/EAR Filter: export control terms with a different built-in list.
- Investigate a Blocked Request: find the matched term in the decision event.