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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
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<img src=x onerror=alert(1)>.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()andescapeBackSlash()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
textis finalized, immediately before it enters theresultsarray 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.nameandfile.path) consumed inside thesearch(text)function - Sink: the search-results renderer that inserts the computed
textvalue 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.