Command Injection vulnerability in asciidoctor-include-ext
Description
Asciidoctor-include-ext before 0.4.0 allows command injection when rendering user-supplied AsciiDoc, even if allow-uri-read is disabled.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Asciidoctor-include-ext before 0.4.0 allows command injection when rendering user-supplied AsciiDoc, even if allow-uri-read is disabled.
Vulnerability
Asciidoctor-include-ext is a reimplementation of Asciidoctor's standard include processor as an extension. Versions prior to 0.4.0 contain a command injection vulnerability in the read_lines and resolve_target_path methods when handling include directives with a target of type URI. The flaw exists because the extension previously used a target_uri? check that allowed arbitrary URIs, including those that could be crafted to execute system commands via Kernel.open, even when the allow-uri-read attribute was not set. This affects all versions before the fix commit c7ea001a597c7033575342c51483dab7b87ae155 [2]. The issue arises from improper validation of the target in the include_allowed? and resolve_target_path functions [2].
Exploitation
An attacker who can supply crafted AsciiDoc markup for rendering (e.g., via a documentation or blog platform that uses Asciidoctor-include-ext) can exploit this by including a specially crafted target URI. The attacker does not need the allow-uri-read attribute enabled; the vulnerable code path treats any URI as valid. The attacker must have the ability to inject include::[] directives with a target that is interpreted as a URI. The target_uri? method in the vulnerable versions did not restrict schemes, allowing protocols such as | (pipe) or other mechanisms that result in command execution [2]. The fix replaced target_uri? with target_http?, which restricts allowed schemes [2].
Impact
Successful exploitation allows an attacker to execute arbitrary system commands on the host operating system with the privileges of the process running the AsciiDoc renderer. This can lead to full compromise of the server, including data exfiltration, installation of malware, and lateral movement within the network [1][4]. The impact is critical as it requires no additional authentication beyond the ability to supply content [1].
Mitigation
The vulnerability is fixed in version 0.4.0 of the asciidoctor-include-ext gem [4]. Users should update to version 0.4.0 or later immediately. The fix was implemented in commit c7ea001a597c7033575342c51483dab7b87ae155 [2]. There is no known workaround other than upgrading, as the vulnerable code path is active by default. The CVE is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog as of the publication date.
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 |
|---|---|---|
asciidoctor-include-extRubyGems | < 0.4.0 | 0.4.0 |
Affected products
2- Range: < 0.4.0
Patches
2cbaccf3de533Make #read_lines code more robust, avoid using IO.open directly
1 file changed · +10 −4
lib/asciidoctor/include_ext/include_processor.rb+10 −4 modified@@ -113,10 +113,16 @@ def resolve_target_path(target, reader) # the line number. If `nil` is given, all lines are passed. # @return [Array<String>] an array of read lines. def read_lines(path, selector) - if selector - IO.foreach(path).select.with_index(1, &selector) - else - URI.open(path, &:read) + # IO.open is deliberately not used directly to avoid potential security risks. + # TODO: Get rid of 'open-uri' (URI.open). + io = target_http?(path) ? URI : File + + io.open(path) do |f| + if selector + f.each.select.with_index(1, &selector) + else + f.read + end end end
c7ea001a597cFix command injection vulnerability
2 files changed · +28 −9
lib/asciidoctor/include_ext/include_processor.rb+14 −9 modified@@ -1,6 +1,7 @@ # frozen_string_literal: true require 'logger' require 'open-uri' +require 'uri' require 'asciidoctor/include_ext/version' require 'asciidoctor/include_ext/reader_ext' @@ -86,15 +87,15 @@ def include_allowed?(target, reader) return false if doc.safe >= ::Asciidoctor::SafeMode::SECURE return false if doc.attributes.fetch('max-include-depth', 64).to_i < 1 - return false if target_uri?(target) && !doc.attributes.key?('allow-uri-read') + return false if target_http?(target) && !doc.attributes.key?('allow-uri-read') true end # @param target (see #process) # @param reader (see #process) # @return [String, nil] file path or URI of the *target*, or `nil` if not found. def resolve_target_path(target, reader) - return target if target_uri? target + return target if target_http? target # Include file is resolved relative to dir of the current include, # or base_dir if within original docfile. @@ -106,16 +107,16 @@ def resolve_target_path(target, reader) # Reads the specified file as individual lines, filters them using the # *selector* (if provided) and returns those lines in an array. # - # @param filename [String] path of the file to be read. + # @param path [String] URL or path of the file to be read. # @param selector [#to_proc, nil] predicate to filter lines that should be # included in the output. It must accept two arguments: line and # the line number. If `nil` is given, all lines are passed. # @return [Array<String>] an array of read lines. - def read_lines(filename, selector) + def read_lines(path, selector) if selector - IO.foreach(filename).select.with_index(1, &selector) + IO.foreach(path).select.with_index(1, &selector) else - URI.open(filename, &:read) + URI.open(path, &:read) end end @@ -142,9 +143,13 @@ def unresolved_include!(target, reader) private # @param target (see #process) - # @return [Boolean] `true` if the *target* is an URI, `false` otherwise. - def target_uri?(target) - ::Asciidoctor::Helpers.uriish?(target) + # @return [Boolean] `true` if the *target* is a valid HTTP(S) URI, `false` otherwise. + def target_http?(target) + # First do a fast test, then try to parse it. + target.downcase.start_with?('http://', 'https://') \ + && URI.parse(target).is_a?(URI::HTTP) + rescue URI::InvalidURIError + false end end end
spec/integration_spec.rb+14 −0 modified@@ -138,6 +138,20 @@ should match /let s = SS.empty;;/ should_not match /(?:tag|end)::snippet\[\]/ end + + it 'does not allow execution of system command when allow-uri-read is set' do + options.merge!(attributes: { 'allow-uri-read' => '' }) + given <<~ADOC + :app-name: |cat LICENSE # + \\ + http://test.com + + include::{app-name}[] + ADOC + + should match /unresolved/i + should_not match /The MIT License/ + end + end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-v222-6mr4-qj29ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-24803ghsaADVISORY
- github.com/jirutka/asciidoctor-include-ext/commit/c7ea001a597c7033575342c51483dab7b87ae155ghsax_refsource_MISCWEB
- github.com/jirutka/asciidoctor-include-ext/commit/cbaccf3de533cbca224bf61d0b74e4b84d41d8eeghsax_refsource_MISCWEB
- github.com/jirutka/asciidoctor-include-ext/security/advisories/GHSA-v222-6mr4-qj29ghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/asciidoctor-include-ext/CVE-2022-24803.ymlghsaWEB
News mentions
0No linked articles in our index yet.