Skip to main content

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: false

Verify 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​

FieldTypeDefaultDescription
blocked_termsstring[]Built-in listTerms to match. Without the field, the request check uses the built-in list, which starts with itar, ear, usml, eccn, and missile guidance.
actionblockblockThe only value. A match always blocks.
fuzzy_matchingbooleanfalseLevenshtein matching in the request check. The response check never uses it.
max_distanceinteger1Maximum edit distance for fuzzy matching

How it works​

  1. The gateway joins the request messages into one lowercase string.
  2. Each term must match at word boundaries.
  3. If none matched and fuzzy_matching is on, each term is compared with word windows of the same length, within max_distance.
  4. A match blocks with export_controls.triggered. Otherwise, the request is allowed with export_controls.clean.
  5. When the provider answers, the gateway matches the same blocked_terms at word boundaries in the answer text. It does this on streamed and buffered Chat Completions, Responses, and Messages responses.
  6. A match in the answer blocks it with export_controls.triggered. A buffered block returns HTTP 400. A streamed block ends the stream with an error event.
  7. The details record the matched term, fuzzy_matching, and max_distance.

Phase and verdicts​

ItemValue
Input phaseThe joined request messages
Output phaseThe streamed or buffered response text
Verdictsallow, block
Reason code without a matchexport_controls.clean
Reason code for a matchexport_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: false

Limits and failure behavior​

  • Every match blocks. There is no redact or warn.
  • The built-in list has export control marker terms, not every USML or CCL category. Short terms such as ear also match ordinary words.
  • The two checks use different built-in lists. Without blocked_terms, the response check matches only itar, ear, usml, and eccn. Set blocked_terms to check answers against your full list.
  • An empty blocked_terms list 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_distance adds 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​