fix(viewer): harden PDF report rendering - #1192
Conversation
Greptile SummaryThis PR hardens PDF report generation against unsafe external content and malformed metadata.
Confidence Score: 5/5The PR appears safe to merge with no actionable changed-code defects identified. The new parser emits only fixed formatting tags or escaped token content, unsafe characters are normalized before ReportLab processing, and the dependency lock remains stable. Important Files Changed
Reviews (1): Last reviewed commit: "fix(viewer): harden PDF report rendering" | Re-trigger Greptile |
itzzdev09
left a comment
There was a problem hiding this comment.
Verified this against #1171 by running both branches. It fixes the bug — but the mechanism isn't the one the issue describes, which seems worth recording since it changes what a regression test should assert.
The issue's repro doesn't actually reproduce on main. _inline_md calls html.escape() before the emphasis regexes run, so literal tags are already neutral:
>>> _inline_md("<b><i></b></i>")
'<b><i></b></i>'End-to-end on current main, the exact finding text from #1171 builds a PDF fine:
issue payload -> PDF built, 4354 bytes
What actually breaks it is crossed markdown emphasis. Lines 421-423 rewrite **/* independently, so interleaved markers emit improperly nested tags:
'*a **b* c**' -> '<i>a <b>b</i> c</b>' ValueError
'**a *b** c*' -> '<b>a <i>b</b> c</i>' ValueError
'*outer **inner* end**' -> '<i>outer <b>inner</i> end</b>' ValueError
'__a *b__ c*' -> '<b>a <i>b</b> c</i>' ValueError
That produces exactly the reported saw </b> instead of expected </i>. So the reporter's finding almost certainly contained emphasis markers alongside the literal tags, and the tags got the blame.
This PR fixes all of them. On 42655fb, every case above builds, and end-to-end:
issue payload -> PDF built, 4354 bytes
crossed emphasis -> PDF built, 4429 bytes (ValueError on main)
normal markdown -> PDF built, 4608 bytes
Formatting still renders rather than merely not crashing:
'**bold**' -> '<b>bold</b>'
'`code`' -> '<font face="Courier" color="#b31d28">code</font>'
'**bold with `code`**' -> '<b>bold with <font face="Courier" ...>code</font></b>'
Two observations:
Crossed input now resolves to well-formed but all-italic markup — *a **b* c** becomes <i>a <i><i>b</i> c</i></i>. CommonMark's own handling of that input is ambiguous, and well-formed-and-italic beats a 500, so this looks like the right trade. Just noting it in case anyone expects mixed bold/italic there.
Nice catch on _severity_badge — Paragraph(severity.upper(), ...) was unescaped on main and this PR wraps it in _esc(). That's a second, separate hole closed.
If it helps, the four crossed-emphasis strings above are good regression cases, since a test built only around <b><i></b></i> would pass on main and prove nothing.
Disclosure: reviewed with AI assistance (Claude Code). Every output above is from running both branches, not inspection.
Summary
This builds on and credits the root-cause analysis by @apetcu in #1176, while closing the remaining ReportLab trust-boundary paths found during the broader audit.
Fixes #1171.
Verification
uv run pytest tests/test_report_pdf.py tests/test_fenced_code.py tests/test_viewer.py- 73 passeduv run ruff format --check strix/interface/viewer/report_pdf.py tests/test_report_pdf.pyuv run ruff check strix/interface/viewer/report_pdf.py tests/test_report_pdf.pyuv run mypy strix/interface/viewer/report_pdf.py tests/test_report_pdf.pyuv run bandit -r strix/interface/viewer/report_pdf.py -c pyproject.tomluv lock --checkpr-1176-reprorun generated a valid PDFuv run pytest- 1221 passed, 1 unrelated failure intests/test_pricing.py::test_resolves_common_bare_model_namesbecause the live LiteLLM catalog no longer resolvesMiniMax-M3Prepared with AI assistance; reviewed and tested locally.