diff --git a/strix/interface/viewer/report_pdf.py b/strix/interface/viewer/report_pdf.py
index 358bd424..91436095 100644
--- a/strix/interface/viewer/report_pdf.py
+++ b/strix/interface/viewer/report_pdf.py
@@ -417,14 +417,15 @@ def _inline_md(text: str) -> str:
codes.append(match.group(1))
return f"\x00{len(codes) - 1}\x00"
- # Emphasis content may not contain its own delimiter or a tag, so the three passes can
- # never interleave into crossed markup (``..``), which reportlab rejects.
- # A run of asterisks such as a masked secret (``******``) therefore stays literal.
+ # Bold spans must start and end with a non-delimiter, so a run of asterisks such as a masked
+ # secret (``******``) stays literal. Italic content is plain text or complete ``..``
+ # spans, so the passes can nest (``**a *b* c**``, ``*a **b** c*``) but can never interleave
+ # into crossed markup (``..``), which reportlab rejects.
seg = html.escape(re.sub(r"`([^`]+)`", _stash, text))
- seg = re.sub(r"\*\*\*([^*<>\n]+?)\*\*\*", r"\1", seg)
- seg = re.sub(r"\*\*([^*<>\n]+?)\*\*", r"\1", seg)
- seg = re.sub(r"__([^_<>\n]+?)__", r"\1", seg)
- seg = re.sub(r"\*([^*<>\n]+?)\*", r"\1", seg)
+ seg = re.sub(r"\*\*\*(?=[^*])(.+?)(?<=[^*])\*\*\*", r"\1", seg)
+ seg = re.sub(r"\*\*(?=[^*])(.+?)(?<=[^*])\*\*", r"\1", seg)
+ seg = re.sub(r"__(?=[^_])(.+?)(?<=[^_])__", r"\1", seg)
+ seg = re.sub(r"\*((?:[^*<>\n]|[^<>*\n]*)+?)\*", r"\1", seg)
def _restore(match: re.Match[str]) -> str:
inner = html.escape(codes[int(match.group(1))])
diff --git a/tests/test_report_pdf.py b/tests/test_report_pdf.py
index c25b486b..35b103b0 100644
--- a/tests/test_report_pdf.py
+++ b/tests/test_report_pdf.py
@@ -156,3 +156,9 @@ def test_generate_report_pdf_with_masked_secret_in_summary(tmp_path: Path) -> No
def test_para_falls_back_to_plain_text_on_crossed_markup() -> None:
para = _para("x & y", ParagraphStyle("t"))
assert para.getPlainText() == "x & y"
+
+
+def test_inline_md_keeps_balanced_nested_emphasis() -> None:
+ assert _inline_md("**bold with *italic* inside**") == "bold with italic inside"
+ assert _inline_md("*outer **bold** inner*") == "outer bold inner"
+ assert "******" in _inline_md("(observed as '******')") # a masked secret stays literal