Untrusted input & allow-list validation

Every input is untrusted

Lesson 1 of 20 · 10 min · Module 1: Untrusted input

Anything that reaches your code from outside, such as form fields, the address bar, stored data or answers from other servers, can be shaped by an attacker. Security starts with deciding exactly what you accept, and refusing everything else.

Start this lesson →

Free. No installation: you write and run the code in your browser, and it is checked automatically.

By the end, you’ll be able to…

  • Treat every outside value as untrusted
  • Validate with allow-lists instead of deny-lists
  • Check the type, the length and the format

The idea

An allow-list describes what is valid: a username of 3 to 20 lowercase letters, digits or underscores, an age from 13 to 120, a colour from a fixed list. A deny-list tries to name what is bad, and attackers simply use something you did not think of. Check the type first, then the length and the format, and treat anything else as an error.

const SIZES = ['S', 'M', 'L'];
function validSize(value) {
  return SIZES.includes(value);
}
console.log(validSize('M'), validSize('<script>'));

01Where untrusted input comes from

Form fields, the address bar, cookies, localStorage, uploaded files and answers from other servers can all be changed by someone. A select with three options limits honest visitors; an attacker sends any value directly, or edits the page with the browser’s developer tools.

02Allow, don’t deny

A deny-list of dangerous characters always misses something: another encoding, another tag, another trick. An allow-list states exactly what is acceptable, so anything unexpected fails automatically.

const username = /^[a-z0-9_]{3,20}$/;
console.log(username.test('ana_92'), username.test('ana<script>'));
Output
true false

03Check the type first

Values from JSON can be arrays, numbers or objects instead of strings. A regular expression quietly turns ['ana_92'] into the text ana_92 and lets it pass, so check typeof before anything else.

console.log(/^[a-z0-9_]+$/.test(['ana_92']), typeof ['ana_92']);
Output
true object

Your turn

Write validateProfile(data). It returns {} when everything is valid, or an object with a message for every invalid field: username must be a string of 3 to 20 lowercase letters, digits or underscores (Use 3 to 20 lowercase letters, digits or underscores.); age must be a whole number from 13 to 120, given as a number or as a string of digits (Enter a whole number from 13 to 120.); colour must be one of COLOURS (Choose one of the listed colours.). Values of any other type, such as arrays, are invalid.

What the checks look for

  • Valid data passes without errors
  • Usernames follow the allow-list
  • Age is a whole number from 13 to 120
  • Only the listed colours are accepted
  • Unexpected types are rejected
  • The form shows the messages as text
  • A valid profile is saved
Try it in the editor →

Take this with you

Decide exactly what you accept, check the type, and reject everything else.

Go a little further

Move focus to the first invalid field when the form is sent, and show each message as soon as its field loses focus.

Learn Web Security Foundations for free

20 short lessons with real code and instant checks.

See the whole course →