Course outline

Web Security / Basic: web trust and identity contracts / Lesson 6

Validation, parsing, and canonical forms

Validate data shape and meaning at explicit boundaries without confusing normalization with permission or safe interpretation.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate transport limits, parsing, schema, and business rules.
  • Define canonicalization and unknown-field behavior deliberately.

Validate each layer for its own purpose

A request can have valid bytes but invalid syntax, valid syntax but the wrong shape, or the right shape with an unacceptable business value. Apply bounded request size and parsing rules before allocating unnecessary resources, then validate the expected fields and domain meaning. A client form check is feedback, not authority. The server must validate all relevant entry points, including imports and background messages. Validation does not replace authorization, parameter binding, or output encoding. Keep the response useful without echoing private raw input or turning a detailed parser error into unnecessary implementation disclosure.

Use a precise local schema contract

The complete Python example accepts only the declared keys, a nonblank title within an illustrative limit, and an integer count in range. It deliberately distinguishes bool from int under Python’s type model. The numeric and length limits belong to the fictional product, not a universal security standard. They count Python string elements, not necessarily user-perceived grapheme clusters or encoded bytes. A real application should define its units, Unicode policy, and resource limits. The example reads supplied in-memory values only, so it does not enforce transport body limits or parse a real request.

python A complete benign input-shape check
def valid_export(value):
    return (isinstance(value, dict)
            and set(value) == {"title", "copies"}
            and isinstance(value["title"], str)
            and 1 <= len(value["title"].strip()) <= 80
            and type(value["copies"]) is int
            and 1 <= value["copies"] <= 5)

assert valid_export({"title": "Notes", "copies": 2})
assert not valid_export({"title": "Notes", "copies": True})
assert not valid_export({"title": " ", "copies": 2})

The assertions cover one valid object, an invalid boolean count, and a blank title. No API, file, account, or database is accessed. This narrow validator is not a parser for every web format or a complete authorization system.

Normalize only under a field-specific policy

Canonicalization can make comparisons consistent, but careless transformations can change meaning or create collisions. Define whether case, Unicode normalization, whitespace, separators, and encoding are significant for each field. A display name and an internal identifier need different policies. Do not lowercase arbitrary values merely because one username field is case-insensitive. Validate the intended normalized representation and preserve the original where the product and privacy rules require it. Multiple decoding or normalization stages across proxies and frameworks can create disagreement, so identify which component owns interpretation rather than applying repeated transformations everywhere.

Specify unknown fields and failure behavior

The synthetic record documents what the validator accepts and which layers remain outside it. An unknown field may need rejection, ignoring, or a versioned extension mechanism depending on the API contract; silent acceptance is not automatically forward compatibility. Avoid directly assigning an entire request object to a privileged domain object. Select fields and enforce server-owned attributes separately. Validation errors should leave protected state unchanged and avoid partially committing a batch unless the product explicitly defines that behavior. Versioned schema changes need compatibility review for all producers and consumers, not only a new frontend form.

json A local input-contract planning record
{
  "schema": "lab-note-export-v1",
  "allowedFields": ["title", "copies"],
  "unknownFields": "reject",
  "serverOwnedFields": ["organization", "createdBy", "permission"],
  "outsideThisExample": ["transport-size", "authentication", "authorization", "output-context"]
}

This JSON is documentation rather than a framework configuration. It grants no access and performs no parsing. The server-owned fields must come from trusted context even when a client supplies similarly named values.

Test boundaries and transformations with ordinary data

Use benign fixtures for empty, missing, wrong-type, duplicate, out-of-range, Unicode, and unknown-field cases as appropriate. Include valid international text so a defense does not unnecessarily reject legitimate users. Test after the actual parser and framework binding because local dictionary checks do not establish wire-format behavior. Keep malformed-input handling bounded and avoid catastrophic regex or unrestricted decompression work. Record exact units, schema version, and error contract. A well-formed value still needs the destination’s safe interpretation mechanism and the subject’s permission. The purpose is predictable data handling, not a claim that rejecting unusual characters makes every vulnerability disappear.

Common errors and fixes

A boolean is accepted as an integer count

Why it happens
The language’s type relationship differs from the domain rule.
How to fix it
Check the intended type and range explicitly.
Prevent it next time
Include language-specific boundary cases.

Normalization changes a legitimate identifier

Why it happens
A global transformation ignores field meaning.
How to fix it
Define and apply a field-specific canonicalization policy.
Prevent it next time
Test collisions and valid international values.

A request object overwrites a server-owned field

Why it happens
All input fields are assigned without an allowlist and policy.
How to fix it
Select allowed data and derive authority from trusted context.
Prevent it next time
Review binding and persistence boundaries together.

Check your understanding

1. A title passes the shape validator. Is it safe to concatenate into SQL or raw HTML?

Show worked solution

No. Business validity is different from interpretation safety. Use parameterized database APIs and the appropriate output handling, while enforcing authorization independently. A valid value can still require encoding or structured binding at its destination.

2. Why should a test include valid text outside English?

Show worked solution

An overly narrow character rule can break legitimate users without addressing the actual trust boundary. Define product units and normalization intentionally, then verify safe processing and output for the supported languages.

Key takeaways

  • Validation has parsing, shape, and business layers.
  • Normalization must preserve the intended field contract.
  • Validated data still needs permission and safe interpretation.

Official references