diff --git a/tools/cve-tool-vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py b/tools/cve-tool-vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py index 75b510d74..efb01f731 100644 --- a/tools/cve-tool-vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py +++ b/tools/cve-tool-vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py @@ -2020,7 +2020,10 @@ def _splice_attachment_into_body(issue_body: str, attachment: str, cve_id: str) end_escaped = re.escape(end_marker) pattern = rf"{begin_escaped}.*?{end_escaped}\n?" if re.search(pattern, issue_body, re.DOTALL): - new_body = re.sub(pattern, attachment.rstrip("\n") + "\n", issue_body, count=1, flags=re.DOTALL) + # A callable replacement is inserted verbatim; a string would have its + # backslash escapes processed, turning the JSON's ``\n`` into newlines. + replacement = attachment.rstrip("\n") + "\n" + new_body = re.sub(pattern, lambda _: replacement, issue_body, count=1, flags=re.DOTALL) return new_body.rstrip() + "\n" # Also tolerate a legacy single-marker attachment that was embedded diff --git a/tools/cve-tool-vulnogram/generate-cve-json/tests/test_cli.py b/tools/cve-tool-vulnogram/generate-cve-json/tests/test_cli.py index 26227546b..0b683fd5b 100644 --- a/tools/cve-tool-vulnogram/generate-cve-json/tests/test_cli.py +++ b/tools/cve-tool-vulnogram/generate-cve-json/tests/test_cli.py @@ -176,6 +176,24 @@ def test_replaces_existing_attachment_block(self): assert begin in result assert end in result + def test_replace_keeps_json_backslash_escapes_verbatim(self): + # Replacing an existing block must not process backslash escapes in + # the new attachment: a JSON ``\n`` inside a string value has to stay + # a two-character escape, or the embedded JSON stops parsing. + begin, end = self._markers() + payload = { + "summary": "First paragraph.\n\nSecond paragraph.", + "path": "C:\\temp\\x", + "group": "keep \\1 literal", + } + json_text = json.dumps(payload, indent=2) + attachment = f"{begin}\n## CVE JSON\n```json\n{json_text}\n```\n{end}\n" + body = f"### Body before\nstuff\n\n{begin}\nOLD CONTENT\n{end}\ntrailing line\n" + result = cve_json._splice_attachment_into_body(body, attachment, self.cve_id) + embedded = result.split("```json\n", 1)[1].split("\n```", 1)[0] + assert embedded == json_text + assert json.loads(embedded) == payload + def test_legacy_single_marker_path(self): begin, _ = self._markers() body = f"### Body\n\n{begin}\nleftover legacy content with no end marker\n"