| Reporter | Title | Published | Views | Family All 20 |
|---|---|---|---|---|
| CVE-2026-32722 | 18 Mar 202621:25 | – | attackerkb | |
| CVE-2026-32722 | 13 Mar 202622:01 | – | circl | |
| Memray 跨站脚本漏洞 | 18 Mar 202600:00 | – | cnnvd | |
| CVE-2026-32722 | 18 Mar 202621:25 | – | cve | |
| CVE-2026-32722 Memray-generated HTML reports vulnerable to Stored XSS via unescaped command-line metadata | 18 Mar 202621:25 | – | cvelist | |
| CVE-2026-32722 | 18 Mar 202621:25 | – | debiancve | |
| Stored XSS in Memray-generated HTML reports via unescaped command-line metadata | 16 Mar 202616:41 | – | github | |
| CVE-2026-32722 | 18 Mar 202622:16 | – | nvd | |
| CVE-2026-32722 Memray-generated HTML reports vulnerable to Stored XSS via unescaped command-line metadata | 18 Mar 202621:25 | – | osv | |
| DEBIAN-CVE-2026-32722 | 18 Mar 202622:16 | – | osv |
# CVE-2026-32722
Bloomberg Memray’s Stored XSS via Unescaped Command-Line Metadata
## 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**.
**Project:** [Memray on GitHub](https://github.com/bloomberg/memray)
**Advisory:** [GHSA-r5pr-887v-m2w9 /CVE-2026-32722](https://github.com/bloomberg/memray/security/advisories/GHSA-r5pr-887v-m2w9)
<img width="840" height="760" alt="photo0" src="https://github.com/user-attachments/assets/2c043ef1-8ea7-4991-b577-6e857487d7e0" />
---
## 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:
```python title="src/memray/reporters/templates/__init__.py"
def get_render_environment() -> jinja2.Environment:
loader = jinja2.PackageLoader("memray.reporters")
env = jinja2.Environment(loader=loader)
```
the Jinja environment is created without autoescape.
Then in:
```html
Command line: <code>{{ metadata.command_line }}</code><br>
```
`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:
```bash
cat > victim.py <<'PY'
x = [b"A" * 1024 for _ in range(1000)]
print("done")
PY
python -m memray run -o poc.bin victim.py '<img src=x onerror=alert(1)>'
python -m memray flamegraph -o poc.html poc.bin
```
The generated HTML contained raw attacker-controlled markup:
```html
Command line: <code>~/memray/src/memray/__main__.py run -o poc.bin victim.py <img src=x onerror=alert(1)></code><br>
```
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:
```bash
python -m memray run -o poc.bin -c '# <img src=x onerror=alert(1)>'
```
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:
```html
<img src=x onerror=alert(1)>
```
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:
```html
{{ metadata.command_line }}
```
to:
```html
{{ metadata.command_line|e }}
```
That is the right fix because it addresses the vulnerable sink directly.
Instead of emitting raw markup like:
```html
<img src=x onerror=alert(1)>
```
the template now emits escaped text:
```html
<img src=x onerror=alert(1)>
```
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 `|tojson`
So the issue was correctly narrowed to `metadata.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**.
<img width="840" height="560" alt="photo0" src="https://github.com/user-attachments/assets/a9751b5f-a614-4501-b257-56b00f0693d0" />Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation