fix(sanitizer): match attribute quotes by type to avoid mangling content (#1371)

stripHiddenAttributes used the pattern `["'][^"']*["']` for each quoted
attribute, which matches an opening quote of either type and stops at the
first quote of either type. When a value contained the other quote
character — e.g. an apostrophe inside a double-quoted attribute like
`title="We'll do it"` — the match terminated at the apostrophe, so the
wrong span was removed and the surrounding text was corrupted (e.g.
`<Tooltip title="We'll do it" placement="top">` became
`<Tooltipll do it" placement="top">`).

This surfaced via the github_inline_comment MCP tool: suggestion blocks
whose code lines contain quotes were mangled before posting (#1366).

Match each quoted form per quote type (`"[^"]*"` and `'[^']*'`),
mirroring stripMarkdownLinkTitles, so a value may freely contain the
other quote character. The unquoted fallback is unchanged.

Closes #1366

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
This commit is contained in:
bymle
2026-06-11 21:19:44 -07:00
committed by GitHub
co-authored by bymle
parent 24b915648e
commit 36617bd48b
2 changed files with 28 additions and 5 deletions
+13 -5
View File
@@ -20,15 +20,23 @@ export function stripMarkdownLinkTitles(content: string): string {
}
export function stripHiddenAttributes(content: string): string {
content = content.replace(/\salt\s*=\s*["'][^"']*["']/gi, "");
// Quoted values are matched per quote type so that a value containing the
// other quote character (e.g. an apostrophe inside a double-quoted value)
// does not terminate the match early and mangle surrounding content (#1366).
content = content.replace(/\salt\s*=\s*"[^"]*"/gi, "");
content = content.replace(/\salt\s*=\s*'[^']*'/gi, "");
content = content.replace(/\salt\s*=\s*[^\s>]+/gi, "");
content = content.replace(/\stitle\s*=\s*["'][^"']*["']/gi, "");
content = content.replace(/\stitle\s*=\s*"[^"]*"/gi, "");
content = content.replace(/\stitle\s*=\s*'[^']*'/gi, "");
content = content.replace(/\stitle\s*=\s*[^\s>]+/gi, "");
content = content.replace(/\saria-label\s*=\s*["'][^"']*["']/gi, "");
content = content.replace(/\saria-label\s*=\s*"[^"]*"/gi, "");
content = content.replace(/\saria-label\s*=\s*'[^']*'/gi, "");
content = content.replace(/\saria-label\s*=\s*[^\s>]+/gi, "");
content = content.replace(/\sdata-[a-zA-Z0-9-]+\s*=\s*["'][^"']*["']/gi, "");
content = content.replace(/\sdata-[a-zA-Z0-9-]+\s*=\s*"[^"]*"/gi, "");
content = content.replace(/\sdata-[a-zA-Z0-9-]+\s*=\s*'[^']*'/gi, "");
content = content.replace(/\sdata-[a-zA-Z0-9-]+\s*=\s*[^\s>]+/gi, "");
content = content.replace(/\splaceholder\s*=\s*["'][^"']*["']/gi, "");
content = content.replace(/\splaceholder\s*=\s*"[^"]*"/gi, "");
content = content.replace(/\splaceholder\s*=\s*'[^']*'/gi, "");
content = content.replace(/\splaceholder\s*=\s*[^\s>]+/gi, "");
return content;
}