Module: OrdarsRailsEditor::Sanitizer

Defined in:
lib/ordars_rails_editor/sanitizer.rb

Overview

에디터 저장 HTML 서버측 새니타이저 — 저장형 XSS 방어의 정본.

⭐호스트 앱은 «저장 직전» 반드시 이걸 통과시킨다: post.content = OrdarsRailsEditor::Sanitizer.clean(params) 클라이언트(에디터)가 만드는 HTML 스키마와 이 화이트리스트는 한 쌍 — 에디터에 서식을 추가하면 여기 허용 목록도 함께 넓힌다(안 넓히면 저장 시 조용히 벗겨진다 — 의도된 동작).

style 속성은 통째 허용하지 않고 «속성 단위»로 거른다(color/background-color/font-size/text-align).

Constant Summary collapse

ALLOWED_TAGS =

⚠에디터 서식과 «한 쌍» — mark=형광펜(Highlight), data-color=Highlight 색 속성(0.1.1 에서 누락 실사고:

형광펜 적용 글이 저장 시 조용히 벗겨짐).
%w[p br strong b em i s del u code pre blockquote ul ol li h1 h2 h3 hr a img span mark].freeze
ALLOWED_ATTRIBUTES =
%w[href target rel src alt width height style data-file-id data-color].freeze
ALLOWED_STYLE_PROPS =
%w[color background-color font-size text-align].freeze
ALLOWED_URI_SCHEMES =
%w[http https mailto tel].freeze

Class Method Summary collapse

Class Method Details

.base_sanitizerObject



65
66
67
# File 'lib/ordars_rails_editor/sanitizer.rb', line 65

def base_sanitizer
  @base_sanitizer ||= Rails::HTML5::SafeListSanitizer.new
end

.clean(html) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/ordars_rails_editor/sanitizer.rb', line 22

def clean(html)
  return "" if html.nil? || html.to_s.strip.empty?

  sanitized = base_sanitizer.sanitize(
    html.to_s,
    tags: ALLOWED_TAGS,
    attributes: ALLOWED_ATTRIBUTES,
    scrubber: nil
  )

  frag = Loofah.fragment(sanitized)

  # style 은 허용 속성만 통과
  frag.css("[style]").each do |node|
    filtered = filter_style(node["style"])
    filtered.empty? ? node.remove_attribute("style") : (node["style"] = filtered)
  end

  # 링크: 스킴 화이트리스트 + 새 탭 안전 rel
  frag.css("a").each do |a|
    href = a["href"].to_s.strip
    scheme = href[/\A([a-z][a-z0-9+.-]*):/i, 1]&.downcase
    if href.empty? || (scheme && !ALLOWED_URI_SCHEMES.include?(scheme))
      a.remove_attribute("href")
    end
    a["rel"] = "noopener noreferrer" if a["target"].to_s == "_blank"
  end

  frag.to_s
end

.filter_style(style) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ordars_rails_editor/sanitizer.rb', line 53

def filter_style(style)
  style.to_s.split(";").filter_map do |decl|
    prop, value = decl.split(":", 2)
    next if prop.nil? || value.nil?
    prop = prop.strip.downcase
    value = value.strip
    next unless ALLOWED_STYLE_PROPS.include?(prop)
    next if value.match?(/url\s*\(|expression|javascript/i)
    "#{prop}: #{value}"
  end.join("; ")
end