-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: detect bidi control characters outside markdown files (CVE-2021-42574) #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,17 +76,24 @@ def _is_p9_skipped_path(file_path: str) -> bool: | |
| # P2: Hidden Instructions. Build the character class from the shared P9 | ||
| # constant so hidden-instruction and padding detection cannot drift apart. | ||
| _ZERO_WIDTH_PATTERN = "[" + "".join(sorted(ZERO_WIDTH_CHARS)) + "]" | ||
| # Bidirectional control characters (Trojan Source, CVE-2021-42574). Evaluated | ||
| # unconditionally below, NOT in the markdown-gated P2_PATTERNS list -- see the | ||
| # comment at that check for why. | ||
| # The class is the 9 paired controls only. The direction marks U+200E/U+200F/ | ||
| # U+061C are deliberately excluded: they cannot reorder a span the way the | ||
| # paired controls can, which is the same line GCC's -Wbidi-chars draws by | ||
| # default. Do not add them here without revisiting that tradeoff. | ||
| _BIDI_CONTROL_PATTERN = r"[\u202a-\u202e\u2066-\u2069]" | ||
| P2_PATTERNS = [ | ||
| (r"<!--.*?(?:system|instructions?|ignore|POST|GET|send|transmit).*?-->", 0.7), | ||
| (r"\[//\]:\s*#\s*\(.*?(?:system|instructions?|ignore|POST|GET|send|transmit).*?\)", 0.8), | ||
| (_ZERO_WIDTH_PATTERN, 0.6), | ||
| (r"[\u202a-\u202e\u2066-\u2069]", 0.85), | ||
| (r"data:text/plain;base64,[A-Za-z0-9+/=]{50,}", 0.7), | ||
| ] | ||
| _SINGLE_CHARACTER_P2_PATTERNS = frozenset( | ||
| { | ||
| _ZERO_WIDTH_PATTERN, | ||
| r"[\u202a-\u202e\u2066-\u2069]", | ||
| _BIDI_CONTROL_PATTERN, | ||
| } | ||
| ) | ||
| # P3: Exfiltration Commands | ||
|
|
@@ -341,6 +348,27 @@ def ctx(start: int) -> str: | |
| ) | ||
| ) | ||
|
|
||
| # P2 (extended): Bidirectional control characters (Trojan Source, | ||
| # CVE-2021-42574). Runs regardless of file_type, like the Tag-block check | ||
| # above — bidi overrides are exploitable in scripts and config files too | ||
| # (issue #39). Zero-width detection stays markdown-gated because | ||
| # ZERO_WIDTH_CHARS includes U+FEFF (BOM), which would false-positive on | ||
| # every BOM-prefixed source file; the bidi range never overlaps it. | ||
| for match in _p2_pattern_matches(content, _BIDI_CONTROL_PATTERN): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the i18n tradeoff you called out in the description — I confirmed it's real but narrow: a legit FSI/PDI-wrapped Arabic string in a |
||
| line_num = get_line_number(content, match.start()) | ||
| findings.append( | ||
| AnalyzerFinding( | ||
| rule_id="P2", | ||
| message="Hidden Instructions", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tiny nit, feel free to ignore: "Hidden Instructions" is indistinguishable from the zero-width and HTML-comment P2 findings when triaging results — the Tag-block check above uses a self-describing message. Changing it would shift the message on existing markdown findings though (your parity test asserts that), so leaving it is also reasonable. |
||
| severity=Severity.HIGH, | ||
| location=loc(line_num), | ||
| confidence=0.85, | ||
| tags=tag, | ||
| context=ctx(match.start()), | ||
| matched_text=match.group(0)[:200], | ||
| ) | ||
| ) | ||
|
|
||
| # P9: Whitespace Padding (skipped for generated/vendored files). | ||
| if not _is_p9_skipped_path(file_path): | ||
| for run in detect_whitespace_padding(content, file_type=file_type): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing worth a one-line code comment here: this class covers the 9 paired controls (U+202A–202E and U+2066–2069) but not the direction marks U+200E/U+200F/U+061C — I checked, those produce zero findings anywhere (they're not in
ZERO_WIDTH_CHARSeither). That matches GCC's-Wbidi-charsdefault, since the marks can't reorder spans the way the paired controls can, so I think the exclusion is right. But since the PR references CVE-2021-42574, documenting that it's deliberate would keep someone from "fixing" it later without understanding the tradeoff.