Web security: XSS, headers and HTTPS

Play this lesson

A nickname that runs code

Hyper Shards' friends board shows every pilot's nickname. One day a player picks a very strange one: <img src=x onerror=...>. On every screen that shows the board, that "name" stops being a name and starts running as code.

Anything a player types is input: nicknames, chat messages, bug reports, even scores. The Stopping cheaters lesson taught the server's golden rule, never trust the client. Web pages have a twin rule: never trust input. Treat it as data to show or store, never as code to run.

Cross-site scripting (XSS)

Cross-site scripting, or XSS, is when an attacker gets their own JavaScript to run inside someone else's page. MDN, the web's main reference, says that code can do anything the site's own code can: read local storage and send requests as the player. When the bad name is saved in Firestore and shown to everyone, it is stored XSS, and it hits every visitor, every time.

The usual cause is one property. el.innerHTML = name reads the text as HTML. el.textContent = name shows it as plain letters, so the tag appears on screen as harmless text.

Bug hunt

friends-board.js
function drawRow(row, pilot) {  const name = document.createElement("td");  const score = document.createElement("td");  name.innerHTML = pilot.nickname;  score.textContent = pilot.score;  row.append(name, score);}

A pilot's nickname hides an image tag with code inside, and the friends board runs it. Which line is the bug?

  1. Line 2
  2. Line 4
  3. Line 5
  4. Line 6
Show the answer

Line 4

Line 4 hands the nickname to innerHTML, which reads it as HTML, so the onerror code runs. Using textContent, like line 5 does for the score, shows the name as plain text.

Injection: data that becomes a command

XSS is one kind of injection: input sneaking into code. A classic is SQL injection. SQL is the language many databases speak. If a server builds a command by gluing text together,

"SELECT * FROM players WHERE name = '" + name + "'"

then the name ' OR '1'='1 changes what the command means, and it returns every player. OWASP's fix is a parameterised query: write the command first with a blank, WHERE name = ?, and send the value separately. The database always knows which part is code and which is data.

Bolt says: Firestore works the same safe way. In where("nickname", "==", name), the name travels as a value, never as part of a command string.

Safe query

steam-scores.js
// Adb.query("SELECT * FROM scores WHERE name = '"  + name + "'");// Bdb.query("SELECT * FROM scores WHERE name = ?",  [name]);// Cdb.query(`SELECT * FROM scores WHERE name = '${name}'`);

A server for the Steam version keeps scores in an SQL database, and name comes from the player. Which version is safe from SQL injection?

  1. A
  2. B
  3. C
Show the answer

B

B writes the command with a blank and passes name separately, so the database never mixes data into code. A glues the name into the command, and C does the same with a template string, which is still string building.

What the padlock really means

HTTPS uses TLS (Transport Layer Security) to encrypt everything between the browser and the server. Nobody in between can read it or change it on the way, and the site has shown a valid certificate for the address you see. The Cloudflare lesson shows how we get ours.

It does not mean the site is honest. In 2023, Google's Chrome team wrote that nearly all phishing sites use HTTPS, and that in their research only 11% of people understood what the padlock meant. So from Chrome 117, the padlock became a neutral "tune" icon for site settings.

Quick check

A page at hypershards-free-skins.net loads over HTTPS. That proves it is safe to type your Hyper Shards login there.

Show the answer

False

HTTPS only means the connection is encrypted and the certificate matches that address. Phishing sites get certificates too: Google's Chrome team says nearly all of them use HTTPS.

Security headers

A header is a label sent with every response, and some tell the browser how to protect the page. On Cloudflare Pages we list them in a plain text file called _headers, with no extension, in the folder we publish. A path comes first, then indented headers:

/*
  X-Content-Type-Options: nosniff

Bolt says: Try a new CSP on a preview link first. A strict policy also blocks scripts we load from other sites, like an SDK from a CDN, until we list them.

Second lock

_headers
/*  Content-Security-Policy: script-src 'self'  X-Content-Type-Options: nosniff  Referrer-Policy: no-referrer

A nickname with an inline onerror script slips past a bug in our code. Which line of Hyper Shards' _headers file still stops it from running?

  1. Line 1
  2. Line 2
  3. Line 3
  4. Line 4
Show the answer

Line 2

A CSP with script-src blocks inline scripts, including attributes like onerror, unless we unlock them on purpose. Line 3 stops file-type guessing, and line 4 limits what the Referer header shares. The real fix is still textContent.

Update, verify and use the map

Bolt says: Never trust input, show it as text, lock the headers, keep packages patched. Next: what happens to the bill when thousands of players show up.

Fact-checked 9 October 2026