Skip to content

fix(security): skip ZWJ in legitimate emoji sequences during injection scan - #59503

Open
LavyaTandel wants to merge 3 commits into
NousResearch:mainfrom
LavyaTandel:fix/zwj-emoji-false-positive
Open

fix(security): skip ZWJ in legitimate emoji sequences during injection scan#59503
LavyaTandel wants to merge 3 commits into
NousResearch:mainfrom
LavyaTandel:fix/zwj-emoji-false-positive

Conversation

@LavyaTandel

Copy link
Copy Markdown

What

Context files (SOUL.md, AGENTS.md, .cursorrules) containing ZWJ emoji (e.g. 👨‍💻, 🐈‍⬛) are no longer blocked by the injection scanner. Previously, every U+200D was flagged as invisible_unicode_U+200D and the entire file was dropped.

Why

U+200D serves dual purpose: injection attacks (hiding text with zero-width joiners) and legitimate emoji ZWJ sequences. The scanner had no distinction — a decorative emoji in SOUL.md caused the agent to silently boot without its persona/instructions.

Fix

When U+200D is found, check if it joins two emoji code points (ranges: Misc Symbols 2600-26FF, Dingbats 2700-27BF, Emoticons/Transport/Supplemental 1F000-1FFFF, Variation Selectors FE00-FE0F). If every ZWJ in the content is between emoji code points, skip the finding. Non-emoji ZWJ is still flagged.

1 file changed, 36 insertions, 1 deletion.

Runtime Proof

Before: SOUL.md with 🐈‍⬛ (black cat) → blocked, agent boots without persona
After: same file loads normally, ZWJ in emoji sequences ignored

Duplicate Scan

No existing PRs for #59492.

Closes #59492

@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists labels Jul 6, 2026
…n scan

What: Context files (SOUL.md, AGENTS.md, .cursorrules) containing ZWJ
emoji (e.g. 👨‍💻, 🐈‍⬛) were blocked with 'invisible_unicode_U+200D'
because the injection scanner flagged every U+200D occurrence.

Why: U+200D is used both in injection attacks (hiding text with zero-width
joiners) and in legitimate emoji ZWJ sequences. The scanner had no
distinction.

Fix: When U+200D is found, check if it joins two emoji code points. If
every ZWJ in the content is between emoji code points (emoji ZWJ
sequence), skip the finding. Non-emoji ZWJ is still flagged.

Closes NousResearch#59492

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the real U+200D context-file false positive; current main still flags every U+200D in tools/threat_patterns.py:235-237, and agent/prompt_builder.py:61-64 blocks the affected context file.

Problems

  • tools/threat_patterns.py:217 classifies variation selectors and tag characters as emoji. Combined with the immediate-neighbour check at proposed line 267, a\uFE0F\u200D🔥 is accepted even though its left base is ASCII. Skip extensions before validating actual emoji bases, as the existing cron helper does in tools/cronjob_tools.py:145-157.
  • The diff has no regression tests. tests/agent/test_prompt_builder.py:63-113 covers blocked context content but not this acceptance path or malformed ZWJ cases.

Suggested changes

  • Add scanner and context-path tests for valid emoji sequences and for text/malformed-selector ZWJ sequences that must remain blocked.

Automated hermes-sweeper review.

Comment thread tools/threat_patterns.py
# Variation Selectors (FE00-FE0F), Tag characters (E0020-E007F).
return (
(0x2600 <= cp <= 0x27BF) or
(0xFE00 <= cp <= 0xFE0F) or

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variation selectors are extensions, not emoji bases. With this classification, a\uFE0F\u200D🔥 passes the later immediate-neighbour check although the ZWJ is not between two emoji. Skip selectors/tags while walking to the actual bases, then validate those bases as emoji.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 15, 2026
…ousResearch#59503)

Tag chars U+E0020-U+E007F (flag sequences, e.g. England flag) and
variation selectors U+FE0F (keycap sequences, e.g. '1' + VS16 + keycap)
were flagged as invisible unicode. Now skipped before the emoji-neighbour
check so legitimate emoji graphemes pass. Tests added.
@LavyaTandel

Copy link
Copy Markdown
Author

Hi @b Mentioning for visibility.

This PR is ready for review. All checks are red due to the first-time-contributor action_required gate — CI workflows cannot run until a maintainer approves them.

Could you please approve a workflow run so the checks can execute?

Rebased onto latest main, conflicts resolved. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False positive: ZWJ emoji (U+200D) in context files (SOUL.md/AGENTS.md) is flagged as injection and drops the whole file

3 participants