Module: RedQuilt::Inline::UrlSanitizer

Defined in:
lib/red_quilt/inline/url_sanitizer.rb

Overview

URL-scheme security policy for inline link / image / autolink destinations. Kept separate from Builder so the “which schemes are safe and how blocking is reported” concern has a single home and can change without touching the inline construction logic.

Stateless (module_function); diagnostics are appended to the caller’s array (or skipped when it is nil), so there is no per-call allocation.

Constant Summary collapse

SAFE_SCHEMES =
%w[http https mailto ftp tel ssh].freeze
%w[javascript vbscript data].freeze
SCHEME_RE =
/\A([a-zA-Z][a-zA-Z0-9+\-.]*):/

Class Method Summary collapse

Class Method Details

Autolink destinations: denylist. The destination is returned unchanged unless its scheme executes script on navigation, in which case the href is emptied and a diagnostic is recorded.



44
45
46
47
48
49
50
51
# File 'lib/red_quilt/inline/url_sanitizer.rb', line 44

def block_unsafe_autolink(destination, diagnostics)
  scheme = destination[SCHEME_RE, 1]
  return destination if scheme.nil?
  return destination unless UNSAFE_AUTOLINK_SCHEMES.include?(scheme.downcase)

  report_blocked(diagnostics, scheme)
  ""
end

.report_blocked(diagnostics, scheme) ⇒ Object



53
54
55
56
57
58
59
60
61
# File 'lib/red_quilt/inline/url_sanitizer.rb', line 53

def report_blocked(diagnostics, scheme)
  return unless diagnostics

  diagnostics << Diagnostic.new(
    severity: :warning,
    rule: :unsafe_url,
    message: "Unsafe URL scheme #{scheme.downcase.inspect} blocked",
  )
end

.sanitize_destination(destination, diagnostics) ⇒ Object

Link / image destinations: allowlist. Relative URLs (starting ‘/` or `#`) and scheme-less URLs pass; an unknown scheme is blocked (href emptied) and a diagnostic is recorded.



29
30
31
32
33
34
35
36
37
38
39
# File 'lib/red_quilt/inline/url_sanitizer.rb', line 29

def sanitize_destination(destination, diagnostics)
  return "" if destination.nil?
  return destination if destination.start_with?("/", "#")

  scheme = destination[SCHEME_RE, 1]
  return destination if scheme.nil?
  return destination if SAFE_SCHEMES.include?(scheme.downcase)

  report_blocked(diagnostics, scheme)
  ""
end