Resumen
ActionText, el componente de Rails para contenido enriquecido, puede convertir el cuerpo de un texto a Markdown con Content#to_markdown (y ActionText::RichText#to_markdown). Internamente, al serializar los adjuntos, ese método envuelve el Markdown ya generado en una etiqueta propia, <action-text-markdown>, para que el conversor lo emita sin tocarlo.
El fallo es que el conversor no distingue entre las etiquetas <action-text-markdown> que crea el propio Rails y las que escribe el usuario en el cuerpo del texto. Cualquier <action-text-markdown> se trata como marcado de confianza y su contenido se copia literal a la salida. Así, un usuario que puede redactar texto enriquecido consigue meter en el Markdown exportado un enlace con un esquema peligroso (javascript:, data:…), saltándose la validación de esquemas URI que se había añadido para los enlaces normales y las imágenes remotas.
El código implicado está en actiontext/lib/action_text/markdown_conversion.rb. Primero, la etiqueta figura en la lista de padres cuyo contenido no se escapa (línea 80):
SKIP_ESCAPING_PARENTS = %w[ action-text-markdown code pre ].freeze
Cuando un nodo cae en esa lista, se devuelve su contenido sin procesar (líneas 96-97):
elsif skip_markdown_escaping?(node)
node.content
Y el visitante de esta etiqueta simplemente une los hijos, sin envolverlos en nada (líneas 226-228):
def visit_action_text_markdown(_node, child_values)
join_children(child_values)
end
pre y code están en la misma lista, pero sus visitantes meten la salida dentro de un bloque de código, de modo que un enlace ahí dentro queda inerte. Solo <action-text-markdown> lo emite en bruto, y por eso es la vía para saltarse la validación.
Pasos de reproducción
- Crear el test
actiontext/test/unit/markdown_passthrough_bypass_test.rbcon este contenido:
require "test_helper"
class ActionTextMarkdownPassthroughBypassTest < ActiveSupport::TestCase
test "user-supplied <action-text-markdown> emits markdown link bypassing URI scheme validation" do
payload = "<action-text-markdown>[click](javascript:alert(1))</action-text-markdown>"
markdown = ActionText::Content.new(payload).to_markdown
refute_match(/\(javascript:/, markdown,
"expected URI scheme validation to reject javascript: URI, got: #{markdown.inspect}")
end
end
- Ejecutarlo desde la raíz del repositorio de Rails:
cd actiontext && bin/test test/unit/markdown_passthrough_bypass_test.rb
- El test falla porque el enlace
javascript:aparece intacto en el Markdown:
Failure:
expected URI scheme validation to reject javascript: URI, got: "[click](javascript:alert(1))"
- Como control, los tests que ya existían para bloquear esquemas no permitidos en imágenes remotas siguen pasando, lo que confirma que la protección existe pero esta etiqueta la esquiva:
cd actiontext && bin/test test/unit/markdown_conversion_test.rb -n '/RemoteImage attachment with disallowed URI scheme|RichText#to_markdown/'
# 5 runs, 5 assertions, 0 failures, 0 errors
Impacto
Cualquier sistema que consuma la salida de ActionText::RichText#to_markdown o Content#to_markdown (por ejemplo, un panel de vista previa, un resumen por correo, una réplica federada o un segundo renderizador de Markdown) puede recibir un enlace Markdown con esquema javascript:, data: u otro no seguro, controlado por cualquier usuario que pueda escribir texto enriquecido.
La barrera de seguridad sobre URIs que introdujeron los PR #56871 y #56909 para los enlaces <a> y los adjuntos de imagen remota queda anulada en la exportación a Markdown con solo envolver el payload en <action-text-markdown>.
El renderizado HTML (Content#to_s) no se ve afectado directamente: ahí la etiqueta se elimina y el payload se muestra como texto inerte. El problema es exclusivo de la exportación a Markdown.
HackerOne clasificó el reporte con severidad media. Se envió el 11 de mayo de 2026, figura como resuelto y se divulgó el 24 de agosto de 2026.
Remediación
El investigador propuso eliminar las etiquetas <action-text-markdown> que vengan del usuario (conservando su contenido como texto normal) antes de que to_markdown genere sus propios marcadores internos:
--- a/actiontext/lib/action_text/content.rb
+++ b/actiontext/lib/action_text/content.rb
@@ -146,6 +146,11 @@ module ActionText
end
def to_markdown(attachment_links: false)
+ # Prevent user-supplied <action-text-markdown> nodes from being treated
+ # as trusted internal markdown passthrough by the markdown converter.
+ fragment.find_all("action-text-markdown").each do |node|
+ node.replace(node.children)
+ end
render_attachments(with_full_attributes: false) { |attachment|
ActionText::HtmlConversion.create_element("action-text-markdown").tap do |node|
node.content = attachment.to_markdown(attachment_links: attachment_links)
fragment.find_all es el mismo recurso de Nokogiri que ya se usa en otras partes de content.rb (líneas 56 y 210). Según el investigador, con este cambio el test de la prueba de concepto pasa y los tests existentes de Markdown para adjuntos no se ven afectados. El reporte consta como resuelto, pero no detalla qué corrección aplicó finalmente el equipo de Rails.