Back to Blog
high SEVERITY7 min read

escapeQuotes() Gap Lets File Names XSS Search Results

The desktop app's file-search results renderer built HTML strings from file names and paths using only `escapeQuotes()` and `escapeBackSlash()`, which strip quotes and backslashes but leave `<`, `>`, and `&` untouched. A file or folder named with an HTML payload such as `<img src=x onerror=alert(1)>` would execute when the matching search result was rendered, giving an attacker script execution in the app's DOM context.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 25, 2026•Reviewed September 25, 2026

Answer Summary

The affected code is first-party JavaScript in a `search(text)` function that builds display text from file-system metadata (`file.name`, `file.path`). An attacker who can name a file (e.g., `<img src=x onerror=alert(1)>.pdf`) gets that markup rendered live in the app's search results, achieving script execution in the UI's DOM context. The fix adds a chained `.replace()` call that HTML-entity-encodes `&`, `<`, `>`, `"`, and `'` before the text is pushed into the results array; no separate version is tracked since this is first-party code. This is classified under CWE-79, Improper Neutralization of Input During Web Page Generation.

Vulnerability at a Glance

cweCWE-79
fixAdded entity-encoding replace chain for &, <, >, ", ' before the text is stored in results
riskMalicious file/folder names execute as HTML/JS when displayed in search results
languageJavaScript
root causeescapeQuotes()/escapeBackSlash() only escape quotes and backslashes, not HTML markup characters
vulnerabilityCross-Site Scripting (XSS) via unescaped file names in search results

TITLE: escapeQuotes() Gap Lets File Names XSS Search Results

SEO_TITLE: escapeQuotes() XSS: File Search HTML Injection Fix

SEO_DESCRIPTION: escapeQuotes() in the file search renderer skips HTML entity encoding, letting crafted file names execute as script (CWE-79); the fix adds entity escaping.

SUMMARY: The desktop app's file-search results renderer built HTML strings from file names and paths using only escapeQuotes() and escapeBackSlash(), which strip quotes and backslashes but leave <, >, and & untouched. A file or folder named with an HTML payload such as <img src=x onerror=alert(1)> would execute when the matching search result was rendered, giving an attacker script execution in the app's DOM context.

INTRODUCTION: The file-search feature parses untrusted input in the form of on-disk file names and paths, and a flaw in that path created a stored cross-site scripting condition inside the app itself. Inside the async search(text) function, the code computes a display string with let text = file.matchPath ? file.path.replace(...) : file.name; and then pushes that raw string into a results array that is later rendered into the DOM. The two existing helper functions, escapeQuotes and escapeBackSlash, were designed to make strings safe for embedding inside quoted JavaScript/HTML attribute values — they were never meant to be an HTML sanitizer, but they were the only defense standing between a file name on disk and the DOM. Any code path that builds HTML from file-system metadata (names, paths, extended attributes) needs to treat that metadata as attacker-controlled, because on most platforms a user can name a file almost anything.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code)
Ecosystem not applicable
CVE / GHSA not assigned
CWE CWE-79 (Improper Neutralization of Input During Web Page Generation)

The Vulnerability Explained

The relevant part of the search() function reads:

let text = file.matchPath ? file.path.replace(new RegExp('^\s*'+pregQuote(file.mainPath)+pregQuote(p.sep)+'?'), '') : file.name;

