Skip to content

Commit bca8c30

Browse files
authored
fix: close @mention neutralization bypass via U+200E/200F/00AD/034F invisible chars (#23735)
1 parent 57eb079 commit bca8c30

3 files changed

Lines changed: 80 additions & 1 deletion

File tree

actions/setup/js/sanitize_content.test.cjs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,57 @@ describe("sanitize_content.cjs", () => {
14881488
const expected = "";
14891489
expect(sanitizeContent(input)).toBe(expected);
14901490
});
1491+
1492+
it("should remove left-to-right mark (U+200E)", () => {
1493+
const input = "Hello\u200EWorld";
1494+
const expected = "HelloWorld";
1495+
expect(sanitizeContent(input)).toBe(expected);
1496+
});
1497+
1498+
it("should remove right-to-left mark (U+200F)", () => {
1499+
const input = "Hello\u200FWorld";
1500+
const expected = "HelloWorld";
1501+
expect(sanitizeContent(input)).toBe(expected);
1502+
});
1503+
1504+
it("should remove soft hyphen (U+00AD)", () => {
1505+
const input = "Hello\u00ADWorld";
1506+
const expected = "HelloWorld";
1507+
expect(sanitizeContent(input)).toBe(expected);
1508+
});
1509+
1510+
it("should remove combining grapheme joiner (U+034F)", () => {
1511+
const input = "Hello\u034FWorld";
1512+
const expected = "HelloWorld";
1513+
expect(sanitizeContent(input)).toBe(expected);
1514+
});
1515+
});
1516+
1517+
describe("@mention bypass prevention via invisible characters", () => {
1518+
it("should neutralize @mention with U+200F (RTL mark) inserted between @ and username", () => {
1519+
const input = "@\u200Fadmin please review";
1520+
expect(sanitizeContent(input)).toBe("`@admin` please review");
1521+
});
1522+
1523+
it("should neutralize @mention with U+200E (LTR mark) inserted between @ and username", () => {
1524+
const input = "@\u200Eadmin please review";
1525+
expect(sanitizeContent(input)).toBe("`@admin` please review");
1526+
});
1527+
1528+
it("should neutralize @mention with U+00AD (soft hyphen) inserted between @ and username", () => {
1529+
const input = "@\u00ADadmin please review";
1530+
expect(sanitizeContent(input)).toBe("`@admin` please review");
1531+
});
1532+
1533+
it("should neutralize @mention with U+034F (combining grapheme joiner) inserted between @ and username", () => {
1534+
const input = "@\u034Fadmin please review";
1535+
expect(sanitizeContent(input)).toBe("`@admin` please review");
1536+
});
1537+
1538+
it("should neutralize @mention with multiple invisible chars inserted between @ and username", () => {
1539+
const input = "ping @\u200E\u200F\u00AD\u034Fadmin now";
1540+
expect(sanitizeContent(input)).toBe("ping `@admin` now");
1541+
});
14911542
});
14921543

14931544
describe("Unicode normalization (NFC)", () => {

actions/setup/js/sanitize_content_core.cjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,10 @@ function hardenUnicodeText(text) {
931931

932932
// Step 3: Strip invisible zero-width characters that can hide content
933933
// These include: zero-width space, zero-width non-joiner, zero-width joiner,
934+
// left-to-right mark (U+200E), right-to-left mark (U+200F),
935+
// soft hyphen (U+00AD), combining grapheme joiner (U+034F),
934936
// word joiner, and byte order mark
935-
result = result.replace(/[\u200B\u200C\u200D\u2060\uFEFF]/g, "");
937+
result = result.replace(/[\u00AD\u034F\u200B\u200C\u200D\u200E\u200F\u2060\uFEFF]/g, "");
936938

937939
// Step 4: Remove bidirectional text override controls
938940
// These can be used to reverse text direction and create visual spoofs

actions/setup/js/sanitize_label_content.test.cjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,32 @@ describe("sanitize_label_content.cjs", () => {
116116
expect(sanitizeLabelContent(maliciousLabel)).toBe("bug");
117117
});
118118

119+
it("should remove left-to-right mark (U+200E) and right-to-left mark (U+200F)", () => {
120+
expect(sanitizeLabelContent("bug\u200Elabel")).toBe("buglabel");
121+
expect(sanitizeLabelContent("bug\u200Flabel")).toBe("buglabel");
122+
});
123+
124+
it("should remove soft hyphen (U+00AD) and combining grapheme joiner (U+034F)", () => {
125+
expect(sanitizeLabelContent("bug\u00ADlabel")).toBe("buglabel");
126+
expect(sanitizeLabelContent("bug\u034Flabel")).toBe("buglabel");
127+
});
128+
129+
it("should neutralize @mention with U+200F (RTL mark) between @ and username", () => {
130+
expect(sanitizeLabelContent("@\u200Fadmin")).toBe("`@admin`");
131+
});
132+
133+
it("should neutralize @mention with U+200E (LTR mark) between @ and username", () => {
134+
expect(sanitizeLabelContent("@\u200Eadmin")).toBe("`@admin`");
135+
});
136+
137+
it("should neutralize @mention with U+00AD (soft hyphen) between @ and username", () => {
138+
expect(sanitizeLabelContent("@\u00ADadmin")).toBe("`@admin`");
139+
});
140+
141+
it("should neutralize @mention with U+034F (combining grapheme joiner) between @ and username", () => {
142+
expect(sanitizeLabelContent("@\u034Fadmin")).toBe("`@admin`");
143+
});
144+
119145
it("should preserve emoji in labels", () => {
120146
expect(sanitizeLabelContent("🐛 bug")).toBe("🐛 bug");
121147
expect(sanitizeLabelContent("✨ enhancement")).toBe("✨ enhancement");

0 commit comments

Comments
 (0)