league/commonmark Attributes extension XSS boundary¶
Source: GitHub Security Advisories REST fallback, updated 2026-05-28.
This advisory is durable because Markdown renderers are common bug-bounty surfaces, and this case shows a reusable sanitizer-order bypass: raw HTML can be stripped and unsafe links blocked, but a post-parse attributes extension can still add executable event-handler attributes to otherwise safe elements.
What changed¶
- league/commonmark Attributes extension XSS — GHSA-3527-qv2q-pfvx / CVE-2025-46734: vulnerable Composer package
league/commonmark >=1.5.0 <2.7.0lets Markdown authors attach arbitrary HTML attributes with curly-brace syntax whenAttributesExtensionis enabled. Even withhtml_input: 'strip'andallow_unsafe_links: false, payloads such as![](){onerror=alert(1)}can render as an<img>with an executableonerrorhandler. Version2.7.0blockson*attributes by default, adds an explicit attribute allowlist, and makes manually addedhref/srcattributes respectallow_unsafe_links.
Operator triage¶
- Search PHP/Composer inventories for
league/commonmarkversions before2.7.0. - Confirm whether
League\CommonMark\Extension\Attributes\AttributesExtensionis registered directly, through framework bundles, CMS plugins, documentation portals, ticketing/comment systems, knowledge bases, or static-site/editor preview features. - Identify low-trust Markdown inputs: comments, profiles, descriptions, README/import previews, support tickets, docs contributions, email templates, AI-generated reports, CMS blocks, and file uploads rendered as Markdown.
- Record the renderer configuration:
html_input,allow_unsafe_links, enabled extensions, post-render sanitizer, CSP, output origin, cache behavior, and victim roles that view rendered content. - Treat configurations that strip raw HTML as still testable when attributes syntax is enabled; the bypass lands after the normal raw-HTML stripping decision.
Replayable validation boundaries¶
- Safe event-attribute marker: in an authorized test field, submit an inert image attribute payload such as
![](){onerror="console.log('skillz-commonmark-marker')"}or a non-exfiltrating callback to a tester-owned endpoint. Expected safe result: theonerrorattribute is removed/escaped, the element is rendered inert, or the content is isolated on a no-credential origin. Vulnerable result: the rendered HTML contains an executableonerrorattribute in the application origin. - Link and source policy drift: test whether manually added attributes can override renderer policy with benign
href/srcvariants, includingjavascript:,data:, protocol-relative, empty, and mixed-case schemes. Do not use credential-stealing payloads; the proof is the policy-bypassing rendered attribute. - Surface propagation check: if Markdown is cached or re-rendered asynchronously, verify whether the dangerous attribute persists into previews, public pages, notification emails, exports, search snippets, RSS feeds, or mobile/webview clients.
- Victim-role boundary: demonstrate impact with a disposable viewer account matching the weakest role needed to trigger rendering. For admin-only views, prove script execution with a harmless marker rather than reading privileged data.
Reporting heuristics¶
- Include the affected package version, extension registration path, renderer configuration, input location, rendered HTML, output origin, CSP posture, and required victim role.
- Explain why standard Markdown hardening was insufficient: raw HTML stripping and unsafe-link blocking did not constrain attributes introduced by the Attributes extension.
- Show whether the same primitive reaches stored views, previews, exports, notifications, or third-party embeds.
- If impact depends on same-origin application APIs, describe the reachable authenticated actions or data classes without harvesting real user data.
- Keep validation scoped to authorized content and accounts; use inert markers, tester-owned callbacks, and disposable objects only.
Notes on skipped items from this scan¶
- SpiceDB datastore DSN leakage in startup logs (
GHSA-jf4f-rr2c-9m58/ CVE-2026-40091) remained processed from the prior pass as credential-log hygiene already represented by existing secret-logging guidance, not a standalone Skillz operator playbook.