Skip to main content

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​

policy-config.yaml
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/plain

Use 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.

tests/document.json
{
  "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 --json

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

FieldTypeDefaultDescription
enabledbooleantruefalse skips every check and allows with empty details
sanitize_codebooleanfalseReplaces the built-in code patterns with [redacted code pattern] in the recorded text
max_document_bytesinteger524288Largest decoded size of one document. A larger one blocks the request. Lint rejects 0.
allowed_mime_typeslist 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:

KeyUseDefault
nameName in the findingsinline-document
mime_type or content_typeType for the allowlist and for extractiontext/plain
textInline text
content_base64 or dataBase64 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​

  1. The fenced code blocks of all request messages become the first text fragments.
  2. The control reads the document objects in the order of the keys above.
  3. A document larger than max_document_bytes blocks with document-analyzer.document_too_large:<name>.
  4. A type not in a non-empty allowed_mime_types blocks with document-analyzer.mime_type_not_allowed:<mime_type>.
  5. Text extraction depends on the type: a wordprocessingml.document type gives the text of word/document.xml without tags, a pdf type gives printable ASCII runs of at least four characters, and every other type is read as UTF-8.
  6. With sanitize_code, the six built-in code patterns are replaced.
  7. A finding records source, mime_type, extracted_chars, and code_findings (the number of fenced code blocks).
  8. Non-empty text is added to text_fragments.
  9. If nothing blocked, the request is allowed with document-analyzer.ok.

The details contain text_fragments, findings, and blocked_reason.

Phase and verdicts​

ItemValue
Stagespre-request (default), pre-response
Request familiesChat Completions, Completions, Responses, Messages
Verdictsallow, block
Reason codesdocument-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.

policy-config.yaml
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 400 with error code content_policy_violation. The first failing document stops the analysis, and the details contain only the findings before it.
  • documents, attachments, input_documents, and files are 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_code changes only the recorded text. To block on code patterns, add code-sanitizer.
  • text_fragments can 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​