Skip to content

core-security-innerhtml-assignment

CategoryDefault severityLifecycleDefault confidence
securityBLOCKER (literal source: MAJOR)experimental0.9 dynamic / 0.7 literal (clamped to 0.6 while experimental)

What it catches

Three DOM sinks in vanilla TS / JS:

  • <expr>.innerHTML = <value>
  • <expr>.outerHTML = <value>
  • <expr>.insertAdjacentHTML(<where>, <value>)

The matcher classifies the right-hand side:

Value sourceSeverity
Variable, prop, function call, member access, template literal with ${…}BLOCKER
Plain string literal ('…', "…", untemplated )MAJOR

This is the framework-free counterpart to `vibe-xss-dangerously-set`. The detection logic and severity model are the same; the sink is different.

Why it matters

Direct DOM HTML injection is the single most-cited stored-XSS pattern in non-framework code. Even when the value source looks safe today, leaving an innerHTML assignment in the codebase invites the next maintainer to pipe a variable through it.

Example: failing code

container.innerHTML = post.html;                                          // BLOCKER
footer.outerHTML = `<small>${post.title}</small>`;                        // BLOCKER
el.insertAdjacentHTML('beforeend', userInput);                            // BLOCKER
el.innerHTML = '<hr/>';                                                   // MAJOR

Example: how to fix

Pick the structured replacement that matches the case:

// (a) Plain text — let the DOM escape for you
container.textContent = post.text;

// (b) Build DOM nodes structurally
const h1 = document.createElement('h1');
h1.textContent = post.title;
container.appendChild(h1);

// (c) Rich text from a trusted markdown pipeline — sanitise first
import DOMPurify from 'dompurify';
container.innerHTML = DOMPurify.sanitize(html);

Known limitations

  • We do not detect sanitisers (DOMPurify.sanitize(x)). A v1.1 AST pass will downgrade severity when a recognised sanitiser wraps the value.
  • Regex-only — escaped quotes and string concatenation across multiple lines may slip past.
  • Reading .innerHTML (right-hand side) is not flagged — only assignment.

Suppression

// codemore-ignore-next-line: core-security-innerhtml-assignment
el.innerHTML = TRUSTED_COMPILE_TIME_CONSTANT;

Or for an entire file:

/* codemore-ignore-file: core-security-innerhtml-assignment */

References

Next →
Back to the catalog
See the other 57 rules — grouped by pack, with lifecycle gates.