VYPR
Critical severityNVD Advisory· Published Feb 8, 2022· Updated Aug 3, 2024

CVE-2022-23340

CVE-2022-23340

Description

Joplin 2.6.10 allows remote attackers to execute system commands through malicious code in user search results.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Joplin 2.6.10 fails to sanitize user content in search results, allowing XSS that can lead to remote code execution via the Electron shell.

Vulnerability

Joplin version 2.6.10 contains a cross-site scripting (XSS) vulnerability in the "Goto Anything" search feature (triggered by Ctrl+P). When a user searches for a term, the application renders note content in the search results without proper HTML sanitization. An attacker can embed a malicious HTML payload (e.g., an ` tag with an onerror` event) inside a note that is indexed by the search engine. If the victim's search matches the surrounding text, the payload is rendered and executed in the Electron context. References [1], [2], and [4] confirm the issue and the fix in commit 810018b.

Exploitation

An attacker must have the ability to create or modify a note in Joplin that will be indexed by the local search engine. No network position or authentication is required beyond normal note access. The attacker inserts a payload like <img/src="1"/onerror=eval("require('child_process').exec('calc.exe');");> within the note content near searchable text. When the victim presses Ctrl+P and types the search term, Joplin displays the matching results and executes the embedded JavaScript in the Electron process. The attack requires no user interaction beyond the normal search action.

Impact

Successful exploitation allows an attacker to execute arbitrary JavaScript code in the context of the Electron application, which can leverage Node.js APIs (e.g., require('child_process').exec) to run system commands. This leads to full remote code execution (RCE) on the victim's machine, with the same privileges as the Joplin process. The impact includes complete compromise of confidentiality, integrity, and availability of the user's data and system [1][4].

Mitigation

The vulnerability was fixed in commit 810018b, which introduced proper sanitization (using MarkupToHtml from the renderer package) for the search result display. Users should upgrade to Joplin version 2.6.11 or later. No official workaround is available for the vulnerable version (2.6.10), but users can avoid using the Ctrl+P search with untrusted notes until patched. The issue is tracked on GitHub as #6004 and has a related CVE (CVE-2022-23340) [2][3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
joplinnpm
< 2.7.12.7.1

Affected products

2

Patches

1
810018b41f4d

Desktop: Security: Fixes #6004: Prevent XSS in Goto Anything

