Validate User Input

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 174 of 200

Everything a User Sends Is Data Until Proven Otherwise

Every application that passes user text to a model has to decide what it will accept. The text may be empty, enormous, full of invisible control characters, written in unusual Unicode forms, or deliberately crafted to confuse the application. Validation is the step that decides, before any request is made, which input is acceptable and what should be done to it. It is the first layer of defense, and it protects the rest of the system from costs, crashes, and malformed data.

Validation is not the defense against prompt injection, which is a different problem handled in Lesson 175. A message can pass every check in this lesson and still contain instructions the model should not follow. What validation does is make the input well formed, bounded, and predictable, so that the layers after it have a reasonable job to do.


What to Check

Input checks fall into a few groups, and each one prevents a different kind of problem:

CheckProblem it prevents
PresenceRequests with nothing to answer, which waste a call
LengthHuge inputs that inflate cost, exceed limits, or slow the service
Character contentControl characters that corrupt logs, break parsing, or hide text
NormalizationVisually identical text that differs in bytes, which defeats matching and filters
StructureInputs that do not match the shape the application expects, when the input is structured

The order matters. Normalization should come before length and content checks, because some Unicode forms change length when they are normalized, and a length check on the raw text can give the wrong answer.


Normalizing Text

Unicode allows the same visible text to be written in several ways. Full-width letters, ligatures, and characters with combining marks can look identical to ordinary letters while being different sequences of bytes. A filter that compares against plain text can be bypassed by a full-width spelling, and a search can miss a match. Normalization converts text to a single standard form. The NFKC form is a common choice, because it also folds compatibility characters, such as full-width letters, into their plain equivalents.

Normalization does not make text safe. It makes the text consistent, which is a prerequisite for every other check.


Control Characters

Control characters are non-printing codes, such as the null character or the escape character used for terminal colors. They can break logs, corrupt files, confuse terminals, and hide text from a reader. Line breaks and tabs are ordinary text and should be kept. The rest should be removed, as the function below does.


A Validation Function

The function below combines the checks in the right order. It normalizes first, removes control characters, trims the result, and then checks presence and length. It returns the cleaned text along with a list of problems, so the caller can show every issue at once:

"""clean_input.py: normalize, clean, and check a user message before it reaches the model."""

import re
import unicodedata

CONTROL = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")


def clean_user_message(raw: str, max_chars: int = 2000) -> tuple[str, list[str]]:
    problems: list[str] = []
    text = unicodedata.normalize("NFKC", raw)
    text = CONTROL.sub("", text).strip()
    if not text:
        problems.append("message is empty")
    if len(text) > max_chars:
        problems.append(f"message is {len(text)} characters; the limit is {max_chars}")
    return text, problems


if __name__ == "__main__":
    cases = [
        "  Hello, how do I reset my password?  ",
        "",
        "a\x00b",
        "x" * 2500,
        "HELLO",
    ]
    for number, raw in enumerate(cases, start=1):
        text, problems = clean_user_message(raw)
        print(f"case {number}: {len(text)} characters, problems={problems}")

Output:

case 1: 34 characters, problems=[]
case 2: 0 characters, problems=['message is empty']
case 3: 2 characters, problems=[]
case 4: 2500 characters, problems=['message is 2500 characters; the limit is 2000']
case 5: 5 characters, problems=[]

How It Works

  1. Normalization runs first. The full-width letters in case five become ordinary letters, so the result is five characters, and the text is in the form every later check sees.
  2. Control characters are removed. The null character in case three disappears, leaving two characters, while ordinary letters around it are kept.
  3. Trimming removes surrounding spaces, so case one is measured as its visible content, thirty-four characters, not with its padding.
  4. Presence is checked after cleaning. An input that is only control characters or spaces becomes empty and is reported as such, which is the case that would otherwise pass as a message.
  5. Length is checked last, on the cleaned text, so the limit applies to what the model would actually receive. The problem names both the measured length and the limit, so the user can shorten the message with a clear target.
  6. Problems are returned, not raised, so a form can show every issue together and keep the text the user entered.

Structured Input

When input is structured, such as a form or a JSON payload, the shape is checked too. A schema, such as a Pydantic model (Lesson 104), states which fields are required, what types they have, and what range each value may take. Structured validation rejects a payload with a missing field or a wrong type before the application uses it, and the error names the field.

Structure and content are separate checks. A payload can be well formed and still contain an unacceptable message, so a structured input usually needs both the schema and the text checks above.


Displaying Input Safely

Validated text is still text, and when it is displayed in a web page it must be escaped for the context where it appears. Text placed into HTML can contain markup that changes the page, and that is a separate vulnerability from anything the model does. The rule is to escape output for the place it is shown, not to rely on the input checks to make it safe.


Summary

Input validation checks presence, length, character content, normalization, and structure, in an order that makes each check reliable. Normalization comes first, control characters are removed, and the cleaned text is what is measured and sent. Validation bounds cost and prevents malformed data, but it does not stop prompt injection, which needs the layers in the next lesson. Structured input needs schema validation in addition to the text checks, and output needs escaping for the place it is displayed.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Validate User Input and get answers drawn from it.

Signed-in readers only.