The first person to create a FolderPress site filled it with links to low-quality domains. The goal: leech SEO authority from folderpress.com. Every outbound link from a *.folderpress.com subdomain passes some of the domain’s search reputation to whatever it links to. One spammer’s links would drag down every legitimate site sharing the domain.
Predictable, in hindsight. Any multi-tenant platform that renders user content will attract this. It moved security work up the priority list.
The spammer exposed two distinct issues that are easy to conflate.
The first is security: reverse tabnapping. A link with target="_blank" lets the linked page access window.opener and redirect the original tab — to a phishing page that looks like your login screen. Fix: rel="noopener", severing the connection.
The second is SEO: link authority transfer. On a shared domain, every outbound link from every tenant passes search reputation to the target. One spammer dilutes everyone. Fix: rel="nofollow", telling search engines not to transfer authority. Google’s spam prevention guide recommends this for any platform with user-generated content.
One attribute for security, one for SEO. Different problems, clean separation. All external links now get target="_blank" rel="noopener nofollow".
FolderPress takes markdown, renders it as HTML, and serves it to readers. That’s a trust boundary.
The rendering pipeline has a specific order: sanitize user HTML first, then let trusted plugins (Shiki for code highlighting, KaTeX for math) add their output. Plugin output is never sanitized — it comes from code I control.
The alternative — sanitize at the end — would require a whitelist broad enough to preserve every <span style="color:..."> from Shiki and every KaTeX math element. Broader whitelist, more attack surface. Sanitize-first keeps the allowlist tight: strip anything dangerous from user input, then let plugins write whatever they need.
Content Security Policy headers are set for all tenant sites. But honesty: Next.js hydration requires unsafe-inline and unsafe-eval, which limits what CSP can block on its own. The primary XSS defense is the HTML sanitizer (rehype-sanitize), not CSP. CSP is defense-in-depth — a second layer. It restricts frame sources to a trusted allowlist (YouTube, Twitter) and blocks framing of tenant pages entirely (frame-ancestors 'none').
FolderPress renders user content as HTML. That’s the product. It’s also a trust boundary. Your content stays yours — and the pipeline that renders it can’t become a vector for someone else’s exploit.