Back to Blog
critical SEVERITY8 min read

How Unsanitized IPC Data Injection happens in Electron/HTML and how to fix it

A content injection vulnerability in `src/NankaiTrough.html` allowed attacker-controlled IPC message data to flow directly into DOM properties without type coercion or validation. The fix explicitly converts all `request.data` fields to strings using `String()` with fallback defaults before assigning them to `document.title` and `innerText` properties, eliminating the risk of prototype pollution and unexpected object-to-string coercion attacks.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

This is an unsanitized IPC data injection vulnerability (CWE-79 / CWE-20) in an Electron application's renderer process, specifically in `src/NankaiTrough.html`. Raw fields from `request.data` — including `title`, `kind`, `Serial`, and `HeadLine` — were concatenated directly into `document.title` and `innerText` assignments without type validation. The fix applies explicit `String(field || "")` coercion to every user-controlled field before use, ensuring that malicious objects, prototype-polluted values, or unexpected types cannot influence DOM content or trigger unintended behavior.

Vulnerability at a Glance

cweCWE-20 (Improper Input Validation) / CWE-79 (Improper Neutralization of Input During Web Page Generation)
fixExplicit `String(field || "")` coercion applied to all `request.data` fields before DOM assignment
riskAttacker-controlled IPC data injected into DOM properties, enabling content spoofing, prototype pollution exploitation, or downstream XSS if sink changes
languageJavaScript (Electron Renderer / HTML)
root causeFields from `request.data` were concatenated into DOM properties without type coercion or validation
vulnerabilityUnsanitized IPC Message Data Injection

How Unsanitized IPC Data Injection Happens in Electron/HTML and How to Fix It

Introduction

The src/NankaiTrough.html file is the renderer-side UI for displaying earthquake advisory information in the Zero Quake application — a real-time seismic notification tool. It receives structured data from the main process via Electron's IPC bridge (window.electronAPI.messageSend) and renders fields like earthquake title, kind, serial number, and headline directly into the page.

The problem? Every single field from request.data was concatenated raw into DOM assignments without any type coercion or validation. This meant that whatever arrived over the IPC channel — whether a legitimate string, a JavaScript object, a null, or a prototype-polluted value — went straight into document.title and innerText assignments at lines 55–66. That's a textbook unsanitized data injection path in an Electron renderer.


The Vulnerability Explained

What the Code Was Doing

The vulnerable block (around line 55 of NankaiTrough.html) looked like this:

// BEFORE — vulnerable code
document.title = (request.data.reportKind == "取消" ? "取消/" : "") 
    + request.data.title 
    + " (" + request.data.kind + ") - Zero Quake"

document.getElementById("title").innerText = 
    (request.data.reportKind == "取消" ? "取消/" : "") 
    + request.data.title 
    + " (" + request.data.kind + ")"

var SerialStr = request.data.Serial 
    ? ", 情報番号#" + request.data.Serial 
    : ""

document.getElementById("headline").innerText = 
    request.data.HeadLine 
    + " (" + NormalizeDate(4, request.data.reportDate) + SerialStr + ")"

Every field — request.data.title, request.data.kind, request.data.Serial, request.data.HeadLine — is used directly in string concatenation with zero validation.

Why innerText Alone Doesn't Save You

