KHAGAN AYDIN

Improper Input Validation: What It Is and How to Fix It

Most famous vulnerabilities are one idea wearing different clothes: the application took something a user sent and trusted it to be what it claimed.

· 3 min read

SQL injection, cross-site scripting, command injection, an oversized file that crashes the server. Underneath each one is the same missing step. The input was never checked against what the code was actually allowed to accept. That missing step has a name, improper input validation, and it sits at the root of a large share of the serious bugs I have found in twenty years of testing.

What counts as input

More than the obvious form field. The URL and its parameters, the headers, the cookies, an uploaded file’s name and size and contents, the JSON body, a value handed to you by another service. Anything that enters your code from outside is input, and “outside” includes systems you own. If this function did not generate the value itself, treat it as a stranger’s.

Two ways to check, and one of them loses

Blocklist, deny what looks bad. List the dangerous characters and patterns, reject anything that matches. This loses, every time, eventually. You are trying to write down every bad input in the world, and the attacker only needs the one you forgot. A new encoding, a new keyword, and the same attack gets through.

Allowlist, accept only what is known good. Decide what valid looks like and refuse everything else. A postal code is five digits. A username is 3 to 20 letters, numbers and underscores. An avatar is a JPEG or PNG under 2 MB. Everything outside that is turned away before any other code touches it. This is the approach that holds, because you are describing the small, finite set of good instead of the infinite set of bad.

The code is short and boring, which is the point:

import re

USERNAME = re.compile(r"^[A-Za-z0-9_]{3,20}$")

def validate_username(value):
    if not isinstance(value, str) or not USERNAME.match(value):
        raise ValueError("Invalid username")
    return value

The two rules people get wrong

Validate on the server, always. A check in the browser is a courtesy to honest users. It is not a security control, because anyone can skip the browser and call your API directly. If your only validation lives in JavaScript, you have no validation.

Validation is not the whole job. Validating input decides whether to accept it. Encoding output decides whether it is safe where it lands, as HTML, as part of a SQL query, as a shell command. You need both: validate when it comes in, encode when it goes out. The same root, text that gets treated as instructions, is what drives HTTP Parameter Pollution and, in a newer form, prompt injection.

And size is input too. A field with no length limit, a file with no size cap, a JSON array with no bound: each is an input you failed to describe, and each is a denial of service waiting for a bored attacker.

The question for your own code

None of this is advanced. Input validation is the oldest advice in application security, which is exactly why it is worth repeating: the bugs that cost the most are almost never clever. They are a field that accepted what it should have refused. The discipline is simple to state and easy to skip under a deadline.

So the question is not “did we block the bad input.” It is “did we define what good input looks like, and refuse everything else, on the server, before anything else ran.” If you cannot point to where that happens for a given field, that field is the hole.