Class: SvgSentinel::Scanner
- Inherits:
-
Object
- Object
- SvgSentinel::Scanner
- Includes:
- Rules
- Defined in:
- lib/svg_sentinel/scanner.rb
Overview
The scanner. It reads an SVG string and reports what makes it unsafe.
The rules come from two public sources: general SVG cross-site-scripting knowledge (no scripts, no event handlers, no javascript: URIs, no foreign objects, no XXE) and the BIMI "SVG Tiny 1.2 Portable/Secure" profile used for brand-mark logos (no scripting, no animation, no external references, no raster images). Nothing here is fetched over the network.
It is built to survive hostile input: input is normalised to valid UTF-8 first, a hard byte cap and streaming structural limits refuse oversized or pathologically shaped payloads before the tree parser runs, DOCTYPE / ENTITY declarations are refused so the parser is never exposed to entity-expansion or external-entity attacks, and every parse failure becomes a finding rather than an exception.
Raw findings are re-rated by a Policy for the rendering context and any configured severity overrides before they reach the Result.
Constant Summary collapse
- DEFAULT_MAX_BYTES =
BIMI recommends brand-mark SVGs stay small.
32_768- DEFAULT_HARD_MAX_BYTES =
5 MiB: refuse to parse anything larger.
5_242_880- DEFAULT_MAX_DEPTH =
100- DEFAULT_MAX_NODES =
200_000- DEFAULT_MAX_ATTRIBUTES =
512- USE_EXPANSION_LIMIT =
Nested
1_000_000
Constants included from Rules
Rules::ANIMATION_ELEMENTS, Rules::DANGEROUS_SCHEMES, Rules::EXTERNAL_SCHEMES, Rules::FOREIGN_ELEMENTS, Rules::SCRIPTABLE_DATA_MEDIA, Rules::SCRIPT_ELEMENTS, Rules::SENSITIVE_ANIMATED_ATTRIBUTES, Rules::STRICT_ALLOWED_ELEMENTS, Rules::URI_ATTRIBUTES
Instance Method Summary collapse
-
#initialize(profile: :strict, allow_external: false, allow_data_uri: false, max_bytes: DEFAULT_MAX_BYTES, hard_max_bytes: DEFAULT_HARD_MAX_BYTES, max_depth: DEFAULT_MAX_DEPTH, max_nodes: DEFAULT_MAX_NODES, max_attributes: DEFAULT_MAX_ATTRIBUTES, context: :inline, severity_overrides: {}, disabled: []) ⇒ Scanner
constructor
A new instance of Scanner.
- #scan(svg) ⇒ Object
Methods included from Rules
allowed_element?, animation_element?, classify_reference, css_reference_targets, dangerous_css?, dangerous_scheme?, data_uri_media_type, decontrol, event_handler_attr?, external_scheme?, foreign_element?, script_element?, scriptable_data?, sensitive_animation_target?, strip_css_comments, to_utf8, uri_attribute?, uri_scheme
Constructor Details
#initialize(profile: :strict, allow_external: false, allow_data_uri: false, max_bytes: DEFAULT_MAX_BYTES, hard_max_bytes: DEFAULT_HARD_MAX_BYTES, max_depth: DEFAULT_MAX_DEPTH, max_nodes: DEFAULT_MAX_NODES, max_attributes: DEFAULT_MAX_ATTRIBUTES, context: :inline, severity_overrides: {}, disabled: []) ⇒ Scanner
Returns a new instance of Scanner.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/svg_sentinel/scanner.rb', line 43 def initialize(profile: :strict, allow_external: false, allow_data_uri: false, max_bytes: DEFAULT_MAX_BYTES, hard_max_bytes: DEFAULT_HARD_MAX_BYTES, max_depth: DEFAULT_MAX_DEPTH, max_nodes: DEFAULT_MAX_NODES, max_attributes: DEFAULT_MAX_ATTRIBUTES, context: :inline, severity_overrides: {}, disabled: []) @profile = profile @allow_external = allow_external @allow_data_uri = allow_data_uri # A limit of 0 (or negative, or nil) means "disabled" - the single place # this is decided, so config, CLI, and the library agree. @max_bytes = limit(max_bytes) @hard_max_bytes = limit(hard_max_bytes) @max_depth = limit(max_depth) @max_nodes = limit(max_nodes) @max_attributes = limit(max_attributes) @policy = Policy.new(context: context, overrides: severity_overrides, disabled: disabled) @findings = [] @current_path = nil end |
Instance Method Details
#scan(svg) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/svg_sentinel/scanner.rb', line 63 def scan(svg) @findings = [] @current_path = nil collect(svg) Result.new(@policy.apply(@findings)) rescue SystemStackError # Recursion (parser or tree walk) overflowed the stack on pathologically # nested input. Only reachable when structural limits are disabled; the # contract is still that scan returns a Result, never raises. @current_path = nil add_security(:too_deep, :critical, "SVG nesting overflowed the stack") Result.new(@policy.apply(@findings)) end |