Skip to main content

UPL Filter

Use upl-filter to examine specified phrases in supported model output. The policy works only with non-streaming output. It can block a response or add constant text.

UPL means unauthorized practice of law. This policy controls text. It does not make a legal determination. It does not identify jurisdictions, attorney status, client relationships, correct legal content, or disclaimer adequacy.

Outcome

ConditionVerdictCause codeBody behavior
Blocked phrase, default behaviorblockupl.blockedThe gateway replaces the provider body with an HTTP 400 policy-violation response.
Blocked phrase and rewrite_to_educational: trueallowupl.rewritten_to_educationalConstant educational text is prepended.
Broad legal-advice cue and necessary disclaimerallowupl.disclaimer_injectedThe policy prepends This is not legal advice.
No applicable mutationallowupl.cleanBody is unchanged.

The policy details contain these values:

  • A blocked result contains { "blocked": true }.
  • An educational rewrite contains { "rewritten": true, "original_would_block": true }.
  • A disclaimer or clean result contains require_disclaimer, looks_like_legal_advice, and disclaimer_injected.

The details do not identify which blocked phrase matched.

Prerequisites

  • Add upl-filter to the effective output chain.
  • Use a non-streaming OpenAI-compatible JSON response path.
  • Identify if the policy must block or add an educational prefix.
  • Get approval for the phrase set and disclaimer from legal and compliance owners.

The built-in strings are not legal advice for a specified jurisdiction.

Configuration

pack:
name: upl-filter-example-1
version: "1.0.0"
enabled: true

policies:
chain:
- upl-filter

policy:
upl-filter:
blocked_patterns:
- you should sue
- file this motion
- sign here
require_disclaimer: true
rewrite_to_educational: false

Supported fields

FieldTypeDefaultNotes
blocked_patternsstring[] (items non-empty)field not specifiedIf you do not specify this field, the runtime uses you should sue, file this motion, and sign here. A specified array replaces these defaults.
require_disclaimerbooleantruePrepends This is not legal advice. for content that looks like legal advice.
rewrite_to_educationalbooleanfalseIf a blocked pattern matches, the policy adds an educational notice. It does not block the response.

blocked_patterns: [] is correct and gives the runtime an empty blocked-phrase list. It does not restore the built-in phrases. The policy continues to examine the broad disclaimer cues. Do not specify the field to use the built-in phrases.

Specified evaluation sequence

1. Blocked phrases

The policy checks each blocked pattern as a case-insensitive substring. It does not use a regex or word boundary. A custom array replaces the defaults. If an item matches, the policy does one of these actions:

  • By default, the policy returns upl.blocked.

  • With rewrite_to_educational: true, the policy returns allow and prepends this specified text:

    Note: The following is for educational purposes only and does not constitute legal advice. Please consult a qualified attorney for advice specific to your situation.

The prefix does not replace the source response. The evaluator does not change the response to an educational summary.

2. Broad disclaimer cues

If no blocked phrase matches, the policy checks for these legal-advice cues:

  • The output contains you should.
  • The output contains file and court.
  • The output contains legal advice.
  • The output contains retain counsel.

When require_disclaimer: true, the policy prepends:

This is not legal advice.

The policy does not add the text if the lowercase response starts with that specified sentence. The same sentence in a subsequent position does not prevent a new prefix.

rewrite_to_educational changes only blocked-phrase matches. It does not change the broad disclaimer path.

Supported response shapes

The evaluator gets text from these fields:

  • Chat Completions choices[*].message.content.
  • A Responses top-level output string.
  • Responses output[*].content[*].text items that have the output_text type.

The prefix operation changes these fields:

  • Each string in choices[*].message.content.
  • Each string in Responses output[*].content[*].text that has the output_text type.

A match in one segment sets the result for the full response. For a prefix result, the helper adds the prefix to each supported segment. It does not change only the segment that contains the cue.

The active prefix helper can examine a top-level output string but cannot change it. Use the standard Responses output array when the policy must add text.

The evaluator cannot get text from unsupported or invalid JSON. Thus, it cannot apply the UPL control to this JSON.

Streaming boundary

Do not use upl-filter for stream: true SSE traffic. The active streaming policy loop does not run the UPL evaluator. Thus, it does not block streamed output or add a prefix. Use stream: false for this control. Test the client setting after each related change.

Safer configuration patterns

Keep the built-in hard blocks

policy:
upl-filter:
require_disclaimer: true

Extend the effective phrase set

Custom patterns replace the defaults. Thus, include the necessary built-in patterns in the list:

policy:
upl-filter:
blocked_patterns:
- you should sue
- file this motion
- sign here
- execute this affidavit
require_disclaimer: true
rewrite_to_educational: false

Prefix, not block

policy:
upl-filter:
blocked_patterns:
- you should sue
- file this motion
- sign here
require_disclaimer: true
rewrite_to_educational: true

This configuration delivers the source text after the educational prefix. Use it only when an authorized person approves this behavior.

Validate and verify

verdictan policy test does not run this gateway output handler. Use a controlled upstream. Send non-streaming integration requests:

verdictan policy lint --file policy-config.yaml
verdictan gateway run \
--listen 127.0.0.1:41002 \
--agent upl-verification \
--policy-config policy-config.yaml

Send stream: false requests that cause each specified test result:

Upstream textSpecified result with default config
You should sue immediately.HTTP 400, upl.blocked
You should retain counsel for this matter.200, disclaimer prefix, upl.disclaimer_injected
Courts have different jurisdictions.200, unchanged, upl.clean
This is not legal advice. You should retain counsel.200, no duplicate prefix, upl.clean

Run the blocked case again with rewrite_to_educational: true. Verify HTTP 200, the educational prefix, and upl.rewritten_to_educational.

Correlate each request ID with:

verdictan events tail --since 10m --json

Verify the nested policy cause. Verify the body that the caller receives. A policy result does not prove that the prefix helper supports the returned JSON shape.

Rollout guidance

  1. Get positive and negative examples from legal owners for the specified jurisdictions and use cases.
  2. Start with non-streaming traffic on a limited route.
  3. Test false positives from blocked text in quotations, historical text, and educational text.
  4. Make sure that custom patterns keep each built-in phrase that is necessary.
  5. Measure block, disclaimer, and educational rewrite volumes for each configuration version.
  6. Review the list again when the product scope, jurisdictions, or legal workflows change.

Troubleshooting

SymptomLikely causeResolution
Built-in phrase does not cause a blockblocked_patterns replaced the defaults. The value can be [].Remove the field to use the defaults. Alternatively, include each necessary phrase.
Text matched unexpectedlyMatching is case-insensitive substring matching.Narrow the phrase and add negative fixtures for quotations and educational content.
The policy adds a duplicate disclaimerThe response does not start with the specified built-in sentence.Use the approved prefix in the same form. Alternatively, remove the subsequent copy from the upstream response.
Event shows an injected disclaimer, but top-level output is unchangedThe evaluator can examine this shape, but the prefix helper cannot change it.Use a supported output array shape. Verify the body.
Streamed response is unchangedThe active SSE policy loop does not run the UPL evaluation.Use stream: false.
Educational mode delivers dangerous contentThe policy adds constant text and does not change the source response.Use the default block when the gateway must not deliver the source text.
Team uses the policy as legal compliance certificationPhrase checks and disclaimers cannot show legal adequacy.Complete a legal review. Apply the necessary controls external to this policy.

Next steps