Skip to the content.

mljar/mercury — security scan

Repository: mljar/mercury Commit scanned: b02bca0 Scan date: 2026-09-11 Disclosure status: reported privately — detail withheld pending maintainer response

Summary

Severity Count
Critical 1
High 66
Medium 71
Low 0
Info 2

Total findings: 140 (1 real after curation, reported privately)

Top findings

Withheld. Mercury has GitHub private vulnerability reporting enabled, which is a signalled preference for a private channel, so the report went there (GHSA-8hq5-94w5-f4ff, filed 2026-09-11, currently in triage) rather than into a public issue — the more so because the finding is a working, unauthenticated request against the exact boundary the product advertises, and a public issue would be a copy-paste recipe against live deployments before a fix exists. What can be said without handing anyone that recipe:

The thing worth saying publicly is how it was found, because no scanner emitted it. The firewall is its own specification. A deny-list of endpoints that a project has deliberately blocked is a list of things the maintainers already know are (a) reachable by anonymous users and (b) dangerous. That single fact — they had to block authenticated endpoints, therefore @authenticated does not stop anonymous callers in this mode — turns the review into a mechanical sweep: enumerate every request handler the app registers, subtract the ones on the block-list and the ones replaced by safe custom versions, and look hard at whatever remains. What remained was one listing route that its own sibling shows how to write safely. The report ships with a runnable reproduction that has a positive control (the legitimate served notebook still lists), a negative control (the confined read path still returns 404 on traversal), and the firewall’s own blocked endpoints as a third control (all 403, proving the mode and that the decorator is not the gate).

Patterns observed

140 findings, and the one Critical is in the documentation website. Trivy’s single Critical — an Astro AVIF-optimization RCE — lives in docs/package-lock.json, and the bulk of the 66 High and 71 Medium are npm advisories in yarn.lock and docs/ (vega, form-data, and friends). Mercury ships to its users as a Python package; the docs/ tree and the root JavaScript lockfiles are the Astro documentation site and the JupyterLab extension build. None of those advisories are reachable by a person running pip install mercury and serving a notebook. This is the reachability-blindness pattern in its purest form: a software-composition scanner faithfully scores every lockfile it can find, and on a repository whose product is Python but whose repository is mostly a docs website, the headline severity describes the website. The useful first act of curation here was not reading code — it was separating the shipped artifact from the things that merely live in the same repo.

On code this careful, the only findings left are self-inconsistencies. The parts of Mercury that matter are well made. The execution path never runs client-supplied code: the browser sends a cell identifier and the server resolves the source from its own server-side manifest, rejecting stale revisions and unknown actions. The contents manager confines path traversal. The firewall blocks terminals, arbitrary contents, shutdown, the file browser, and the Lab UI. Notebook rendering is sanitized. When a codebase has already done everything on the generic checklist, the checklist is worthless, and the only class of finding that survives is where the project disagrees with itself — one route written to a weaker standard than its sibling two files over. That is the same shape as most of the real findings in this series, and it is the shape no single-file rule can see.

“Simple and safe” is a threat model, and it should be tested as one. Mercury’s own welcome copy tells the visitor that “everything is designed to be simple and safe.” That is a promise about a specific deployment: an app anyone can open, with no login. The firewall exists to keep that promise. A finding here is not “this endpoint has a bug” in the abstract — it is “this endpoint breaks the specific promise the firewall is there to keep,” which is exactly why it belongs in a private report to the people who made the promise rather than in a public issue.

Notes on the tool

Disclosure timeline

Reproduce

git clone https://github.com/mljar/mercury /tmp/scan-target
python scanner/run_scan.py --repo /tmp/scan-target --reports-dir ./reports/mljar-mercury --min-severity medium --ignore-samples