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:
- Prototype pollution: If an attacker can pollute
Object.prototype.title, thenrequest.data.titlecould resolve to an attacker-controlled value even if the IPC message itself looks clean. - Non-string coercion: If
request.data.titleis an object like{ toString: () => "malicious content" }, JavaScript's implicittoString()call during concatenation executes that function. null/undefinedinjection:request.data.HeadLinebeingundefinedwould render the string"undefined"visibly on screen — a content integrity issue.- Unexpected type confusion: If
request.data.Serialis 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.
Prevention & Best Practices
1. Treat IPC Data as Untrusted Input
In Electron applications, the IPC channel is a trust boundary. Data arriving via ipcRenderer.on() or window.electronAPI.* callbacks should be treated with the same skepticism as data from an HTTP API. Never assume that because data came from "your own main process," it is safe.
2. Define and Enforce a Data Schema
Use a validation library (like Zod or Joi) to parse IPC message payloads against a strict schema before using any fields:
import { z } from "zod";
const NankaiTroughInfoSchema = z.object({
reportKind: z.string(),
title: z.string().max(200),
kind: z.string().max(50),
Serial: z.number().optional(),
HeadLine: z.string().max(500),
reportDate: z.string(),
Text: z.string(),
Appendix: z.string(),
NextAdvisory: z.string(),
});
window.electronAPI.messageSend((event, request) => {
if (request.action == "NankaiTroughInfo") {
const data = NankaiTroughInfoSchema.parse(request.data); // throws on invalid
// Now use `data` safely
}
});
3. Use Explicit Type Coercion — Always
Whenever you concatenate external data into strings, use String(value || "") or template literals with validated values. Never rely on JavaScript's implicit coercion in security-sensitive contexts.
4. Enable Electron's Context Isolation and Sandbox
Ensure contextIsolation: true and sandbox: true are set in your BrowserWindow configuration. This limits the blast radius if renderer-side code is compromised, and prevents renderer scripts from directly accessing Node.js APIs.
new BrowserWindow({
webPreferences: {
contextIsolation: true,
sandbox: true,
preload: path.join(__dirname, 'preload.js')
}
});
5. OWASP and CWE References
- CWE-20: Improper Input Validation — the root cause here
- CWE-79: Improper Neutralization of Input During Web Page Generation — the DOM injection risk
- OWASP ASVS V5: Input Validation requirements for web applications
- Electron Security Checklist: Electron Security Documentation
Key Takeaways
innerTextis not a security boundary — it prevents HTML tag injection but not object coercion, prototype pollution, or type confusion attacks inNankaiTrough.html.- Every
request.datafield is an IPC trust boundary crossing —title,kind,Serial, andHeadLineall required explicitString()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
prefixternary 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.dataobject arriving viawindow.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, andrequest.data.HeadLineintodocument.titleandelement.innerTextassignments insrc/NankaiTrough.htmlat line 55 - Missing control: No type coercion, no schema validation, and no sanitization of any
request.datafields 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 totitle,kind,Serial, andHeadLinebefore 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.
References
- CWE-20: Improper Input Validation
- CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)
- OWASP Input Validation Cheat Sheet
- OWASP DOM-based XSS Prevention Cheat Sheet
- Electron Security Documentation
- Semgrep rules for JavaScript DOM injection
- fix: the nankaitrough in NankaiTrough.html