How a community post becomes a page: the allow-list, the sanitizer, and why there are no images

Show & TellHysen Yan5 replies

Hysen YanStaff#1

Every post on this forum is stored exactly as you typed it: Markdown, with the line breaks. Nothing is converted when it is written. The transformation happens on the way out, and it happens in two passes.

Pass one: Markdown to HTML

The first pass is marked, a Markdown parser. It handles the ordinary things — paragraphs, lists, emphasis, blockquotes, links, code fences — and it is the reason a fenced block keeps its language. A block opened with three backticks and bash comes out as a <code> element with a class on it, and the highlighter on the page follows that class.

Pass two: the allow-list

The second pass decides what HTML is allowed to exist. sanitize-html keeps a shortlist of tags and drops everything else:

const SANITIZE = {
  allowedTags: ["p", "br", "strong", "em", "del", "code", "pre", "blockquote", "ul", "ol", "li", "a", "h3", "h4", "hr"],
  allowedAttributes: { a: ["href", "rel", "target"], code: ["class"] },
  allowedClasses: { code: [/^language-[a-z0-9+#-]{1,20}$/] },
  allowedSchemes: ["http", "https", "mailto"],
};

Raw HTML you type goes through the same filter, so a script, an iframe, a style or an event handler has no way through. It is not a blacklist of dangerous tags; it is a shortlist of the ones we are willing to render.

Three decisions you can see in that list

Headings are demoted. h1 and h2 in a post become h3; h5 and h6 become h4. The page already has a title and a subtitle, and a post that opened with its own h1 would fight them for the outline. Use ## for a section and ### for a subsection.

Images are not allowed. There are no uploads here, and a remote image is a tracking pixel the reader never agreed to see. ![alt](url) renders as a plain link to the same address, so the link survives and the beacon does not. If a screenshot is the fastest way to show an error, paste the text of the error instead: it is searchable, and the next person can copy it.

Links are rewritten. Every link comes out as rel="nofollow ugc noopener noreferrer" and opens in a new tab. A community that passes link equity is a community that gets spammed for it. This is the single biggest reason spam shows up on forums, and removing the incentive removes most of the spam.

What is not on the list

Tables. GitHub-flavoured Markdown tables are parsed into <table> markup, and the allow-list does not include it, so a table arrives as loose text with its pipes still in it. Use a list, a code block, or a link to the table somewhere else. There is no LaTeX and no footnote syntax either.

Why the Markdown is kept

Because the raw text is what the search index reads, what the excerpt in a notification is cut from, and what the "is this thread worth indexing" check measures. If we stored rendered HTML, the review model would be reading markup instead of prose, and every change to the renderer would be a change to the stored data.

If any of this gets in your way, say so here. /tools/markdown-cleaner strips a document down to plain Markdown before you paste it, and /tools/markdown-toc builds a table of contents for a long post.

5 replies

IvanMember#2

Tables are the one that will cost somebody an afternoon. I wrote a comparison as a Markdown table, pasted it, and got three lines of pipe-separated text back.

Is the table tag deliberately excluded, or is it a consequence of the allow-list being short? If it is only a shortlist, adding table, thead, tbody, tr, th and td looks like six entries and one regex for the alignment attributes.

Hysen YanAuthorStaff#3

In reply to Ivan (#2)

It is a shortlist, so it is one line to add, and we have not added it on purpose. Two reasons.

The first is that GitHub-flavoured tables have no colspan or rowspan, so a table that is fine on another site comes here as a grid of identical cells, and a table that is not fine is not fixed by allowing the tag.

The second is the review model: a table is the easiest way to smuggle in a layout that reads as an instruction, and we would rather ask people to write the comparison as a list, which is also how it reads on a phone.

If people keep hitting it, we will add the tag and test the model against it.

DaisyRegular#4

Two questions about the ## headings. Do they end up in the document outline of the page, and does the first line of the post become the meta description?

I have been putting a heading as the first line of my posts because it reads well, and if that is what becomes the description then it is a bad idea.

Hysen YanAuthorStaff#5

In reply to Daisy (#4)

The headings are in the outline, as h3 and h4. The meta description is the first 160 characters of the body as plain text, so yes, a heading as the first line becomes the description.

Put the sentence you want a reader to see in search results first, and the heading after it.

JecikaMember#6

The nofollow decision has a second effect worth naming: a link inside a post does not pass ranking, which means a good answer that quotes somebody else's blog does not help that blog.

For a community whose whole point is that the answers are findable, that is the right trade, and it is also why the site's own pages are the ones worth linking.

One thing I would like to know: is the URL still indexed by search engines, even if it passes no ranking? That is a different question from passing authority, and the first one still matters to somebody pasting a link into a search engine.