Back to Blog
critical SEVERITY5 min read

navbar.html Injection via innerHTML in loadNavbar.js

An Electron app's `loadNavbar.js` fetched `navbar.html` and wrote the response straight into `innerHTML`, so any script tags or event-handler attributes in that file would execute in the renderer. The fix adds a `sanitizeNavbarHtml` routine that parses the markup with `DOMParser` and strips `<script>` tags, `on*` attributes, and `srcdoc` before the content is injected into the DOM.

O
By Orbis AppSec
•Published October 1, 2026•Reviewed October 1, 2026

Answer Summary

The affected code is the first-party `loadNavbar.js` script in an Electron desktop app, which fetched `navbar.html` and assigned the raw response to the navbar container's `innerHTML`. An attacker able to tamper with `navbar.html` — through a compromised dependency, a corrupted update, or local file write access — could inject HTML like an `<img onerror>` handler to execute arbitrary JavaScript in the Electron renderer, including Node.js APIs such as `require('child_process')`. The fix introduces a `sanitizeNavbarHtml` function that uses `DOMParser` to strip `<script>` elements and `on*`/`srcdoc` attributes before the markup reaches `innerHTML`; there is no package version or CVE involved since this is first-party code. No CWE identifier was assigned to this finding.

Vulnerability at a Glance

cweN/A
fixParse the fetched HTML with `DOMParser` and strip `<script>` tags and `on*`/`srcdoc` attributes before injection
riskArbitrary JavaScript execution in a renderer process with potential Node.js API access
languageJavaScript (Electron renderer)
root cause`fetch("navbar.html")` response text written directly to `innerHTML` with no sanitization
vulnerabilityDOM-based script injection via unsanitized innerHTML assignment

Summary

A navbar-loading script in an Electron desktop application fetched a local navbar.html fragment and inserted it directly into the page using innerHTML, with no sanitization step in between. Because Electron renderers can run with Node.js integration enabled, any HTML or JavaScript smuggled into that fragment — through a compromised dependency, a corrupted installer, or local file tampering — could execute with the same privileges as the app itself. The fix rewrites the render path to strip <script> tags and on*/srcdoc attributes from the fetched markup before it ever touches the DOM.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — fixed by the PR described below
Ecosystem n/a (Electron/JavaScript, first-party script)
CVE / GHSA not assigned
CWE unknown

Since this is application code rather than a published package, there's no version range to check — if your build includes the pre-fix loadNavbar.js, you're exposed; once the PR is merged, you aren't.

The Vulnerability Explained

The vulnerable logic lived in the navbar bootstrap routine that runs on DOMContentLoaded. It fetched a local HTML fragment and wrote the response straight into the DOM:

