Class: UniversalRenderer::SSR::Scrubber

Inherits:
Loofah::Scrubber
  • Object
show all
Defined in:
lib/universal_renderer/ssr/scrubber.rb

Overview

Removes executable content from HTML returned by the SSR service while preserving the elements and data attributes needed to hydrate an app.

A blocklist over the whole HTML grammar cannot be a boundary against attacker-controlled markup, because the sanitizer and the browser have to agree on how the document parses. Treat it as defense in depth over HTML your own renderer produced.

Constant Summary collapse

PARSER_CONTEXT_ELEMENTS =

Elements that change how the rest of the markup is tokenized, so the tree this scrubber inspects is not the tree the browser builds.

<noscript> is raw text with scripting enabled (the browser) and markup without it (the sanitizer), so <noscript><p title="</noscript><img src=x onerror=...>"> reaches the browser as a live element. The MathML three flip the parser between foreign content and HTML integration points for the same effect. None belong in a server render.

%w[
  noscript mglyph malignmark annotation-xml
].freeze
BLOCKED_ELEMENTS =
(
  %w[
    base embed frame frameset iframe object script
    animate animatemotion animatetransform set
  ] + PARSER_CONTEXT_ELEMENTS
).freeze
ALLOWED_SCRIPT_TYPES =

Non-executable data-script MIME types. HTML treats a script whose type is neither a JavaScript MIME type nor one of the special types (module, importmap, speculationrules) as an inert data block.

The exception exists for JSON-LD, which the render emits and which would otherwise be sanitized away along with the SEO that motivates SSR.

%w[application/json application/ld+json].freeze
URI_ATTRIBUTES =
%w[
  action background cite codebase data formaction href longdesc poster src
  srcset xlink:href
].freeze
ALLOWED_PROTOCOLS =
%w[http https mailto tel].freeze
DANGEROUS_PROTOCOL =
/\A(?:javascript|vbscript):/i
DANGEROUS_DATA =
%r{\Adata:(?:text/html|application/xhtml\+xml|image/svg\+xml)}i
SAFE_DATA_IMAGE =
%r{\Adata:image/(?:avif|gif|jpeg|png|webp);base64,}i
INLINE_SVG_DATA =

An SVG referenced by renders in a restricted mode: no scripts, no external references. Bundlers routinely inline icons as data URIs, so the render would otherwise lose them. <source> inside <picture> feeds the same mode; inside <video>/<audio> it does not.

%r{\Adata:image/svg\+xml[,;]}i
IMAGE_SOURCE_ATTRIBUTES =
%w[src srcset].freeze

Instance Method Summary collapse

Constructor Details

#initializeScrubber

Returns a new instance of Scrubber.



54
55
56
57
# File 'lib/universal_renderer/ssr/scrubber.rb', line 54

def initialize
  super
  @direction = :top_down
end

Instance Method Details

#scrub(node) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/universal_renderer/ssr/scrubber.rb', line 59

def scrub(node)
  return Loofah::Scrubber::CONTINUE unless node.element?

  if blocked_element?(node) || refreshing_meta?(node)
    node.remove
    return Loofah::Scrubber::STOP
  end

  clean_attributes(node)
  Loofah::Scrubber::CONTINUE
end