INOUTYOUR SERVERlib alib blib cAPI_KEY1n = form["id"]2doc = load(n)3q = "...id=" + n4page += comment5KEY = "a1b2c3"1n = int(form["id"])2if doc.owner != me: deny()3cur.execute(q, (n,))4page += escape(comment)5KEY = environ["API_KEY"]id=42id=4x2user 7400403SQL+nSQL ?nrow<b>&lt;b&gt;CSPSELECTDROP
Arches on the lanes: checkpoints (input check, access check, output escape)Front cards: the mistake (left) and its fix (right), line for lineMovers: requests, query tiles, values, error codes and keysA request comes in along the back lane, past an input check and an access check, to your code, which asks the database; the answer goes out along the front lane through an output escape to the browser and its CSP shield. Unsafe requests stop at a checkpoint and get 400 or 403 back. Query tiles and values travel to the database separately, so text can never become part of the command. In front, each common mistake sits beside its fix. The card lines are short sketches of each pattern, not a whole program: escape is html.escape, environ is os.environ, deny() stands for answering 403 Forbidden, and q holds the query with a ? placeholder.

The request

  1. A browser sends a request; your code reads the database and sends back a page. But anyone can send any request with any text in it, so treat every part of it as untrusted until your code has checked it.

Checkpoints on the way in

Safe output

Around the code

What happens

;; step 1/11 · untrusted
browser  →  your server  →  database
a request carries text chosen by whoever sent it:
  the address, form fields, cookies, headers
none of it can be trusted until it is checked
checkpoints below: input · access · query · output

The card lines are short sketches of each pattern, not a whole program: escape is html.escape, environ is os.environ, deny() stands for answering 403 Forbidden, and q holds the query with a ? placeholder. Rankings and names come from the OWASP Top 10, 2021 edition. In that edition cross-site scripting is part of A03 Injection. OWASP revises the list every few years; the fixes shown here stay the same.