Input Controls
ITAR/EAR Filter
itar-ear-filter blocks requests and responses that contain export-control terms, such as missile guidance. It checks the request before dispatch and the answer before the client gets a matched term.
A term match does not classify anything for export or give it a legal status. Configure your own list, so that the request and response checks use the same terms.
Configuration
Save this as policy-config.yaml:
policy-config.yaml
pack:
name: itar-ear-filter
version: "1.0.0"
enabled: true
policies:
chain:
- itar-ear-filter
policy:
itar-ear-filter:
blocked_terms:
- "missile guidance"
- "night vision"
- "export controlled"
fuzzy_matching: falseVerify an exact match
Add these tests and run verdictan policy test --json:
Add to policy-config.yaml
testing:
suites:
- name: itar-ear-filter
cases:
- name: blocks-missile-guidance
input: {messages: [{role: user, content: "Explain missile guidance"}]}
expected: {verdict: block, reason_code: export_controls.triggered}
- name: allows-hello
input: {messages: [{role: user, content: "Hello"}]}
expected: {verdict: allow, reason_code: ok}Both pass. For the response check, apply the file to a test agent with the config-first workflow and use a controlled provider response that contains missile guidance. The client receives a block.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
blocked_terms | string[] | Built-in list | Terms to match. Without the field, the request check uses the built-in list, which starts with itar, ear, usml, eccn, and missile guidance. |
action | block | block | The only value. A match always blocks. |
fuzzy_matching | boolean | false | Levenshtein matching in the request check. The response check never uses it. |
max_distance | integer | 1 | Maximum edit distance for fuzzy matching |
How it works
- The gateway joins the request messages into one lowercase string.
- Each term must match at word boundaries.
- If none matched and
fuzzy_matchingis on, each term is compared with word windows of the same length, withinmax_distance. - A match blocks with
export_controls.triggered. Otherwise, the request is allowed withexport_controls.clean. - When the provider answers, the gateway matches the same
blocked_termsat word boundaries in the answer text. It does this on streamed and buffered Chat Completions, Responses, and Messages responses. - A match in the answer blocks it with
export_controls.triggered. A buffered block returns HTTP400. A streamed block ends the stream with an error event. - The details record the matched term,
fuzzy_matching, andmax_distance.
Phase and verdicts
| Item | Value |
|---|---|
| Input phase | The joined request messages |
| Output phase | The streamed or buffered response text |
| Verdicts | allow, block |
| Reason code without a match | export_controls.clean |
| Reason code for a match | export_controls.triggered |
Examples
Strict built-in defaults
policy-config.yaml
pack:
name: defense-defaults
version: "1.0.0"
enabled: true
policies:
chain:
- itar-ear-filter
policy:
itar-ear-filter: {}Replace the term list
Your list replaces the built-in terms, so include every term that must block.
policy-config.yaml
pack:
name: defense-program-terms
version: "1.0.0"
enabled: true
policies:
chain:
- itar-ear-filter
policy:
itar-ear-filter:
blocked_terms:
- "radar cross section"
- "thermal imaging"
- "satellite technology"
fuzzy_matching: falseLimits and failure behavior
- Every match blocks. There is no
redactorwarn. - The built-in list has export control marker terms, not every USML or CCL category. Short terms such as
earalso match ordinary words. - The two checks use different built-in lists. Without
blocked_terms, the response check matches onlyitar,ear,usml, andeccn. Setblocked_termsto check answers against your full list. - An empty
blocked_termslist matches nothing in the request check. Leave the field out to use the built-in list. - The response check matches exact terms only.
- A streamed response is not buffered. 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. Text before the term can already be with the client. See Output blocks in a stream.
- When the control blocks a response, the request records the usage that the provider reports.
- Each step up in
max_distanceadds false matches. Use fuzzy matching only when you must catch misspellings. - This is a technical guardrail. Complete the export control review in its own workflow.
Next steps
- Dual-Use Filter: dual-use technology terms.
- Entity List Filter: restricted party names.
- Data Routing Policy: which providers may receive controlled data.