fetch("navbar.html")
  .then((response) => response.text())
  .then((data) => {
    document.getElementById("navbar").innerHTML = data;

The problem is the unconditional innerHTML = data assignment. Whatever bytes come back from navbar.html are parsed and executed as live DOM content — including <script> tags and inline event-handler attributes like onerror or onload. There is no check that the fetched content is the trusted, unmodified navbar markup shipped with the app.

In a desktop app context, the attack doesn't need a network man-in-the-middle. The threat model described in the fix is local: if navbar.html is replaced on disk — by a compromised npm dependency during install, a tampered auto-update package, or any process with local file write access — the next time the app starts, that modified file is fetched and rendered without question. A payload as simple as:

<img src=x onerror="require('child_process').exec('calc.exe')">

would execute the moment the broken image fails to load, because the handler runs inside the Electron renderer. If that renderer has Node integration enabled (common in older or loosely configured Electron apps), require is directly reachable from DOM event handlers, turning a markup-injection bug into full code execution on the user's machine — spawning processes, reading files, or reaching out to the network, all under the identity of the desktop app.

The Fix

The patch adds a sanitizeNavbarHtml helper that runs the fetched text through DOMParser, then strips the dangerous parts before anything is attached to the live document:

const sanitizeNavbarHtml = (html) => {
  const doc = new DOMParser().parseFromString(html, "text/html");
  doc.querySelectorAll("script").forEach((el) => el.remove());
  doc.querySelectorAll("*").forEach((el) => {
    [...el.attributes].forEach((attr) => {
      if (/^on/i.test(attr.name) || attr.name === "srcdoc") {
        el.removeAttribute(attr.name);
      }
    });
  });
  return doc.body.innerHTML;
};

The render call then uses the sanitized output instead of the raw response:

document.getElementById("navbar").innerHTML = sanitizeNavbarHtml(data);

This matters because the parsing happens in a detached document created by DOMParser — nothing executes while the markup is being inspected and cleaned. Only after <script> elements are removed and every on*/srcdoc attribute is stripped from every element does the resulting HTML get written into the real, live #navbar container. That ordering is what neutralizes the <img onerror="..."> style payload described in the exploit scenario: the onerror attribute is gone before the <img> tag ever reaches the DOM that the browser actually evaluates.

Key Takeaways

  • innerHTML = fetchedText is equivalent to running eval() on whatever that fetch returns — treat any locally fetched .html fragment as untrusted input, not just data pulled from the network.
  • Sanitizing with DOMParser plus explicit <script> and on*/srcdoc stripping is a targeted fix for this payload shape, but it's not a substitute for a full sanitizer if the navbar markup ever grows more complex attributes (e.g., javascript: URLs in href/src).
  • In Electron apps, a markup-injection bug is far more dangerous than in a browser tab because require('child_process') and similar Node APIs may be reachable from renderer-side event handlers — disabling Node integration in renderers is a complementary mitigation worth checking alongside this fix.
  • Sanitizing the render path doesn't verify the integrity of navbar.html itself; an attacker who can still overwrite that file on disk retains some influence over what's displayed, even if script execution is blocked.

How Orbis AppSec Detected This

  • Source: the fetch("navbar.html") response body, read via response.text()
  • Sink: document.getElementById("navbar").innerHTML = data
  • Missing control: no HTML sanitization or script/attribute stripping between fetching the file and injecting it into the live DOM
  • CWE: unknown (not assigned for this finding)
  • Fix: a new sanitizeNavbarHtml function parses the fetched markup with DOMParser, removes <script> elements and on*/srcdoc attributes, and only then assigns the result to innerHTML

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

This finding is a reminder that "local" content isn't automatically trusted content. navbar.html was fetched from the app's own filesystem, which made it easy to assume it was safe to pour straight into innerHTML — but anything that can modify a file on disk, from a compromised dependency to a corrupted update, can turn that assumption into renderer-level code execution. The fix doesn't change what the navbar looks like to a legitimate user; it just makes sure that if navbar.html is ever tampered with, the <script> tags and event handlers an attacker would rely on never survive the trip from fetch() to the DOM.

Prevention and further reading

Frequently Asked Questions

Does the `sanitizeNavbarHtml` fix block every possible injection into `navbar.html`, or just script tags and event handlers?

It specifically removes `<script>` elements and any `on*`/`srcdoc` attributes using `DOMParser`, which closes the two vectors described in the exploit scenario. It is not a general-purpose HTML sanitizer like DOMPurify, so unusual markup-based attacks outside those categories aren't automatically covered.

Why did an `onerror` handler with `require('child_process')` work in this navbar in the first place?

The exploitation scenario in the finding shows `<img src=x onerror="require('child_process').exec('calc.exe')">` executing successfully, which indicates the renderer where the navbar loads has Node integration enabled, giving DOM event handlers direct access to Node's `require`.

Does patching `loadNavbar.js` fix how `navbar.html` could be tampered with to begin with?

No. The fix sanitizes whatever markup is rendered, but it doesn't verify the integrity or origin of `navbar.html` itself; protecting the file from modification (e.g., bundling it as a trusted, checksummed asset) would need to be addressed separately.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #15

Related Articles

critical

addSourceInput() javascript: URL Injection in Source List Handler

The `addSourceInput()` function in the application's source list handler was assigning user-provided URLs directly to input elements without protocol validation, allowing `javascript:` URLs to persist and execute. A new `isSafeSourceUrl()` helper now restricts values to HTTP/HTTPS protocols or empty strings.

critical

getCheckboxString() HTML Injection via MIDI Metadata

The `getCheckboxString` helper built checkbox markup by interpolating MIDI instrument and program names directly into an HTML template string, which was then parsed with `DOMParser` and injected via `replaceChildren()`. A crafted MIDI file could smuggle an HTML/JS payload through its instrument metadata and have it rendered as live DOM, including inline event handlers. The fix adds a dedicated `escapeHtml()` function and routes both parameters through it before the markup is built.

high

Location Search XSS in index.html: Unsanitized Query Rendering

A location search feature in index.html rendered user-supplied search queries directly into the DOM without HTML entity encoding, allowing attackers to inject malicious JavaScript. The fix adds output encoding that converts dangerous characters (`&`, `<`, `>`, `"`, `'`) to their HTML entity equivalents before the query string reaches the page.

high

[ValidateInput(false)] on SettingsController.Index Enables Stored XSS

The `Index` POST action of the Power BI module's `SettingsController` was decorated with `[ValidateInput(false)]`, switching off ASP.NET MVC's built-in request validation for every form field bound to `SettingsModel`. Raw `<script>` markup could therefore be persisted into module settings and later rendered back to other users. The fix removes the attribute, restoring framework-level rejection of markup-bearing input on that action while leaving its `[ValidateAntiForgeryToken]` and edit-level au

high

innerHTML Injection in postAlert(): Glitch.me Data Renders Unsanitized

The `postAlert()` function fetched alert data from a Glitch.me endpoint and injected it directly into the DOM using `innerHTML`, enabling arbitrary JavaScript execution if that external source was compromised. The fix replaces the HTML string concatenation with safe DOM API methods: `document.createTextNode()` for content and `addEventListener()` for event handlers, eliminating the injection vector entirely.

critical

i18next-fs-backend 2.6.4 Prototype Pollution via Crafted Missing-Key

A critical prototype pollution vulnerability in i18next-fs-backend 2.6.4 allows attackers to modify Object.prototype through maliciously crafted translation key strings. The fix upgrades the package from 2.6.4 to 2.6.6, eliminating the unsafe key handling that permitted this attack vector.