Skip to content

Validation

checking input against the rules before trusting it

What it is

Validation is the step in which a program compares incoming data with the rules that data is meant to follow and refuses it when it falls short, before anything is stored, executed or sent on. It goes by several names depending on the layer: input validation for form fields and API requests, schema validation when the rules are written down as a JSON Schema or an OpenAPI description, and markup validation when a web page is checked against the grammar of HTML. A piece of input that passes is called valid.

OWASP’s guidance splits the work into two questions. Syntax asks whether a value has the expected type and format: is this a date, is this an integer, does this body parse as JSON. Semantics asks “whether the value makes sense for the operation”, so a hotel booking needs two valid dates and also an end date after its start date. Converting the string “500” into an integer settles the first question and says nothing about whether 500 nights is an acceptable stay.

Markup validation is the oldest public form of the idea on the web. The W3C Markup Validation Service describes validation as checking a document against the grammar it claims to use, and compares its validator to lint for C. Its documentation is careful about what that proves: a valid page is not necessarily a good one, only one that a tool found compliant with a specific set of rules. The same caution applies to every other kind. Validation tells you the data fits the rules you wrote; it does not tell you the rules were the right ones.

Two neighbouring ideas are often confused with it. Sanitisation changes input to make it acceptable, by stripping or rewriting parts of it, where validation accepts or rejects it as it stands. Encoding and parameterisation make data safe for the place it is going, an HTML page or an SQL query, and they are still needed after validation has passed.

Why it matters

The decision behind validation is where a system draws its trust boundary and what it checks there. Every request from a browser, every message from a queue, every file from a partner and every configuration change from a colleague arrives with assumptions attached. Checking those assumptions at the edge means the rest of the code can rely on them; skipping the check means every function downstream has to defend itself, and in practice most of them will not.

The second decision is how the rules are written. Checks written out by hand, endpoint by endpoint, start out cheap and drift apart as the system grows, because each one is its own small piece of code. Rules declared once, as types or as a schema, can be enforced by a library and published as documentation from the same source, so the description and the behaviour cannot disagree. The price is a framework to learn and defaults to understand, because a schema enforces exactly what it says and nothing it leaves unsaid.

Strictness is the last choice. An allowlist describes what is acceptable and rejects everything else, which OWASP recommends over trying to recognise every malicious string. Rejecting fields a schema does not know about catches typos and stale clients early. Rejecting the whole request on the first problem, with a clear error, is safer than carrying on with data that was only partly checked.

What it looks like in practice

The company’s API began with request validation written by hand for each endpoint, and moving it to Huma v2 turned the checks into types across 649 paths and 760 operations. Each endpoint now has input and output structs, Huma generates the request validation from them, and an OpenAPI description falls out of the same types. Two WebSocket endpoints stay on raw Fiber, where the request and response model does not fit. A CI check compares the routes the server registers with the ones the description advertises, parity is 100%, and the bug class of a field somebody forgot to check went away with the hand-written code.

Configuration needs the same treatment, and SSH hardening on the company’s host now runs three stages of validation on every converge. The candidate file is syntax-checked before it is installed, the assembled configuration is checked after installation, and then the daemon’s own resolved settings are read back and 24 directives asserted against them. The third stage exists because an access report in September 2026 found two settings that the automation had been writing on every converge and the daemon had been overriding, because the operating system’s own defaults were read first and, for those two, the first occurrence wins. As the page puts it, grepping for a directive only proves it was written.

Markup validation is part of the routine too: HTML validation is one of the checks in a 22-linter commit gate with no warning tier, where every diagnostic is an error and inline suppressions are prohibited, so an invalid page cannot be committed by marking the error as acceptable.

Where it goes wrong

The server trusts what the browser already checked. A form that refuses a bad email address in the browser looks validated, but the request can be sent without the form at all. OWASP lists client-only checks as a common pitfall: validate on the server, and treat the browser check as a quick hint for the person typing.

A schema accepts everything it does not mention. In JSON Schema, listing a property neither makes it required nor forbids other fields, and under the default 2020-12 vocabulary the format keyword is collected as an annotation, with format checking off by default. This schema looks strict:

{
    "type": "object",
    "properties": {
        "email": { "type": "string", "format": "email" }
    }
}

It accepts {}, {"email": "nope"} and {"emial": "a@example.com"}. Add required and "additionalProperties": false, and turn on format assertion in the validator, or check the format in code. Huma, for its part, rejects unknown body fields by default, documents readOnly and writeOnly without enforcing them, and ignores unknown query parameters unless told otherwise.

The check passes on one version of the data and the program uses another. A validator that reads the raw request while the handler reads a decoded copy, or a test that inspects the file on disk while the daemon applies a different setting, has checked something the system never uses. OWASP’s advice reads: “Validate the representation that will actually be used”. The SSH work above is the same lesson in configuration: only the daemon’s read-back showed what was in force.

A denylist meets the next variant. Blocking known bad characters rejects legitimate input, an apostrophe in a surname for instance, and still leaves the query unsafe. Define what each field accepts, and reject the rest.

Valid input is treated as safe input. A string that passed every rule can still break an SQL statement or a page when it is pasted into one. Validation narrows what arrives; parameterised queries and output encoding protect the place where it ends up, and both are needed.

Further reading

Glossary