Cross-site scripting via dart:html innerHtml / setInnerHtml from non-literal source

ID

dart.code.cross_site_scripting

Severity

high

Remediation Complexity

hard

Remediation Risk

high

Remediation Effort

medium

Resource

Code

Language

Dart

Description

dart:html innerHtml / outerHtml / setInnerHtml is assigned a non-literal value or a string-interpolated literal. If the value comes from user input or a network response, the browser executes attacker-controlled HTML and JavaScript in the page origin.

Rationale

This rule targets dart:html (the dart-to-JS / Flutter Web surface). Setting element.innerHtml = userValue parses the string as HTML and renders any <script>, <img onerror>, <iframe>, javascript: URL, or similar attack vector. The result is a classic stored / reflected / DOM XSS — depending on where the tainted value came from — with full access to the page origin’s DOM, cookies, sessionStorage, and any in-page JS state.

Common patterns this rule catches:

  • el.innerHtml = userBio — user-controlled profile field rendered without sanitization.

  • el.innerHtml = 'Welcome, $name' — interpolation in a literal string; name = '<img src=x onerror=fetch(…​)>' works.

  • el.setInnerHtml(html) without a validator: argument — the validator: named arg lets you supply a NodeValidatorBuilder that restricts allowed tags / attributes.

Note: pure-Flutter mobile apps don’t use dart:html directly — it’s a Web-target import. WebView XSS is covered by unrestricted_webview_load and javascript_injection_webview.

The following code illustrates a vulnerable pattern detected by this rule:

class XssSamples {
  void innerHtmlVar(Element el, String userHtml) {
    // VULNERABLE: Cross-site scripting via dart:html innerHtml / setInnerHtml from non-literal source
    el.innerHtml = userHtml;
  }
}

Remediation

Use Element.text = …​ for textual content (Dart escapes for you). For genuinely-HTML content, pass a strict NodeValidatorBuilder to setInnerHtml:

import 'dart:html';

// Vulnerable
el.innerHtml = userBio;
el.innerHtml = 'Welcome, $name';
el.setInnerHtml(html);

// Fixed: textual content only
el.text = userBio;

// Fixed: HTML content with strict allow-list validator
final v = NodeValidatorBuilder()
  ..allowTextElements()
  ..allowElement('a', attributes: ['href']);
el.setInnerHtml(html, validator: v);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved