Web security: XSS, headers and HTTPS
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
1function drawRow(row, pilot) {2 const name = document.createElement("td");3 const score = document.createElement("td");4 name.innerHTML = pilot.nickname;5 score.textContent = pilot.score;6 row.append(name, score);7}
A pilot's nickname hides an image tag with code inside, and the friends board runs it. Which line is the bug?
- Line 2
- Line 4
- Line 5
- 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
1// A2db.query("SELECT * FROM scores WHERE name = '"3 + name + "'");4// B5db.query("SELECT * FROM scores WHERE name = ?",6 [name]);7// C8db.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?
- A
- B
- 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.
- True
- False
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:
- Content-Security-PolicyLists where scripts may come from. script-src 'self' allows only our own files and blocks inline code, like an onerror attribute. MDN calls it defence in depth: a second lock, never a replacement for textContent.
- frame-ancestors 'none'A CSP rule that stops other sites showing our page inside a frame. That blocks clickjacking: hiding our real buttons under a fake page to trick players into clicking them.
- X-Content-Type-Options: nosniffTells the browser to trust the file type we declare instead of guessing. A text file can't suddenly be run as a web page.
/* 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
1/*2 Content-Security-Policy: script-src 'self'3 X-Content-Type-Options: nosniff4 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?
- Line 1
- Line 2
- Line 3
- 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
- Dependabot alertsGitHub checks our packages against its Advisory Database of known weaknesses and alerts us when one is affected. Dependabot security updates can open a pull request that moves it to the first fixed version, and our tests run on it like any change.
- npm auditAsks the npm registry about known problems in our installed packages. npm audit fix updates what it can within the allowed versions. Avoid --force, which can jump to a new major version and break the game.
- Firebase App CheckChecks that requests to Firestore and callable Cloud Functions come from our real app: reCAPTCHA Enterprise on the web, Play Integrity on Android, App Attest on Apple devices. Firebase says it stops some abuse, not all, and it adds to logins and Security Rules, never replaces them.
- The OWASP Top 10The list of the ten most critical web app risks that professionals check against. The 2025 edition puts Broken Access Control first. Injection, which includes XSS and SQL injection, is A05.
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
- OWASP Top 10:2025 top10.owasp.org
- OWASP Top 10:2025, A05 Injection top10.owasp.org
- MDN: Element.innerHTML security considerations developer.mozilla.org
- MDN: cross-site scripting (XSS) developer.mozilla.org
- OWASP: XSS prevention cheat sheet cheatsheetseries.owasp.org
- OWASP: SQL injection prevention cheat sheet cheatsheetseries.owasp.org
- MDN: HTTPS developer.mozilla.org
- Chromium blog: an update on the lock icon blog.chromium.org
- Cloudflare Pages: headers developers.cloudflare.com
- MDN: Content Security Policy developer.mozilla.org
- MDN: X-Content-Type-Options developer.mozilla.org
- GitHub: about Dependabot alerts docs.github.com
- GitHub: about Dependabot security updates docs.github.com
- npm: npm audit docs.npmjs.com
- Firebase: App Check firebase.google.com