</> MAANG.io
system design · 301

Advanced

Master system design interviews with scalable architecture patterns, distributed systems, and real-world design challenges.

0/480 solved 0% complete

Section 10: Strangers' HTML on Your Domain - Security, Abuse & Content Safety

Executive Summary: The platform takes text from 2M monthly writers it has never met and serves it, under its own domain, to 25M readers a day. That makes the writer the most dangerous user in the system. Each threat below gets a mechanism, a rejected alternative and a named residual risk. The structured-document decision (D13), already visible as the doc field in Section 4's API, turns out to be the strongest security control in the design.

The interviewer turns the marker around. "So far your attackers have been traffic and failure. Now the attacker has an account. Maya is honest. The next writer isn't. What does he do with your editor?"

He writes a post, and the platform serves it from its own domain, cached at every edge for 24 hours.

Stored XSS: Never Accept HTML

You: "The editor never sends HTML. It sends the structured JSON document from decision D13, the ProseMirror-style tree we autosave. The server validates it against a schema of known nodes (paragraph, heading, list, code block, image, embed) and marks (link, bold, italic). There's no raw-HTML node, so there's nowhere to put a <script>. The renderer emits only tags it knows and escapes every text node."

Interviewer: "Why not accept HTML and run a good sanitizer? Most CMSs do."

You: "Sanitizer bypasses are a recurring CVE class. With an allow-list renderer I'm not choosing what to strip from an attacker's HTML; I'm generating HTML from a format that can't express anything dangerous. I don't see a trigger that flips this."

The format's edges still need rules:

  • Links: only http, https and mailto are accepted. javascript: and data: URLs fail validation. Every outbound link gets rel="nofollow ugc noopener".
  • Embeds: only allow-listed providers, resolved via oEmbed, rendered in a sandboxed iframe on a separate origin, with no access to our cookies or DOM.
  • CSP: script-src allows only our versioned bundles by hash, not by nonce, because a nonce cached for every reader is no nonce at all. Add object-src 'none' and base-uri 'none', and even a renderer bug doesn't execute.
  • Cookies and origins: the editor, sign-in and /v1 APIs live on a separate origin (app. / api.). The session cookie is HttpOnly; Secure; SameSite=Lax and host-only there, so script that somehow ran on an article page could neither read the session nor make credentialed same-origin calls. Personal state comes from /me, a credentialed CORS call to the API origin, so the cached HTML holds no secrets.

Stored XSS defence: the editor's JSON doc is schema-validated (unknown nodes or bad URLs rejected), rendered by an allow-list renderer into article HTML under a hash-based CSP and served by the CDN, with embeds in a sandboxed iframe on a separate origin

Key Insight: The protection comes from what the format can't express. A sanitizer has to beat every new trick. An allow-list renderer only has to get its handful of node types right, and CSP backs it up.

Quick check before moving on:

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.