[high] fix(report): escape finding titles and restore the executive summary's markup - #128
Draft
elhoim wants to merge 1 commit into
Draft
[high] fix(report): escape finding titles and restore the executive summary's markup#128elhoim wants to merge 1 commit into
elhoim wants to merge 1 commit into
Conversation
…s markup
Two coupled defects on one path from evidence to the rendered HTML report.
_build_executive_summary returns a string of pre-built HTML. The template
interpolated it as `{{ executive_summary }}` with no `| safe`, and autoescaping
is on for .html.j2, so the first block an analyst reads showed raw
`<div class=...>` tags as literal text instead of a formatted summary.
Marking it `| safe` is only correct once the finding titles baked into that
string are escaped. Four of them are -- the first, middle and last timeline
events and the Key Threats list -- and titles come straight off the evidence,
so an attacker-chosen filename or registry value would otherwise reach the
report as live markup. html.escape appeared nowhere in the module. Fixing
either half alone is wrong: escaping without `| safe` leaves the display bug,
and `| safe` without escaping turns a display bug into stored XSS.
Recovered from closed PR calebevans#42, which carried this fix under a large
evidence-envelope refactor. Only the fix is taken; no framework comes with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF
<div class="exec-threats">instead of a formatted summary.| safe) makes the XSS live. Neither half is correct alone.html.escape()on the four title interpolations, plus| safeon the single template use site. 9 lines inrenderer.py, 1 token in the template.src/mulder/report/renderer.pyandreport.html.j2only.The bug
_build_executive_summarybuilds raw HTML with f-strings and returns it as a plainstr:Three more titles go in via the timeline narrative (
first_event,mid_titles,last_event).html.escapeappears nowhere in the module.The template interpolates the result without
| safe:Autoescaping is on for
.html.j2, and exactly three values are marked trusted there (narrative_html | safe,f.description_html | safe×2).executive_summaryis not one of them, so its own markup is escaped and displayed as text.The fix
The
last_event != first_eventcomparison still holds: both sides are escaped identically.Why both halves ship together
| safealone →<script>in a finding title becomes live markup in the report.A reviewer applying only one half gets either no improvement or a regression, so they are one change.
Deliberately out of scope
{{ f.title }}sites in the template (finding list, timeline, MITRE panels) need no change: autoescaping already covers them because they are interpolated as values, not as pre-built HTML. A test pins that this PR did not weaken that._build_executive_summary_mdfeeds the markdown report, which is deliberately not autoescaped. Different output, different concern.Verification
uvx pre-commit run --all-files(new test staged first) → ruff, ruff-format, mypy all pass.uv run --locked --extra dev pytest tests/ -q→ 865 passed, nothing deselected.renderer.pyandreport.html.j2restored toorigin/mainand the new tests kept, 7 of 10 fail:The three that pass on both trees are the narrowness tests — they pin the fix, not the bug.
Context
Recovered from closed PR #42 (
codex/pr-3.1-evidence-envelope), which carried this fix underneath a large evidence-envelope refactor. Only the fix is taken: no outcome framework, no envelope types, no shared helper — the maintainer's objection in #32 was to that architecture, and none of it appears here.The display half is a regression introduced by my own merged #73, which turned autoescaping on for
.html.j2. #73 marked the three genuinely pre-rendered values| safeand missed this fourth one, which is pre-rendered HTML built in Python rather than bymarkdown.markdown.Branched fresh from current
main(2e5432c).🤖 Generated with Claude Code