Back to Blog
high SEVERITY9 min read

How Denial of Service via Adjacent Inline Attribute Blocks Happens in PHP and how to fix it

A high-severity denial-of-service vulnerability (GHSA-g2gp-3wwq-f4ph) was discovered in `league/commonmark`, a popular PHP Markdown parsing library. The flaw allows an attacker to craft Markdown input containing adjacent inline attribute blocks that trigger catastrophic processing, potentially exhausting server resources. Upgrading from version 2.7.1 to 2.9.0 resolves the issue by hardening how the parser handles these malformed constructs.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

GHSA-g2gp-3wwq-f4ph is a high-severity Denial of Service vulnerability in the PHP library `league/commonmark` (CWE-400: Uncontrolled Resource Consumption). In versions prior to 2.9.0, the inline attribute block parser can be triggered into excessive computation when processing specially crafted Markdown containing adjacent `{...}` attribute blocks. The fix is to upgrade `league/commonmark` to version 2.9.0, which tightens the parsing logic so malformed adjacent attribute blocks no longer cause runaway processing. In `composer.json`, the version constraint was changed from `^2.1` to the pinned `2.9.0` to ensure the vulnerable range is excluded.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade league/commonmark from 2.7.1 to 2.9.0, which fixes the unbounded parsing loop in the attribute block handler
riskAttackers can submit crafted Markdown to exhaust CPU/memory and take down the application
languagePHP
root causeThe inline attribute block parser in league/commonmark did not bound or short-circuit processing of adjacent `{...}` blocks, leading to excessive resource consumption
vulnerabilityDenial of Service via Adjacent Inline Attribute Blocks

How Denial of Service via Adjacent Inline Attribute Blocks Happens in PHP and how to fix it


Vulnerability at a Glance

Field Detail
Vulnerability Denial of Service via Adjacent Inline Attribute Blocks
CWE CWE-400 — Uncontrolled Resource Consumption
Language PHP
Risk Attacker-supplied Markdown exhausts server CPU/memory
Root Cause Unbounded processing of consecutive {...} inline attribute blocks
Fix Upgrade league/commonmark from 2.7.12.9.0

Quick Answer

GHSA-g2gp-3wwq-f4ph is a high-severity DoS in league/commonmark < 2.9.0 (CWE-400). The inline attribute block parser performs unbounded work when it encounters adjacent {...} blocks in Markdown, allowing any user who can submit Markdown input to hang or crash the server. Fix: pin league/commonmark to 2.9.0 in composer.json and regenerate composer.lock.


Introduction

