CONTENTS
Intro
I found this issue while reviewing Memray, Bloomberg’s Python memory profiler, with a simple question in mind:
Can attacker-controlled runtime metadata cross into browser-rendered report output unsafely?
In this case, the answer was yes.
The bug was in Memray’s HTML report generation path, where command-line metadata was rendered into a browser-opened report without escaping. That turned an operational field into an executable HTML sink and ultimately became CVE-2026-32722.
Memray: Memray
Advisory: GHSA-r5pr-887v-m2w9 /CVE-2026-32722
Attack Chain
attacker-controlled argv → metadata.command_line → HTML template sink without escaping → raw markup in generated report → browser-side JavaScript execution
What Memray Does
Memray is a Python memory profiler.
It instruments a Python process, records allocation behavior, and produces reports that help developers understand:
- where memory is allocated
- which call paths are responsible
- what peak memory usage looks like
- how memory changes over time
Some of those reports are emitted as HTML and opened in a browser.
That makes report generation a real security boundary.
The relevant question is not whether Memray is a “local tool.”
The relevant question is whether attacker-controlled data can cross into browser-rendered output unsafely.
In this case, it could.
Why This Bug Was Worth Looking At
A lot of people underestimate developer tooling.
That is a mistake.
Once a tool:
- records runtime metadata,
- stores attacker-influenced values,
- and later renders them into HTML,
it inherits the same output-encoding risks as a web application.
That was the core issue here.
This bug was not in profiling logic. It was not in allocation tracking. It was not in native memory handling.
It was a classic trust-boundary failure:
- untrusted metadata entered the system,
- crossed into an HTML sink,
- and was rendered without escaping.
That is enough to create a real vulnerability.
The Boundary I Focused On
I did not approach Memray by fuzzing random CLI options or chasing crashes.
The stronger approach was to identify the highest-probability security surface first.
For Memray, that was HTML report generation.
Why?
Because HTML output introduces a browser sink, and browser sinks turn ordinary metadata bugs into security issues if:
- the input is attacker-influenced,
- the output is unescaped,
- and the browser interprets the result as markup instead of text.
That is exactly what happened here.
Root Cause
The bug reduces to two lines.
In ~/templates/__init__.py:
the Jinja environment is created without autoescape.
Then in:
metadata.command_line is rendered directly into HTML.
That is the whole vulnerability.
Why this is exploitable
Because metadata.command_line is attacker-influenced.
Memray records the command line used to run the profiled program.
That means user-controlled values from argv are preserved as metadata and later inserted into the report.
So the exploit chain is straightforward:
- attacker controls command-line content
- Memray stores it in
metadata.command_line - the template emits it into HTML
- the environment does not autoescape it
- the browser parses it as live markup
That turns metadata into executable browser content.
What Makes This a Security Issue, Not Just Bad Rendering
The important distinction is execution.
Plenty of bugs produce malformed HTML. That alone is not enough.
Here, attacker-controlled content was not merely visible in the page source. It was interpreted by the browser as active HTML and executed as JavaScript.
That is the difference between:
- formatting corruption
- and a real XSS sink
So the question was not:
“Can HTML appear in the report?”
The real question was:
“Can attacker-controlled HTML become executable when the report is opened?”
The answer was yes.
PoC
My initial reproducer used an explicit script plus an attacker-controlled argument:
The generated HTML contained raw attacker-controlled markup:
Opening or refreshing the generated report triggered JavaScript execution.
That established the core claim:
- the value was unescaped,
- the browser parsed it as markup,
- and the sink was executable.
Later, during coordinated disclosure, the maintainer simplified the reproducer further:
That version is better because it isolates the vulnerable boundary more directly:
- no extra file
- no extra application logic
- just attacker-controlled command-line content entering the report pipeline
Why the Payload Was Chosen
The payload was intentionally simple:
This is not about flashy payloads.
It is a clean execution probe because:
- it requires no external infrastructure
- it is obvious in rendered HTML
- it proves HTML interpretation immediately
- it proves browser-side execution without relying on remote content
For this class of bug, that is enough.
Scope Validation
One report type would already have been enough to justify the issue.
But I wanted to know whether this was isolated or structural.
I confirmed the same behavior in:
- flamegraph reports
- table reports
- flamegraph reports generated with
--no-web
That mattered for two reasons.
First:
It showed the vulnerable sink was reused across multiple HTML outputs.
Second:
It proved the bug was not dependent on external CDN-hosted assets.
--no-web still reproduced the issue, which means the problem was in Memray’s own generated HTML and template handling, not in remote JS behavior.
That made the case much stronger.
Why This Was Classified as Low Severity
The maintainer’s main concern was practical attacker control.
That is fair.
This is not the kind of issue where an unauthenticated remote attacker hits an exposed HTTP endpoint and gets instant impact.
The exploit condition is narrower:
- attacker influences command-line input
- a victim later opens the generated report in a browser
So the realistic classification was low severity.
That does not make it a weak bug.
Severity is about exploit conditions and likely impact. Validity is about whether the issue is real.
This issue was clearly real:
- attacker-controlled source
- HTML sink
- missing escaping
- actual JavaScript execution
- deterministic fix
That is why it still became a CVE.
Fix Analysis
The fix was minimal and correct.
The maintainer changed:
to:
That is the right fix because it addresses the vulnerable sink directly.
Instead of emitting raw markup like:
the template now emits escaped text:
That preserves the informational value of the command-line field while removing the browser execution path.
The maintainer also reviewed the rest of the template context and concluded that:
- several fields were numeric
- some strings were fully controlled by Memray
- script-bound values were rendered with
|tojsonSo the issue was correctly narrowed tometadata.command_line.
That is exactly the kind of fix review you want in a real disclosure.
Disclosure
This was reported privately through GitHub Security Advisories.
The maintainers:
- validated the issue
- agreed it was their bug to fix
- simplified the reproducer
- patched the vulnerable sink
- released the fix in 1.19.2
- requested a CVE
- published the advisory
The issue was assigned: CVE-2026-32722
What This Bug Actually Teaches
The key lesson here is simple:
metadata is not automatically trusted just because it looks operational.
- A command line feels harmless.
- A report modal feels harmless.
- A local HTML file feels harmless.
None of that matters once attacker-controlled content crosses into browser-rendered output without escaping.
The moment a tool emits HTML, it needs to be treated like an HTML-producing application.
That is the real takeaway.
Key Points
- HTML report generators are security surfaces
- developer tooling still needs output encoding discipline
- local report artifacts can contain real XSS sinks
- missing escaping in templates is enough when attacker-controlled metadata reaches the sink
- low severity does not mean low quality
- the right mindset here was trust boundary analysis, not blind fuzzing
Final Words
This vulnerability was not about a clever payload.
It was about identifying the right boundary.
Memray took attacker-influenced command-line metadata and rendered it into generated HTML without escaping it. The browser did the rest.
That is why this became CVE-2026-32722.
Fixed in Memray 1.19.2.