https://github.com/laurent22/joplinLaurent CozicJan 15, 2022via ghsa
6 files changed · +49 39
  • .eslintignore+3 0 modified
    @@ -1927,6 +1927,9 @@ packages/renderer/headerAnchor.js.map
     packages/renderer/htmlUtils.d.ts
     packages/renderer/htmlUtils.js
     packages/renderer/htmlUtils.js.map
    +packages/renderer/htmlUtils.test.d.ts
    +packages/renderer/htmlUtils.test.js
    +packages/renderer/htmlUtils.test.js.map
     packages/renderer/index.d.ts
     packages/renderer/index.js
     packages/renderer/index.js.map
    
  • .gitignore+3 0 modified
    @@ -1917,6 +1917,9 @@ packages/renderer/headerAnchor.js.map
     packages/renderer/htmlUtils.d.ts
     packages/renderer/htmlUtils.js
     packages/renderer/htmlUtils.js.map
    +packages/renderer/htmlUtils.test.d.ts
    +packages/renderer/htmlUtils.test.js
    +packages/renderer/htmlUtils.test.js.map
     packages/renderer/index.d.ts
     packages/renderer/index.js
     packages/renderer/index.js.map
    
  • packages/app-desktop/plugins/GotoAnything.tsx+2 1 modified
    @@ -19,6 +19,7 @@ const { mergeOverlappingIntervals } = require('@joplin/lib/ArrayUtils.js');
     import markupLanguageUtils from '../utils/markupLanguageUtils';
     import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
     import Logger from '@joplin/lib/Logger';
    +import { MarkupToHtml } from '@joplin/renderer';
     
     const logger = Logger.create('GotoAnything');
     
    @@ -81,7 +82,7 @@ class Dialog extends React.PureComponent<Props, State> {
     	private inputRef: any;
     	private itemListRef: any;
     	private listUpdateIID_: any;
    -	private markupToHtml_: any;
    +	private markupToHtml_: MarkupToHtml;
     	private userCallback_: any = null;
     
     	constructor(props: Props) {
    
  • packages/lib/htmlUtils.ts+0 35 modified
    @@ -1,7 +1,6 @@
     const urlUtils = require('./urlUtils.js');
     const Entities = require('html-entities').AllHtmlEntities;
     const htmlentities = new Entities().encode;
    -const htmlparser2 = require('@joplin/fork-htmlparser2');
     const { escapeHtml } = require('./string-utils.js');
     
     // [\s\S] instead of . for multiline matching
    @@ -138,40 +137,6 @@ class HtmlUtils {
     		return output.join(' ');
     	}
     
    -	public stripHtml(html: string) {
    -		const output: string[] = [];
    -
    -		const tagStack: any[] = [];
    -
    -		const currentTag = () => {
    -			if (!tagStack.length) return '';
    -			return tagStack[tagStack.length - 1];
    -		};
    -
    -		const disallowedTags = ['script', 'style', 'head', 'iframe', 'frameset', 'frame', 'object', 'base'];
    -
    -		const parser = new htmlparser2.Parser({
    -
    -			onopentag: (name: string) => {
    -				tagStack.push(name.toLowerCase());
    -			},
    -
    -			ontext: (decodedText: string) => {
    -				if (disallowedTags.includes(currentTag())) return;
    -				output.push(decodedText);
    -			},
    -
    -			onclosetag: (name: string) => {
    -				if (currentTag() === name.toLowerCase()) tagStack.pop();
    -			},
    -
    -		}, { decodeEntities: true });
    -
    -		parser.write(html);
    -		parser.end();
    -
    -		return output.join('').replace(/\s+/g, ' ');
    -	}
     }
     
     export default new HtmlUtils();
    
  • packages/renderer/htmlUtils.test.ts+32 0 added
    @@ -0,0 +1,32 @@
    +import htmlUtils from './htmlUtils';
    +
    +describe('htmlUtils', () => {
    +
    +	test('should strip off HTML', () => {
    +		const testCases = [
    +			[
    +				'',
    +				'',
    +			],
    +			[
    +				'<b>test</b>',
    +				'test',
    +			],
    +			[
    +				'Joplin&circledR;',
    +				'Joplin®',
    +			],
    +			[
    +				'&lt;b&gttest&lt;/b&gt',
    +				'&lt;b>test&lt;/b>',
    +			],
    +		];
    +
    +		for (const t of testCases) {
    +			const [input, expected] = t;
    +			const actual = htmlUtils.stripHtml(input);
    +			expect(actual).toBe(expected);
    +		}
    +	});
    +
    +});
    
  • packages/renderer/htmlUtils.ts+9 3 modified
    @@ -97,8 +97,7 @@ class HtmlUtils {
     		return selfClosingElements.includes(tagName.toLowerCase());
     	}
     
    -	// TODO: copied from @joplin/lib
    -	stripHtml(html: string) {
    +	public stripHtml(html: string) {
     		const output: string[] = [];
     
     		const tagStack: string[] = [];
    @@ -130,7 +129,14 @@ class HtmlUtils {
     		parser.write(html);
     		parser.end();
     
    -		return output.join('').replace(/\s+/g, ' ');
    +		// In general, we want to get back plain text from this function, so all
    +		// HTML entities are decoded. Howver, to prevent XSS attacks, we
    +		// re-encode all the "<" characters, which should break any attempt to
    +		// inject HTML tags.
    +
    +		return output.join('')
    +			.replace(/\s+/g, ' ')
    +			.replace(/</g, '&lt;');
     	}
     
     	public sanitizeHtml(html: string, options: any = null) {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.