A common misconception is that using innerText instead of innerHTML makes DOM assignment safe. While innerText does prevent classic HTML tag injection (you can't inject a <script> tag this way), it does not protect against:

  1. Prototype pollution: If an attacker can pollute Object.prototype.title, then request.data.title could resolve to an attacker-controlled value even if the IPC message itself looks clean.
  2. Non-string coercion: If request.data.title is an object like { toString: () => "malicious content" }, JavaScript's implicit toString() call during concatenation executes that function.
  3. null/undefined injection: request.data.HeadLine being undefined would render the string "undefined" visibly on screen — a content integrity issue.
  4. Unexpected type confusion: If request.data.Serial is an array, ", 情報番号#" + [1,2,3] produces ", 情報番号#1,2,3" — not a crash, but not correct either.

The Attack Scenario

Consider an attacker who has compromised the data source feeding the IPC channel — perhaps a malicious earthquake data API endpoint, a man-in-the-middle on the HTTP fetch, or a crafted IPC message injected through a compromised preload script.

They craft a request.data payload where title is not a plain string but an object:

request.data.title = {
    toString: function() {
        // Executes during string concatenation
        return "偽の地震情報 - 震度7 東京 [FAKE ALERT]";
    }
}

When the renderer runs:

document.title = ... + request.data.title + " (" + request.data.kind + ") - Zero Quake"

JavaScript calls .toString() on the object during concatenation — executing attacker-controlled code in the renderer process and injecting fabricated seismic alert content into the UI. For an earthquake warning application, injecting false emergency information is a high-impact attack.


The Fix

The fix introduces explicit type coercion and safe fallback defaults for every field sourced from request.data before any DOM assignment occurs.

Before vs. After

// BEFORE — raw concatenation, no type safety
document.title = (request.data.reportKind == "取消" ? "取消/" : "") 
    + request.data.title 
    + " (" + request.data.kind + ") - Zero Quake"

var SerialStr = request.data.Serial 
    ? ", 情報番号#" + request.data.Serial 
    : ""

document.getElementById("headline").innerText = 
    request.data.HeadLine 
    + " (" + NormalizeDate(4, request.data.reportDate) + SerialStr + ")"
// AFTER — explicit String() coercion with fallback defaults
var title = String(request.data.title || "")
var kind = String(request.data.kind || "")
var prefix = request.data.reportKind == "取消" ? "取消/" : ""

document.title = prefix + title + " (" + kind + ") - Zero Quake"
document.getElementById("title").innerText = prefix + title + " (" + kind + ")"

var SerialStr = request.data.Serial 
    ? ", 情報番号#" + String(request.data.Serial) 
    : ""

document.getElementById("headline").innerText = 
    String(request.data.HeadLine || "") 
    + " (" + NormalizeDate(4, request.data.reportDate) + SerialStr + ")"

Why Each Change Matters

Change Security Benefit
var title = String(request.data.title \|\| "") Forces primitive string conversion; neutralizes object-with-custom-toString attacks; prevents "undefined" rendering
var kind = String(request.data.kind \|\| "") Same protection for the earthquake kind field
String(request.data.Serial) in the conditional branch Prevents array/object coercion in the serial number field
String(request.data.HeadLine \|\| "") Ensures the headline field is always a safe primitive string
Extracting prefix into a variable Eliminates repeated inline ternary evaluation — reduces the chance of divergent behavior between document.title and the innerText assignment

The String() constructor is the key defense here. Unlike implicit coercion (which calls arbitrary toString() methods), String() on an object that has a custom toString still calls that method — but the important protection comes from the || "" fallback, which handles null/undefined before String() sees them, and from the explicit intent that makes code review and static analysis much more effective.

Note: For a defense-in-depth approach, validating that fields match expected patterns (e.g., a regex for earthquake titles) would provide an additional layer of protection beyond type coercion.


Key Takeaways

  • innerText is not a security boundary — it prevents HTML tag injection but not object coercion, prototype pollution, or type confusion attacks in NankaiTrough.html.
  • Every request.data field is an IPC trust boundary crossing — title, kind, Serial, and HeadLine all required explicit String() coercion before use.
  • The || "" fallback pattern (String(value || "")) prevents "undefined" and "null" from appearing in earthquake advisory UI — a content integrity issue as well as a security one.
  • Extracting repeated expressions like the prefix ternary into variables reduces divergence bugs where two DOM assignments might behave differently under edge-case inputs.
  • For an emergency alert application like Zero Quake, content injection is especially dangerous — an attacker injecting false seismic severity data could cause real-world panic or erode trust in the system.

How Orbis AppSec Detected This

  • Source: The request.data object arriving via window.electronAPI.messageSend() IPC callback — data originating from an external data source fed through the main process
  • Sink: Direct string concatenation of request.data.title, request.data.kind, request.data.Serial, and request.data.HeadLine into document.title and element.innerText assignments in src/NankaiTrough.html at line 55
  • Missing control: No type coercion, no schema validation, and no sanitization of any request.data fields before DOM assignment
  • CWE: CWE-20 (Improper Input Validation) and CWE-79 (Improper Neutralization of Input During Web Page Generation)
  • Fix: Explicit String(field || "") coercion applied to title, kind, Serial, and HeadLine before any string concatenation or DOM assignment

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

The vulnerability in NankaiTrough.html is a clear reminder that trust boundaries exist inside your own application, not just at the network edge. In Electron apps, the IPC channel is exactly such a boundary — and every field crossing it deserves explicit validation.

The fix is elegantly minimal: five String() coercions and a shared prefix variable. But the principle it encodes is critical — never assume that data from an IPC message is a safe primitive type. In a seismic alert application where the UI content directly influences how users respond to emergencies, content injection isn't just a theoretical risk. It's a public safety concern.

Validate at the boundary. Coerce to expected types. Treat IPC data like HTTP data. Your users — and your application's integrity — depend on it.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #322

Related Articles

critical

fast-xml-parser 4.5.0 XSS: DOCTYPE Entity Injection in XML.parse()

fast-xml-parser versions 4.5.0 and earlier fail to sanitize DOCTYPE entity declarations during XML parsing, allowing attackers to inject JavaScript through crafted XML payloads. The vulnerability permits reflected XSS in applications that process untrusted XML input. Upgrading to version 5.7.0 eliminates the unsafe entity expansion path.

high

sanitizeBangumiHtml() XSS Fix: insertAdjacentHTML RCE via JSON

A high-severity cross-site scripting vulnerability existed where untrusted JSON data from bangumis.json was rendered directly into the DOM using insertAdjacentHTML without sanitization. An attacker who could modify this external data source could execute arbitrary JavaScript in visitors' browsers. The fix introduces a dedicated sanitizeBangumiHtml() function that strips script tags, event handlers, and javascript: URLs before insertion.

high

form.js jQuery Selector Construction: CWE-79 Quote-Breaking Fix

A defense-in-depth fix in the form.js initialization routine eliminates a jQuery selector injection vector where user-controlled category data was concatenated directly into a string literal. The vulnerable pattern at lines 47-48 of the form handler could allow malicious content to break out of the selector's quoted context and execute arbitrary jQuery methods.

high

ItemPicker `_commitTraitInput` XSS via Unescaped Trait Chip Rendering

The `_commitTraitInput` function in ItemPicker accepted arbitrary user input for trait values without sanitization, then rendered those values directly into HTML chip elements through `_updateList`. An attacker could inject malicious JavaScript payloads that executed when trait chips were displayed. The fix applies a strict whitelist filter removing all non-alphanumeric characters except spaces and hyphens.

high

formatObject() Leaves localStorage Keys Unescaped Before innerHTML

The `formatObject()` pretty-printer used to build the key-configuration storage viewer interpolated object keys and array key/value pairs directly into an HTML string that is later assigned with `innerHTML`. Only string *values* were passed through `escapeHtml()`, so a crafted key name persisted in `localStorage` could execute script every time a user opened the storage viewer. The fix wraps the index-pair branch and the object-key branch in `escapeHtml()`, keeping the raw path only for non-colo

high

Voice Assistant Widget XSS: Unsanitized Bot Messages Execute in

The voice assistant widget's `appendMessage` function had a critical cross-site scripting (XSS) vulnerability where bot messages were inserted directly into the DOM without sanitization, while user messages were escaped. An attacker controlling bot responses could inject and execute arbitrary JavaScript in the user's browser context. The fix applies HTML escaping to all message types uniformly.