The composer.lock file in this PHP project recorded league/commonmark at version 2.7.1 — and locked inside that version was a ticking clock. The league/commonmark library is one of the most widely used Markdown-to-HTML parsers in the PHP ecosystem, powering content rendering in CMS platforms, documentation sites, and SaaS applications. When it parses Markdown, it walks the document tree and applies inline parsers to handle special syntax like links, emphasis, and — crucially — inline attribute blocks written as {.class #id key=value}.

The vulnerability tracked as GHSA-g2gp-3wwq-f4ph lives in exactly that attribute-block parsing path. When the parser encounters multiple attribute blocks placed adjacent to one another — think {.foo}{.bar}{.baz} repeated many times — it enters a processing loop that does not properly bound its work. The result is that a tiny, carefully constructed Markdown payload can drive CPU utilization to 100% and hold it there, effectively denying service to every other user of the application.

This post walks through what the vulnerability is, how it can be exploited, and exactly what changed in the composer.json and composer.lock to close it.


The Vulnerability Explained

What Are Inline Attribute Blocks?

league/commonmark supports an extended Markdown syntax (from the CommonMark Attributes extension) that lets authors attach HTML attributes to inline elements using curly-brace blocks:

This is *emphasized*{.highlight} text.

The {.highlight} block tells the renderer to add class="highlight" to the <em> tag. This is a legitimate, useful feature. The parser scans the inline content character by character, and when it sees {, it tries to consume an attribute block.

Where the Problem Lives

The vulnerability arises when multiple attribute blocks appear consecutively with no intervening content:

text{.a}{.b}{.c}{.d}{.e}...{.z}

In league/commonmark 2.7.1, the inline attribute block parser does not correctly short-circuit or bound the number of consecutive attribute blocks it will attempt to process in a single pass. Each {...} block triggers a new parsing attempt, and the interaction between the parser's backtracking logic and the sequential block structure causes the time complexity to grow super-linearly — potentially exponentially — with the number of adjacent blocks.

This is a classic ReDoS-adjacent pattern: not a regex catastrophic backtrack per se, but a parsing loop whose work grows unboundedly with attacker-controlled repetition in the input.

A Concrete Attack Payload

An attacker who can submit any Markdown to the application — a comment field, a wiki page, a ticket description, an API endpoint that accepts Markdown — can send something like:

x{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}{.a}

A payload of just a few hundred bytes can cause the PHP process handling the request to spin at 100% CPU for seconds, minutes, or indefinitely — depending on the server's timeout configuration. With a handful of concurrent requests, an attacker can saturate all available PHP workers and render the application completely unavailable to legitimate users.

Real-World Impact for This Application

This project uses league/commonmark directly as a production dependency ("require" in composer.json, not "require-dev"). That means any code path that calls the CommonMark parser on user-supplied content is a potential DoS vector. Given that CommonMark is typically used to render user-generated content — exactly the scenario where untrusted input flows in — the exposure surface is significant.


The Fix

What Changed

Two files were modified: composer.json and composer.lock.

composer.json — Tightening the Version Constraint

Before:

"require": {
    "league/commonmark": "^2.1",
    "rlanvin/php-rrule": "^2.3"
}

After:

"require": {
    "league/commonmark": "2.9.0",
    "rlanvin/php-rrule": "^2.3"
}

The original constraint ^2.1 allowed any version from 2.1.0 up to (but not including) 3.0.0. That range includes every vulnerable 2.x release. By pinning to the exact version 2.9.0, the project guarantees that only the patched release is ever installed — no future composer update can silently pull in a vulnerable version.

Why pin exactly instead of using ^2.9?
Pinning to 2.9.0 is the most conservative choice: it ensures reproducibility and prevents any future 2.x release (which might introduce new issues) from being installed without an explicit, reviewed version bump. This is a deliberate security posture trade-off: slightly more maintenance overhead in exchange for tighter control over the dependency surface.

composer.lock — Recording the Patched Release

The lock file records the resolved package metadata. The key change is the version and Git reference for league/commonmark:

Before:

{
    "name": "league/commonmark",
    "version": "2.7.1",
    "source": {
        "type": "git",
        "url": "https://github.com/thephpleague/commonmark.git",
        "reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
    },
    "dist": {
        "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/10732241927d3971d28e7ea7b5712721fa2296ca",
        "reference": "10732241927d3971d28e7ea7b5712721fa2296ca"
    }
}

After:

{
    "name": "league/commonmark",
    "version": "2.9.0",
    "source": {
        "type": "git",
        "url": "https://github.com/thephpleague/commonmark.git",
        "reference": "5703d83ba3da3b2e356a5fedc848ed6d8ffb6529"
    },
    "dist": {
        "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/5703d83ba3da3b2e356a5fedc848ed6d8ffb6529",
        "reference": "5703d83ba3da3b2e356a5fedc848ed6d8ffb6529"
    }
}

The content-hash of the lock file also changed from a961e0ba61e216076bb4c0bd520eb32f to 3add01dd708f6be5dfd7f4293205f1d8, reflecting the updated dependency tree. Both the source reference and the dist URL now point to the 2.9.0 commit (5703d83ba3da3b2e356a5fedc848ed6d8ffb6529), which contains the upstream fix for the adjacent inline attribute block parsing logic.

Why This Fix Works

The league/commonmark 2.9.0 release patches the internal inline attribute block parser to correctly limit the number of consecutive attribute blocks it will process, and/or fixes the backtracking behavior so that processing time remains linear (or at worst polynomial with a low exponent) rather than exponential with respect to the number of adjacent blocks. The fix is contained entirely within the library — no application-level code changes are needed. Valid Markdown with a reasonable number of attribute blocks continues to render correctly; only the pathological "many adjacent blocks" case is bounded.


Prevention & Best Practices

1. Keep Dependency Constraints Tight

The original constraint ^2.1 was too permissive. It allowed the project to silently run on any vulnerable 2.x release. Best practice for production PHP applications:

  • Use exact pins (2.9.0) or narrow ranges (>=2.9.0 <2.10.0) for security-sensitive dependencies.
  • Commit composer.lock to version control and treat changes to it as security-relevant events.
  • Run composer audit (available since Composer 2.4) in CI to catch known-vulnerable packages automatically.

2. Scan Dependencies in CI/CD

Tools that would have caught this before it reached production:

  • Trivy — detected this exact issue (as noted in the PR, scanner: trivy).
  • composer audit — built into Composer, checks against the PHP Security Advisories Database.
  • Dependabot or Renovate — automated PRs when new versions with security fixes are released.
  • Snyk or FOSSA — commercial SCA tools with broader advisory databases.

Add at least one of these to your CI pipeline so vulnerable dependencies are flagged before they reach staging or production.

3. Never Trust User-Supplied Markdown Without Rate Limiting

Even with a patched parser, rendering user-supplied Markdown is computationally non-trivial. Apply defense in depth:

  • Rate-limit Markdown rendering endpoints.
  • Set PHP max_execution_time to a low value for rendering workers.
  • Limit input size — reject Markdown payloads above a reasonable byte threshold before they reach the parser.
  • Sandbox rendering — consider rendering Markdown in a separate process or queue worker so a DoS attack cannot take down the main application process pool.

4. Understand the Attack Surface of Parsing Libraries

Parsing libraries — Markdown, XML, JSON, CSV — are a common source of DoS vulnerabilities because they must handle adversarial input by design. When evaluating a parsing library:

  • Check its security advisory history.
  • Look for evidence of fuzzing or adversarial testing in the project.
  • Prefer libraries that have explicit policies on input size limits.

5. Relevant Standards


Key Takeaways

  • Adjacent {...} attribute blocks are the trigger — the vulnerability is not in Markdown generally, but specifically in the inline attribute block extension of league/commonmark. Any application using this extension with user input was exposed.
  • ^2.1 in composer.json was the enabling condition — the overly broad semver range allowed the vulnerable 2.7.1 to be installed and stay installed. Pinning to 2.9.0 closes this gap.
  • The composer.lock commit hash matters — the change from reference 10732241927d3971d28e7ea7b5712721fa2296ca (2.7.1) to 5703d83ba3da3b2e356a5fedc848ed6d8ffb6529 (2.9.0) is the concrete artifact of the fix; reviewing lock file diffs is a meaningful security activity.
  • DoS via parsing is cheap for attackers — a payload of a few hundred bytes can saturate a server. The asymmetry between attacker cost and defender cost makes this class of bug especially dangerous in public-facing applications.
  • composer audit in CI would have caught this — a single pipeline step would have flagged league/commonmark 2.7.1 as vulnerable before any deployment.

How Orbis AppSec Detected This

  • Source: User-supplied Markdown content submitted to any application endpoint that passes input to league/commonmark's parser.
  • Sink: The inline attribute block parser within league/commonmark 2.7.1, invoked whenever the CommonMark parser processes inline content containing {...} blocks.
  • Missing control: No bound on the number of consecutive inline attribute blocks processed in a single parse pass; no input size or complexity limit enforced before the parser is invoked.
  • CWE: CWE-400 — Uncontrolled Resource Consumption
  • Fix: composer.json version constraint for league/commonmark was changed from ^2.1 to the exact pin 2.9.0, and composer.lock was regenerated to record the patched release at Git reference 5703d83ba3da3b2e356a5fedc848ed6d8ffb6529.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

GHSA-g2gp-3wwq-f4ph is a high-severity reminder that parsing libraries are attack surface. The league/commonmark inline attribute block parser, when presented with a stream of adjacent {...} blocks, consumed unbounded resources — turning a few hundred bytes of Markdown into a server-killing payload. The fix is straightforward: upgrade to 2.9.0, pin the version, and add dependency scanning to CI so the next advisory doesn't linger in your lock file.

The broader lesson is that semver ranges like ^2.1 trade security predictability for convenience. For production dependencies that handle untrusted input — parsers, serializers, template engines — tighter version constraints and automated advisory scanning are not optional hardening; they are baseline hygiene.


References

Frequently Asked Questions

What is a Denial of Service via adjacent inline attribute blocks?

It is an attack where a specially crafted Markdown document containing multiple consecutive `{...}` attribute blocks causes the parser to consume excessive CPU or memory, making the application unresponsive.

How do you prevent this DoS vulnerability in PHP Markdown parsing?

Upgrade league/commonmark to 2.9.0 or later, and always pin or tightly constrain third-party dependency versions so vulnerable ranges cannot be silently installed.

What CWE is this vulnerability?

CWE-400 — Uncontrolled Resource Consumption, because the parser performs unbounded work proportional to attacker-controlled input.

Is input sanitization enough to prevent this vulnerability?

Not on its own — the root cause is in the parser's internal logic, not missing sanitization at the application layer. The library itself must be patched to correctly bound processing of adjacent attribute blocks.

Can static analysis detect this vulnerability?

Dependency-scanning tools like Trivy, Dependabot, or Snyk can detect known-vulnerable versions of league/commonmark. Static analysis of the parser's internal regex or loop logic can also surface unbounded-backtracking patterns.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #34

Related Articles

high

How Denial of Service via Unbounded Intermediate Arrays happens in JavaScript and how to fix it

CVE-2026-69152 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package (versions prior to 1.1.18/2.1.4/3.0.6/5.0.9) that allows attackers to crash a Node.js application by crafting glob patterns that generate unbounded intermediate arrays, effectively bypassing the earlier CVE-2026-14257 mitigation. The fix upgrades `brace-expansion` from 1.1.14 to 1.1.18 in `frontend/package-lock.json`, closing the bypass and restoring safe memory bounds during pattern expansion.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability (GHSA-5p4m-2wfm-xmqj) was discovered in js-yaml affecting both the 3.x and 4.x branches, where parsing YAML documents containing `!!omap` tags triggers quadratic CPU consumption. The fix upgrades js-yaml from `^4.1.1` to `5.2.0` in the project's GitHub Actions workflow dependencies, closing the attack surface for any untrusted YAML input processed by CI/CD tooling.

critical

How Missing Rate Limiting happens in Express.js and how to fix it

Two public API endpoints in `server.js` — `/api/health` and `/api/contact` — were exposed without any rate limiting middleware, allowing attackers to exhaust server resources or spam an SMTP server with unlimited requests. The fix adds rate limiting to both endpoints, with stricter controls on the resource-intensive `/api/contact` route that triggers email sending operations. This change closes a directly exploitable denial-of-service vector in a production web service.

high

How Denial of Service via Specific Input Sequence happens in JavaScript (marked) and how to fix it

CVE-2026-41680 is a high-severity Denial of Service vulnerability in the marked Markdown parsing library, affecting versions prior to 18.0.2. By supplying a crafted input sequence to the parser, an attacker can cause the application to hang or exhaust resources, making the frontend unavailable. Upgrading marked from 18.0.0 to 18.0.2 in both `package.json` and `package-lock.json` closes the vulnerability without affecting valid Markdown rendering.

high

How Quadratic CPU Consumption happens in JavaScript YAML parsing and how to fix it

A high-severity denial-of-service vulnerability in js-yaml (GHSA-5p4m-2wfm-xmqj) caused quadratic CPU consumption when resolving `!!omap` YAML types in both the 3.x and 4.x branches. The fix upgrades js-yaml from 3.14.2 to 3.15.1 and from 4.1.1 to 4.3.1, eliminating the algorithmic complexity exploit while leaving all valid YAML inputs unaffected.

high

How Denial of Service via Unbounded Data Happens in JavaScript and how to fix it

CVE-2025-58754 is a high-severity Denial of Service vulnerability in the popular axios HTTP client library, caused by the absence of a data size check on incoming response or request payloads. An attacker who can influence the size of data processed by axios could exhaust server memory or CPU, bringing down dependent Node.js applications. The fix upgrades axios from version 1.8.4 to 1.18.0, closing the unbounded data processing path.