results.push({
    icon: file.compressed ? 'folder_zip' : (file.folder ? 'folder' : ''),
    image: image,

Before the fix, text was pushed straight into the results array with no transformation beyond whatever escapeQuotes/escapeBackSlash did elsewhere in the file. escapeQuotes only escapes single/double quotes and escapeBackSlash only escapes backslashes — neither one touches <, >, &, or the characters that actually matter for HTML/script injection. Because search results are rendered into the DOM (as list entries showing the matched file or folder name), any file whose name or path contains raw markup gets interpreted by the browser/renderer rather than displayed as text.

Attack scenario: An attacker drops or renames a file to something like report<img src=x onerror=alert(document.cookie)>.pdf inside a directory the victim will search (a shared folder, a downloads directory, an extracted archive, etc.). When the victim opens the app's search UI and types a query that matches, search(text) computes file.name for the display string, pushes it unescaped, and the rendering layer inserts it into the results list. The onerror handler fires immediately, running attacker-controlled JavaScript in the same context as the rest of the application UI — a classic stored/DOM-based XSS, except the "storage" is the file system itself rather than a database.

For a desktop or Electron-style app, this is more than a nuisance: script execution inside the app's renderer can potentially reach IPC bridges, local file APIs, or other privileged surfaces depending on how the app is architected, turning a mislabeled file into a foothold for further compromise.

The Fix

The fix adds a dedicated HTML-entity-encoding step for the computed text value, immediately before it is placed into the results array:

text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');

This single line closes the gap left by escapeQuotes/escapeBackSlash by encoding all five characters that matter for HTML context injection: &, <, >, ", and '. Encoding & first is important — doing it last would double-encode the entities just produced by the other replacements, so the order in the chain matters. With this change, a file named report<img src=x onerror=alert(1)>.pdf is rendered as literal text (report&lt;img src=x onerror=alert(1)&gt;.pdf) instead of being parsed as a live <img> tag, regardless of whether it arrived via file.name or the file.path substring computed a few lines above.

Key Takeaways

  • escapeQuotes() and escapeBackSlash() are not HTML sanitizers — they protect quoted string literals, not markup context, and reusing them as if they were general-purpose output encoding is exactly what created this bug.
  • File names and paths returned by the file system (file.name, file.path) are attacker-controllable data on any platform that lets users rename files, and must be treated the same as any other untrusted input before hitting the DOM.
  • When a value crosses from "file metadata" to "rendered HTML," the encoding has to happen at that boundary — the fix inserts the escaping right where text is finalized, immediately before it enters the results array that feeds the UI.
  • Order matters when chaining .replace() calls for entity encoding: & must be escaped first, or the entities produced by escaping </>/"/' get double-encoded.

How Orbis AppSec Detected This

  • Source: file-system metadata (file.name and file.path) consumed inside the search(text) function
  • Sink: the search-results renderer that inserts the computed text value into the DOM as part of the results list
  • Missing control: HTML entity encoding of <, >, &, ", and ' — only quote and backslash escaping (escapeQuotes, escapeBackSlash) were applied
  • CWE: CWE-79, Improper Neutralization of Input During Web Page Generation (Cross-site Scripting)
  • Fix: a chained .replace() call HTML-encodes the display text before it is pushed into the results array

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 case is a good reminder that "we already escape it" is not the same as "we escape it for the right context." The search() function had escaping logic in place — it just protected against the wrong set of characters for where the output actually landed. Because file names are effectively free-form attacker input on most systems, any UI that displays them needs explicit, context-appropriate output encoding, not string helpers borrowed from a different part of the codebase. The one-line fix here — HTML-encoding text right before it's stored in results — closes that gap without touching the rest of the search logic.

TAGS: xss, cwe-79, output-encoding, javascript, dom-manipulation, file-search

ANSWER_SUMMARY: The affected code is first-party JavaScript in a search(text) function that builds display text from file-system metadata (file.name, file.path). An attacker who can name a file (e.g., <img src=x onerror=alert(1)>.pdf) gets that markup rendered live in the app's search results, achieving script execution in the UI's DOM context. The fix adds a chained .replace() call that HTML-entity-encodes &, <, >, ", and ' before the text is pushed into the results array; no separate version is tracked since this is first-party code. This is classified under CWE-79, Improper Neutralization of Input During Web Page Generation.

VULNERABILITY_AT_A_GLANCE:
Vulnerability: Cross-Site Scripting (XSS) via unescaped file names in search results
CWE: CWE-79
Language: JavaScript
Risk: Malicious file/folder names execute as HTML/JS when displayed in search results
Root cause: escapeQuotes()/escapeBackSlash() only escape quotes and backslashes, not HTML markup characters
Fix: Added entity-encoding replace chain for &, <, >, ", ' before the text is stored in results

PRIMARY_ENTITY:
cve: N/A
ghsa: N/A
package: N/A
ecosystem: N/A
fixed_in: N/A
cwe: CWE-79

FAQ:
Q: Does the fix in the search() function replace escapeQuotes() and escapeBackSlash()?
A: No, both helper functions are left in place; the fix adds an additional .replace() chain that HTML-encodes &, <, >, ", and ' on top of the existing quote/backslash escaping.

Q: Where exactly is the new encoding applied relative to file.matchPath and file.name?
A: It's applied to the unified text variable right after it's computed from either file.path (when matchPath is true) or file.name, and before that value is pushed into the results array.

Q: Could a crafted folder name trigger this issue the same way as a file name?
A: Yes — the vulnerable code path used file.name for both files and folders when matchPath was false, so a malicious folder name would be rendered unescaped in exactly the same way.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #646

Related Articles

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.

critical

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.

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.