VYPR
High severityNVD Advisory· Published Mar 19, 2021· Updated Aug 3, 2024

CVE-2021-28834

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.

PackageAffected versionsPatched versions
kramdownRubyGems
>= 1.16.0, < 2.3.12.3.1

Affected products

4

Patches

2
d6a1cbcb2caa

Restrict Rouge formatters to Rouge::Formatters namespace

https://github.com/stanhu/kramdownStan HuMar 14, 2021via ghsa
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
    
ff0218aefcf0

Allow for passing Rouge formatter as a `String`

https://github.com/stanhu/kramdownAlpha ChenSep 11, 2017via ghsa
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

News mentions

0

No linked articles in our index yet.