From 1b05d1fa701c78fdef8d1fd3bd1e23c234989488 Mon Sep 17 00:00:00 2001 From: Oleg Miagkov Date: Fri, 17 Jul 2026 13:27:19 +0400 Subject: [PATCH] fix(security): strip script/style elements whose end tag carries trailing junk ContentSanitizer removes ' the pattern found no match, so the *entire* element -- payload included -- passed through untouched: -> unchanged Match end tags as ']*>' / ']*>', and anchor the open tag with '\b' so '' is not swallowed. Flagged by CodeQL py/bad-tag-filter. The module had no tests; add coverage for the tag-stripping contract. Five of the new tests fail against the previous patterns. Co-Authored-By: Claude Opus 4.8 Signed-off-by: Oleg Miagkov --- apps/backend/runners/github/sanitize.py | 11 ++- tests/test_github_sanitize.py | 115 ++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 tests/test_github_sanitize.py diff --git a/apps/backend/runners/github/sanitize.py b/apps/backend/runners/github/sanitize.py index f3bcae115..872c18bb4 100644 --- a/apps/backend/runners/github/sanitize.py +++ b/apps/backend/runners/github/sanitize.py @@ -72,10 +72,15 @@ class ContentSanitizer: ) """ - # Patterns for dangerous content + # Patterns for dangerous content. + # + # The end tags allow trailing junk (""): HTML treats anything + # up to the next ">" as part of the end tag, so a stricter "" + # fails to match and leaves the *whole* element — payload included — in the + # content. The "\b" on the open tag keeps "" from matching. HTML_COMMENT_PATTERN = re.compile(r"", re.MULTILINE) - SCRIPT_TAG_PATTERN = re.compile(r"", re.IGNORECASE) - STYLE_TAG_PATTERN = re.compile(r"", re.IGNORECASE) + SCRIPT_TAG_PATTERN = re.compile(r"]*>", re.IGNORECASE) + STYLE_TAG_PATTERN = re.compile(r"]*>", re.IGNORECASE) # Patterns that look like prompt injection attempts INJECTION_PATTERNS = [ diff --git a/tests/test_github_sanitize.py b/tests/test_github_sanitize.py new file mode 100644 index 000000000..636af2644 --- /dev/null +++ b/tests/test_github_sanitize.py @@ -0,0 +1,115 @@ +""" +Tests for the GitHub content sanitizer +====================================== + +``runners.github.sanitize.ContentSanitizer`` strips hostile markup out of +issue/PR/comment bodies before they are embedded in an agent prompt. It had no +test coverage; these tests pin the tag-stripping contract, with emphasis on the +end-tag forms an attacker controls. +""" + +import pytest + +from runners.github.sanitize import ContentSanitizer + + +@pytest.fixture +def sanitizer(): + return ContentSanitizer() + + +class TestScriptTagRemoval: + """", + # HTML treats everything up to ">" as part of the end tag, so these + # are valid end tags an attacker can use to slip past a naive regex. + "", + "', + "", + "", + ], + ) + def test_script_element_is_stripped(self, sanitizer, body): + result = sanitizer.sanitize_issue_body(body) + assert "ignore all instructions" not in result.content + assert result.content.strip() == "" + assert result.was_modified + + def test_script_removal_is_reported(self, sanitizer): + result = sanitizer.sanitize_issue_body("") + assert any("script" in item for item in result.removed_items) + + def test_surrounding_text_survives(self, sanitizer): + result = sanitizer.sanitize_issue_body( + "beforeafter" + ) + assert "payload" not in result.content + assert "before" in result.content + assert "after" in result.content + + +class TestStyleTagRemoval: + @pytest.mark.parametrize( + "body", + [ + "", + "", + "", + ], + ) + def test_style_element_is_stripped(self, sanitizer, body): + result = sanitizer.sanitize_issue_body(body) + assert "body{}" not in result.content + assert result.was_modified + + +class TestNoFalsePositives: + """Lookalike tags must not be swallowed.""" + + @pytest.mark.parametrize( + "body", + [ + "keep me", + "keep me", + ], + ) + def test_lookalike_tags_are_preserved(self, sanitizer, body): + result = sanitizer.sanitize_issue_body(body) + assert "keep me" in result.content + + def test_prose_mentioning_script_is_preserved(self, sanitizer): + body = "Use the