@@ -115,7 +115,70 @@ func TestScanMarkdownSecurity_UnicodeAbuse_AllowsNormalWhitespace(t *testing.T)
115115 assert .Empty (t , findings , "should not flag normal whitespace characters" )
116116}
117117
118- // --- Hidden Content Tests ---
118+ func TestScanMarkdownSecurity_UnicodeAbuse_AllowsEmojiZWJ (t * testing.T ) {
119+ // ZWJ (U+200D) is legitimate between emoji codepoints and must not be flagged.
120+ tests := []struct {
121+ name string
122+ content string
123+ }{
124+ {
125+ name : "people holding hands (issue report example)" ,
126+ content : "Status: \U0001F9D1 \u200D \U0001F91D \u200D \U0001F9D1 Team Triage" ,
127+ },
128+ {
129+ name : "family emoji" ,
130+ content : "Group: \U0001F468 \u200D \U0001F469 \u200D \U0001F467 " ,
131+ },
132+ {
133+ name : "woman technologist" ,
134+ content : "Role: \U0001F469 \u200D \U0001F4BB " ,
135+ },
136+ {
137+ name : "rainbow flag (variation selector before ZWJ)" ,
138+ content : "Flag: \U0001F3F3 \uFE0F \u200D \U0001F308 " ,
139+ },
140+ {
141+ name : "couple with heart (symbol-range emoji before ZWJ)" ,
142+ content : "Love: \U0001F468 \u200D \u2764 \uFE0F \u200D \U0001F469 " ,
143+ },
144+ }
145+
146+ for _ , tt := range tests {
147+ t .Run (tt .name , func (t * testing.T ) {
148+ findings := ScanMarkdownSecurity (tt .content )
149+ assert .Empty (t , findings , "should not flag emoji ZWJ sequence in %s" , tt .name )
150+ })
151+ }
152+ }
153+
154+ func TestScanMarkdownSecurity_UnicodeAbuse_FlagsNonEmojiZWJ (t * testing.T ) {
155+ // ZWJ between non-emoji (ASCII) characters is still suspicious and must be flagged.
156+ tests := []struct {
157+ name string
158+ content string
159+ }{
160+ {
161+ name : "ZWJ between ASCII letters" ,
162+ content : "Hello\u200D world" ,
163+ },
164+ {
165+ name : "ZWJ at start of text" ,
166+ content : "\u200D Hello" ,
167+ },
168+ {
169+ name : "ZWJ at end of line" ,
170+ content : "Hello\u200D " ,
171+ },
172+ }
173+
174+ for _ , tt := range tests {
175+ t .Run (tt .name , func (t * testing.T ) {
176+ findings := ScanMarkdownSecurity (tt .content )
177+ require .NotEmpty (t , findings , "should flag ZWJ outside emoji sequence in %s" , tt .name )
178+ assert .Equal (t , CategoryUnicodeAbuse , findings [0 ].Category , "category should be unicode-abuse" )
179+ })
180+ }
181+ }
119182
120183func TestScanMarkdownSecurity_HiddenContent_SuspiciousHTMLComments (t * testing.T ) {
121184 tests := []struct {
0 commit comments