Class: Alchemy::SvgScrubber

Inherits:
Loofah::Scrubber
  • Object
show all
Defined in:
lib/alchemy/svg_scrubber.rb

Overview

A Loofah scrubber that sanitizes SVG content by removing dangerous elements and attributes that could execute scripts or load malicious external resources.

Constant Summary collapse

DANGEROUS_ELEMENTS =

Elements that can execute scripts or load external resources

%w[
  script
  foreignObject
  iframe
  object
  embed
  handler
  listener
].freeze
ANIMATION_ELEMENTS =

Animation elements that are dangerous only when targeting URL attributes

%w[
  animate
  animateMotion
  animateTransform
  set
].freeze
DANGEROUS_ANIMATION_TARGETS =

Attributes that animations should not be allowed to target

%w[
  href
  xlink:href
].freeze
URL_ATTRIBUTES =

Attributes that can contain javascript: URLs

%w[
  href
  xlink:href
  formaction
  src
  data
  srcdoc
].freeze
SAFE_URL_PROTOCOLS =

Protocols that are safe in URL attributes

%w[
  http
  https
].freeze
SAFE_DATA_URI_TYPES =

Safe data URI MIME types (images only, no text/html or SVG)

%w[
  data:image/png
  data:image/jpeg
  data:image/jpg
  data:image/gif
  data:image/webp
  data:image/avif
].freeze

Instance Method Summary collapse

Instance Method Details

#scrub(node) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/alchemy/svg_scrubber.rb', line 61

def scrub(node)
  # Remove dangerous elements entirely
  if DANGEROUS_ELEMENTS.include?(node.name)
    node.remove
    return
  end

  # Remove animation elements only if they target dangerous attributes
  if ANIMATION_ELEMENTS.include?(node.name)
    target = node["attributeName"]&.downcase
    if DANGEROUS_ANIMATION_TARGETS.include?(target)
      node.remove
      return
    end
  end

  node.attributes.each do |attr_name, attr_obj|
    attr_lower = attr_name.downcase

    # Remove all event handlers (on*)
    if attr_lower.start_with?("on")
      node.remove_attribute(attr_name)
      next
    end

    # Check URL attributes for dangerous protocols
    if URL_ATTRIBUTES.include?(attr_lower)
      value = attr_obj.value.to_s.strip.downcase.gsub(/[\s\x00-\x1f]+/, "")
      unless safe_url?(value)
        node.remove_attribute(attr_name)
      end
      next
    end

    # Remove style attributes with dangerous content
    if attr_lower == "style"
      value = attr_obj.value.to_s.downcase
      if value.match?(/javascript:|expression\(|vbscript:|url\s*\(\s*["']?\s*(?:javascript|vbscript|data:text\/html):/i)
        node.remove_attribute(attr_name)
      end
    end
  end
end