Cross-site Scripting (XSS) - Stored in usememos/memos
Description
Cross-site Scripting (XSS) - Stored in GitHub repository usememos/memos prior to 0.9.1.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A stored XSS vulnerability in the Memos markdown renderer before v0.9.1 allows attackers to execute arbitrary JavaScript in other users' browsers.
The Memos open-source note-taking application, a self-hosted, markdown-native tool, contains a stored cross-site scripting (XSS) vulnerability in its markdown rendering pipeline [1]. The root cause involves the improper escape of user-controlled content during the rendering of bold, italic, and bold-italic markdown elements. In the source code, the escape function from the Lodash library was applied to the input text before passing it to the marked parser for these specific inline styles, but the commit that fixes the vulnerability shows that this escaping was removed, allowing raw HTML or JavaScript to be interpreted [2]. The fixing commit (64e5c343c5f74b0abdf3ac0d21a6139daea58cf8) also restructured the recursive rendering order to ensure escaping occurs at the correct step [2].
An attacker can exploit this stored XSS vulnerability by creating a note—such as a memo or comment—that contains a specially crafted markdown snippet using bold (**), italic (*), or bold-italic (***) syntax. For example, a payload like **** would not be properly sanitized, so that when any other authenticated user views the note in the web interface, the injected JavaScript executes in their browser. No special privileges are required beyond the ability to create or post content on the affected instance [1].
The impact is significant for a shared, self-hosted service: an attacker can steal session cookies, perform actions on behalf of the victim (such as modifying or deleting memos), deface pages, or perform other client-side attacks. Because the XSS is stored, every user who views the malicious note is automatically affected, enabling multi-user compromise from a single injection [1][4].
The vulnerability was addressed in Memos version 0.9.1, released in late December 2022. Users of earlier versions should upgrade as soon as possible. The fix is included in the project's main branch via commit 64e5c343c5f74b0abdf3ac0d21a6139daea58cf8 [2]. There are no known workarounds for this vulnerability beyond applying the patch.
AI Insight generated on May 20, 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.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/usememos/memosGo | < 0.9.1 | 0.9.1 |
Affected products
2- usememos/usememos/memosv5Range: unspecified
Patches
164e5c343c5f7chore: fix XSS in renderer (#875)
6 files changed · +7 −11
web/src/labs/marked/index.ts+1 −1 modified@@ -47,7 +47,7 @@ export const marked = (markdownStr: string, blockParsers = blockElementParserLis const matchedLength = matchedStr.length; const prefixStr = markdownStr.slice(0, matchedIndex); const suffixStr = markdownStr.slice(matchedIndex + matchedLength); - return prefixStr + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers); + return marked(prefixStr, [], inlineParsers) + matchedInlineParser.renderer(matchedStr) + marked(suffixStr, [], inlineParsers); } }
web/src/labs/marked/parser/BoldEmphasis.ts+1 −2 modified@@ -1,4 +1,3 @@ -import { escape } from "lodash"; import { marked } from ".."; import Link from "./Link"; @@ -15,7 +14,7 @@ const renderer = (rawStr: string): string => { return rawStr; } - const parsedContent = marked(escape(matchResult[1]), [], [Link]); + const parsedContent = marked(matchResult[1], [], [Link]); return `<strong><em>${parsedContent}</em></strong>`; };
web/src/labs/marked/parser/Bold.ts+1 −2 modified@@ -1,4 +1,3 @@ -import { escape } from "lodash"; import { marked } from ".."; import Link from "./Link"; @@ -15,7 +14,7 @@ const renderer = (rawStr: string): string => { return rawStr; } - const parsedContent = marked(escape(matchResult[1]), [], [Link]); + const parsedContent = marked(matchResult[1], [], [Link]); return `<strong>${parsedContent}</strong>`; };
web/src/labs/marked/parser/Emphasis.ts+1 −2 modified@@ -1,4 +1,3 @@ -import { escape } from "lodash"; import { marked } from ".."; import Link from "./Link"; @@ -15,7 +14,7 @@ const renderer = (rawStr: string): string => { return rawStr; } - const parsedContent = marked(escape(matchResult[1]), [], [Link]); + const parsedContent = marked(matchResult[1], [], [Link]); return `<em>${parsedContent}</em>`; };
web/src/labs/marked/parser/Link.ts+1 −1 modified@@ -17,7 +17,7 @@ const renderer = (rawStr: string): string => { if (!matchResult) { return rawStr; } - const parsedContent = marked(escape(matchResult[1]), [], [InlineCode, BoldEmphasis, Emphasis, Bold]); + const parsedContent = marked(matchResult[1], [], [InlineCode, BoldEmphasis, Emphasis, Bold]); return `<a class='link' target='_blank' rel='noreferrer' href='${escape(matchResult[2])}'>${parsedContent}</a>`; };
web/src/labs/marked/parser/Strikethrough.ts+2 −3 modified@@ -1,4 +1,4 @@ -import { marked } from ".."; +import { escape } from "lodash"; export const STRIKETHROUGH_REG = /~~(.+?)~~/; @@ -13,8 +13,7 @@ const renderer = (rawStr: string): string => { return rawStr; } - const parsedContent = marked(matchResult[1], [], []); - return `<del>${parsedContent}</del>`; + return `<del>${escape(matchResult[1])}</del>`; }; export default {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.