Input & Output Controls
Document Analyzer
document-analyzer checks documents that a request carries: it blocks a document that is too large or has a type you do not allow, and records the extracted text for inspection. The documents go to the provider unchanged.
It reads custom request fields such as documents, not the standard OpenAI or Anthropic upload fields. Use it with an application and provider that understand those fields.
Configuration
pack:
name: document-analyzer-example
version: 1.0.0
enabled: true
policies:
chain:
- document-analyzer
policy:
document-analyzer:
enabled: true
sanitize_code: false
max_document_bytes: 524288
allowed_mime_types:
- text/plainUse a document fixture
Put the whole request, with its documents, in input.request of a JSON test. The runner ignores a documents field placed directly in input.
{
"name": "accepts-plain-text",
"input": {
"messages": [{"role": "user", "content": "Inspect the attached note."}],
"request": {
"model": "test-model",
"messages": [{"role": "user", "content": "Inspect the attached note."}],
"documents": [{"name": "note.txt", "mime_type": "text/plain", "text": "A short public note."}]
}
},
"expected": {"verdict": "allow", "reason_code": "ok"}
}verdictan policy lint --file policy-config.yaml
verdictan policy test --jsonThe test passes, and the document-analyzer result has a finding for note.txt and the text A short public note. in text_fragments.
For a rejection test, copy the file and change mime_type to application/octet-stream. That case is blocked with document-analyzer.mime_type_not_allowed:application/octet-stream.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | false skips every check and allows with empty details |
sanitize_code | boolean | false | Replaces the built-in code patterns with [redacted code pattern] in the recorded text |
max_document_bytes | integer | 524288 | Largest decoded size of one document. A larger one blocks the request. Lint rejects 0. |
allowed_mime_types | list of strings | [] | Exact MIME types to accept. Case matters. An empty list accepts every type. |
Supported request shape
The control reads the arrays documents, attachments, input_documents, and files of the request. In each object:
| Key | Use | Default |
|---|---|---|
name | Name in the findings | inline-document |
mime_type or content_type | Type for the allowlist and for extraction | text/plain |
text | Inline text | |
content_base64 or data | Base64 content, used when text is missing |
An object without text, content_base64, or data, or with Base64 that does not decode, is skipped.
How it works
- The fenced code blocks of all request messages become the first text fragments.
- The control reads the document objects in the order of the keys above.
- A document larger than
max_document_bytesblocks withdocument-analyzer.document_too_large:<name>. - A type not in a non-empty
allowed_mime_typesblocks withdocument-analyzer.mime_type_not_allowed:<mime_type>. - Text extraction depends on the type: a
wordprocessingml.documenttype gives the text ofword/document.xmlwithout tags, apdftype gives printable ASCII runs of at least four characters, and every other type is read as UTF-8. - With
sanitize_code, the six built-in code patterns are replaced. - A finding records
source,mime_type,extracted_chars, andcode_findings(the number of fenced code blocks). - Non-empty text is added to
text_fragments. - If nothing blocked, the request is allowed with
document-analyzer.ok.
The details contain text_fragments, findings, and blocked_reason.
Phase and verdicts
| Item | Value |
|---|---|
| Stages | pre-request (default), pre-response |
| Request families | Chat Completions, Completions, Responses, Messages |
| Verdicts | allow, block |
| Reason codes | document-analyzer.ok, document-analyzer.document_too_large:<name>, document-analyzer.mime_type_not_allowed:<mime_type> |
Examples
Accept PDF, text, and two image types up to 1 MiB. Images are accepted but not read, because the control does no OCR.
pack:
name: upload-screening
version: 1.0.0
enabled: true
policies:
chain:
- audit-logger
- document-analyzer
policy:
document-analyzer:
max_document_bytes: 1048576
allowed_mime_types:
- application/pdf
- text/plain
- image/png
- image/jpeg
audit-logger: {}Limits and failure behavior
- A block returns HTTP
400with error codecontent_policy_violation. The first failing document stops the analysis, and the details contain only the findings before it. documents,attachments,input_documents, andfilesare not part of the OpenAI or Anthropic formats. The gateway forwards them unchanged.- Compressed PDF streams are not decoded, so text from such a PDF is incomplete.
- The control does not fetch URLs or read multipart uploads. For URLs, use
content-extractor. sanitize_codechanges only the recorded text. To block on code patterns, addcode-sanitizer.text_fragmentscan contain the full document text. Restrict access to decision events if documents are confidential.- Lint rejects unknown fields. A block with a wrong type that bypasses lint runs with
enabled: true,sanitize_code: true,max_document_bytes: 524288, and no MIME allowlist.
Next steps
- Content Extractor: documents linked by URL.
- Code Sanitizer: block the same code patterns in message text.
- Investigate a Blocked Request: read the
blocked_reasonof a blocked document.