Class: WSDL::XML::ThreatScanner Private

Inherits:
Object
  • Object
show all
Defined in:
lib/wsdl/xml/threat_scanner.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Scans raw XML strings for attack patterns without parsing.

Provides defense-in-depth threat detection by identifying suspicious patterns before they reach the parser. All scanning operates on binary-encoded strings using byte-level comparisons to avoid allocations from regex match objects.

Detected threats:

  • +:doctype+ — DOCTYPE declaration (XXE vector)
  • +:entity_declaration+ — ENTITY definitions
  • +:external_reference+ — SYSTEM or PUBLIC identifiers
  • +:parameter_entity+ — Parameter entity references (+%entity;+)
  • +:deep_nesting+ — Excessive open tags (>MAX_OPEN_TAGS)
  • +:large_attribute+ — Single attribute value >MAX_ATTRIBUTE_VALUE_SIZE bytes
  • +:large_attributes_total+ — Cumulative attribute size >MAX_TOTAL_ATTRIBUTE_SIZE bytes

Examples:

scanner = ThreatScanner.new(xml_string)
threats = scanner.scan
# => [:doctype, :entity_declaration]

Byte Constants collapse

SLASH_BYTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)
'/'.ord
BANG_BYTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)
'!'.ord
QUESTION_BYTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)
'?'.ord
DOUBLE_QUOTE_BYTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)
'"'.ord
SINGLE_QUOTE_BYTE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Returns:

  • (Integer)
"'".ord
WHITESPACE_BYTES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Whitespace byte values: space, tab, LF, CR.

Returns:

  • (Set<Integer>)
Set[0x20, 0x09, 0x0A, 0x0D].freeze
QUOTE_BYTES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Quote byte values: double quote and single quote.

Returns:

  • (Set<Integer>)
Set[DOUBLE_QUOTE_BYTE, SINGLE_QUOTE_BYTE].freeze
NON_OPEN_TAG_BYTES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Non-open-tag byte values: closing (+/+), declaration (+!+), processing instruction (+?+).

Returns:

  • (Set<Integer>)
Set[SLASH_BYTE, BANG_BYTE, QUESTION_BYTE].freeze
QUOTE_CHARS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Pre-computed binary quote character strings, keyed by byte value. Avoids allocating a new String via +Integer#chr+ on every call.

Returns:

  • (Hash{Integer => String})
{
  DOUBLE_QUOTE_BYTE => '"'.b.freeze,
  SINGLE_QUOTE_BYTE => "'".b.freeze
}.freeze

Constant Summary collapse

MAX_OPEN_TAGS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Maximum number of open tags before flagging deep nesting.

Returns:

  • (Integer)
1_000
MAX_ATTRIBUTE_VALUE_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Maximum size of a single attribute value in bytes.

Returns:

  • (Integer)
10_000
MAX_TOTAL_ATTRIBUTE_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Maximum cumulative size of all attribute values in bytes.

Returns:

  • (Integer)
1_000_000

Instance Method Summary collapse

Constructor Details

#initialize(xml_string) ⇒ ThreatScanner

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of ThreatScanner.

Parameters:

  • xml_string (String)

    the XML string to scan



76
77
78
# File 'lib/wsdl/xml/threat_scanner.rb', line 76

def initialize(xml_string)
  @bin = xml_string.b
end

Instance Method Details

#count_open_tagsInteger

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Counts open XML tags using byte-level scanning.

Matches tags that start with an ASCII letter and are not closing tags (+</+), comments (+<!+), or processing instructions (+<?+).

Returns:

  • (Integer)

    the number of open tags found



103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/wsdl/xml/threat_scanner.rb', line 103

def count_open_tags
  count = 0
  pos = 0

  while (pos = @bin.index('<', pos))
    byte = @bin.getbyte(pos + 1)
    count += 1 if open_tag_start?(byte)
    pos += 1
  end

  count
end

#scanArray<Symbol>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Scans for all threat patterns and returns unique threat symbols.

Returns:

  • (Array<Symbol>)

    detected threat indicators



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/wsdl/xml/threat_scanner.rb', line 83

def scan
  threats = []

  threats << :doctype            if @bin.match?(/<!DOCTYPE/i)
  threats << :entity_declaration if @bin.match?(/<!ENTITY/i)
  threats << :external_reference if @bin.match?(/\bSYSTEM\s+["']/i)
  threats << :external_reference if @bin.match?(/\bPUBLIC\s+["']/i)
  threats << :parameter_entity   if @bin.match?(/%[a-zA-Z_][a-zA-Z0-9_]*;/)
  threats << :deep_nesting       if count_open_tags > MAX_OPEN_TAGS
  threats.concat(scan_attribute_threats)

  threats.uniq
end

#scan_attribute_threatsArray<Symbol>

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Scans attribute values for size-based threats.

Locates +=+ followed by a quoted string, then measures each value's length via position arithmetic (zero allocations).

Returns:

  • (Array<Symbol>)

    detected attribute threats



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/wsdl/xml/threat_scanner.rb', line 122

def scan_attribute_threats
  threats = []
  total_size = 0
  pos = 0

  while pos < @bin.size
    eq_pos = @bin.index('=', pos)
    break unless eq_pos

    measure_attribute_value(eq_pos)
    pos = @measured_next_pos
    next unless @measured_value_length

    total_size += @measured_value_length
    threats << :large_attribute if @measured_value_length > MAX_ATTRIBUTE_VALUE_SIZE
  end

  threats << :large_attributes_total if total_size > MAX_TOTAL_ATTRIBUTE_SIZE
  threats
end