CVE-2021-28834
Description
Kramdown before 2.3.1 does not restrict Rouge formatters to the Rouge::Formatters namespace, and thus arbitrary classes can be instantiated.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Kramdown before 2.3.1 allows arbitrary class instantiation via the Rouge syntax highlighter formatter option, leading to potential remote code execution.
Root
Cause
The vulnerability lies in the syntax highlighter of Kramdown when using Rouge. The Rouge::Formatters.const_get(formatter) call did not restrict lookups to the Rouge::Formatters namespace, allowing an attacker to instantiate arbitrary classes by providing a string such as 'CSV' instead of a valid formatter class [2][3]. This bypasses the intended restriction introduced in commit ff0218a which added the false parameter to const_get to prevent inheritance-based lookups [3].
Exploitation
An attacker can trigger the vulnerability by supplying a specially crafted formatter option when rendering Markdown content with syntax highlighting. This can be done through user-supplied Markdown input in applications that use Kramdown, such as GitLab [4]. The attacker does not require special privileges; any vector that passes options to the syntax highlighter can be used [3].
Impact
By instantiating arbitrary classes, an attacker may achieve remote code execution. If a class with a dangerous constructor (e.g., one that executes system commands) is instantiated, the attacker could execute arbitrary code on the server [1][4]. This could lead to full compromise of the affected system.
Mitigation
The issue is fixed in Kramdown version 2.3.1, which restricts formatter classes to the Rouge::Formatters namespace and uses const_get with inheritance disabled [2][3]. GitLab also patched the vulnerability in a separate commit [4]. Users should upgrade to Kramdown 2.3.1 or apply the relevant patch.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
kramdownRubyGems | >= 1.16.0, < 2.3.1 | 2.3.1 |
Affected products
4- Kramdown/Kramdowndescription
- ghsa-coords3 versionspkg:gem/kramdownpkg:rpm/opensuse/ruby3.2-rubygem-kramdown&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/rubygem-kramdown&distro=openSUSE%20Tumbleweed
>= 1.16.0, < 2.3.1+ 2 more
- (no CPE)range: >= 1.16.0, < 2.3.1
- (no CPE)range: < 2.4.0-1.8
- (no CPE)range: < 2.4.0-1.1
Patches
2d6a1cbcb2caaRestrict Rouge formatters to Rouge::Formatters namespace
2 files changed · +12 −8
lib/kramdown/converter/syntax_highlighter/rouge.rb+1 −1 modified@@ -70,7 +70,7 @@ def self.formatter_class(opts = {}) when Class formatter when /\A[[:upper:]][[:alnum:]_]*\z/ - ::Rouge::Formatters.const_get(formatter) + ::Rouge::Formatters.const_get(formatter, false) else # Available in Rouge 2.0 or later ::Rouge::Formatters::HTMLLegacy
test/test_files.rb+11 −7 modified@@ -21,16 +21,20 @@ end # custom formatter for tests - class RougeHTMLFormatters < Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class + module Rouge + module Formatters + class RougeHTMLFormatters < Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class - tag 'rouge_html_formatters' + tag 'rouge_html_formatters' - def stream(tokens, &b) - yield %(<div class="custom-class">) - super - yield %(</div>) - end + def stream(tokens, &b) + yield %(<div class="custom-class">) + super + yield %(</div>) + end + end + end end rescue LoadError, SyntaxError, NameError end
ff0218aefcf0Allow for passing Rouge formatter as a `String`
4 files changed · +22 −17
doc/syntax_highlighter/rouge.page+2 −2 modified@@ -25,8 +25,8 @@ formatter : A custom Rouge formatter class that should be used instead of the following default formatter (Rogue 1.x: `Rouge::Formatters::HTML` / Rogue 2.x: `Rouge::Formatters::HTMLLegacy`). - Note that setting this key only makes sense using Ruby code because the value needs to be a class - object! + If this setting is a string, it needs to contain the name of a constant under the + `Rouge::Formatters` namespace. disable : If set to `true`, highlighting with Rouge will be disabled.
lib/kramdown/converter/syntax_highlighter/rouge.rb+16 −9 modified@@ -17,14 +17,6 @@ module Rouge # Highlighting via Rouge is available if this constant is +true+. AVAILABLE = true - - begin - # Rouge::Formatters::HTMLLegacy is available on Rouge 2.0 or later - FORMATTER_CLASS = ::Rouge::Formatters::HTMLLegacy - rescue NameError - # Fallbacks to Rouge 1.x formatter if Rouge::Formatters::HTMLLegacy is not available - FORMATTER_CLASS = ::Rouge::Formatters::HTML - end rescue LoadError, SyntaxError AVAILABLE = false # :nodoc: end @@ -35,7 +27,7 @@ def self.call(converter, text, lang, type, call_opts) lexer = ::Rouge::Lexer.find_fancy(lang || opts[:default_lang], text) return nil if opts[:disable] || !lexer opts[:css_class] ||= 'highlight' # For backward compatibility when using Rouge 2.0 - formatter = (opts.fetch(:formatter, FORMATTER_CLASS)).new(opts) + formatter = formatter_class(opts).new(opts) formatter.format(lexer.lex(text)) end @@ -62,6 +54,21 @@ def self.prepare_options(converter) cache[:block] = opts.merge(block_opts) end + def self.formatter_class(opts = {}) + case formatter = opts[:formatter] + when Class + formatter + when /\A[[:upper:]][[:alnum:]_]*\z/ + ::Rouge::Formatters.const_get(formatter) + else + # Available in Rouge 2.0 or later + ::Rouge::Formatters::HTMLLegacy + end + rescue NameError + # Fallback to Rouge 1.x + ::Rouge::Formatters::HTML + end + end end
test/testcases/block/06_codeblock/rouge/multiple.options+1 −1 modified@@ -1,4 +1,4 @@ :syntax_highlighter: rouge :syntax_highlighter_opts: default_lang: ruby - formatter: !ruby/class 'RougeHTMLFormatters' + formatter: RougeHTMLFormatters
test/test_files.rb+3 −5 modified@@ -15,14 +15,12 @@ begin require 'kramdown/converter/syntax_highlighter/rouge' - class Kramdown::Converter::SyntaxHighlighter::Rouge::FORMATTER_CLASS - def format(tokens, &b) - super.sub(/<\/code><\/pre>\n?/, "</code></pre>\n") - end + Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class.send(:define_method, :format) do |tokens, &b| + super(tokens, &b).sub(/<\/code><\/pre>\n?/, "</code></pre>\n") end # custom formatter for tests - class RougeHTMLFormatters < Kramdown::Converter::SyntaxHighlighter::Rouge::FORMATTER_CLASS + class RougeHTMLFormatters < Kramdown::Converter::SyntaxHighlighter::Rouge.formatter_class tag 'rouge_html_formatters' def stream(tokens, &b)
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- github.com/advisories/GHSA-52p9-v744-mwjjghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/NJCJVYHPY6LNUFM6LYZIAUIYOMVT5QGV/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/S3BBLUIDCUUR3NEE4NJLOCCAV3ALQ3O6/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/SYOLQKFL6IJCQLBXV34Z4TI4O54GESPR/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2021-28834ghsaADVISORY
- www.debian.org/security/2021/dsa-4890ghsavendor-advisoryx_refsource_DEBIANWEB
- about.gitlab.com/releases/2021/03/17/security-release-gitlab-13-9-4-released/ghsaWEB
- github.com/gettalong/kramdown/compare/REL_2_3_0...REL_2_3_1ghsax_refsource_MISCWEB
- github.com/gettalong/kramdown/pull/708ghsax_refsource_MISCWEB
- github.com/stanhu/kramdown/commit/d6a1cbcb2caa2f8a70927f176070d126b2422760ghsaWEB
- github.com/stanhu/kramdown/commit/ff0218aefcf00cd5a389e17e075d36cd46d011e2ghsaWEB
- gitlab.com/gitlab-org/gitlab/-/commit/179329b5c3c118924fb242dc449d06b4ed6ccb66ghsax_refsource_MISCWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/NJCJVYHPY6LNUFM6LYZIAUIYOMVT5QGVghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/S3BBLUIDCUUR3NEE4NJLOCCAV3ALQ3O6ghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/SYOLQKFL6IJCQLBXV34Z4TI4O54GESPRghsaWEB
News mentions
0No linked articles in our index yet.