Class: Herb::Engine::Validators::SecurityValidator

Inherits:
Herb::Engine::Validator show all
Defined in:
lib/herb/engine/validators/security_validator.rb,
sig/herb/engine/validators/security_validator.rbs

Instance Attribute Summary

Attributes inherited from Herb::Engine::Validator

#diagnostics, #enabled

Instance Method Summary collapse

Methods inherited from Herb::Engine::Validator

#clear_diagnostics, #diagnostic_count, #enabled?, #error, #errors, #errors?, #hint, #info, #initialize, #validate, #warning, #warnings, #warnings?

Methods inherited from Visitor

#visit, #visit_all, #visit_cdata_node, #visit_child_nodes, #visit_document_node, #visit_erb_begin_node, #visit_erb_block_node, #visit_erb_case_match_node, #visit_erb_case_node, #visit_erb_content_node, #visit_erb_else_node, #visit_erb_end_node, #visit_erb_ensure_node, #visit_erb_for_node, #visit_erb_if_node, #visit_erb_in_node, #visit_erb_node, #visit_erb_open_tag_node, #visit_erb_render_node, #visit_erb_rescue_node, #visit_erb_strict_locals_node, #visit_erb_unless_node, #visit_erb_until_node, #visit_erb_when_node, #visit_erb_while_node, #visit_erb_yield_node, #visit_html_attribute_node, #visit_html_attribute_value_node, #visit_html_close_tag_node, #visit_html_comment_node, #visit_html_conditional_element_node, #visit_html_conditional_open_tag_node, #visit_html_doctype_node, #visit_html_element_node, #visit_html_omitted_close_tag_node, #visit_html_text_node, #visit_html_virtual_close_tag_node, #visit_literal_node, #visit_node, #visit_ruby_html_attributes_splat_node, #visit_ruby_literal_node, #visit_ruby_parameter_node, #visit_ruby_render_keywords_node, #visit_ruby_render_local_node, #visit_whitespace_node, #visit_xml_declaration_node

Methods included from AST::Helpers

#erb_comment?, #erb_graphql?, #erb_output?, #erb_outputs?, #inline_ruby_comment?

Constructor Details

This class inherits a constructor from Herb::Engine::Validator

Instance Method Details

#add_diagnostic(message, location, severity, code: nil, source: nil, suggestion: nil) ⇒ Object

Parameters:

  • message (Object)
  • location (Object)
  • severity (Object)
  • code: (Object) (defaults to: nil)
  • source: (Object) (defaults to: nil)
  • suggestion: (Object) (defaults to: nil)

Returns:

  • (Object)


96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/herb/engine/validators/security_validator.rb', line 96

def add_diagnostic(message, location, severity, code: nil, source: nil, suggestion: nil)
  diagnostic = {
    message: message,
    location: location,
    severity: severity,
    code: code,
    source: source || self.class.name,
    suggestion: suggestion,
  }

  @diagnostics << diagnostic
end

#add_security_error(location, message, suggestion) ⇒ Object

Parameters:

  • location (Object)
  • message (Object)
  • suggestion (Object)

Returns:

  • (Object)


91
92
93
94
# File 'lib/herb/engine/validators/security_validator.rb', line 91

def add_security_error(location, message, suggestion)
  add_diagnostic(message, location, :error, code: "SecurityViolation", source: "SecurityValidator",
                                            suggestion: suggestion)
end

#conditional_tag_attributes_prism_node?(prism_node) ⇒ Boolean

Parameters:

  • prism_node (Object)

Returns:

  • (Boolean)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/herb/engine/validators/security_validator.rb', line 75

def conditional_tag_attributes_prism_node?(prism_node)
  if prism_node.is_a?(Prism::IfNode) || prism_node.is_a?(Prism::UnlessNode)
    body = prism_node.statements&.body
    return false unless body
    return false unless body.length == 1

    return tag_attributes_prism_node?(body.first)
  end

  if prism_node.is_a?(Prism::AndNode) || prism_node.is_a?(Prism::OrNode)
    return tag_attributes_prism_node?(prism_node.right)
  end

  false
end

#tag_attributes_prism_node?(prism_node) ⇒ Boolean

Parameters:

  • prism_node (Object)

Returns:

  • (Boolean)


65
66
67
68
69
70
71
72
73
# File 'lib/herb/engine/validators/security_validator.rb', line 65

def tag_attributes_prism_node?(prism_node)
  return false unless prism_node.is_a?(Prism::CallNode)
  return false unless prism_node.name == :attributes

  receiver = prism_node.receiver
  return false unless receiver.is_a?(Prism::CallNode)

  receiver.name == :tag
end

#validate_attribute_name_security(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


53
54
55
56
57
58
59
60
61
62
63
# File 'lib/herb/engine/validators/security_validator.rb', line 53

def validate_attribute_name_security(node)
  node.children.each do |child|
    next unless child.is_a?(Herb::AST::ERBContentNode) && erb_outputs?(child)

    add_security_error(
      child.location,
      "ERB output in attribute names is not allowed for security reasons.",
      "Use static attribute names with dynamic values instead."
    )
  end
end

#validate_tag_security(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


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/herb/engine/validators/security_validator.rb', line 24

def validate_tag_security(node)
  node.children.each do |child|
    next if child.is_a?(Herb::AST::HTMLAttributeNode)
    next if child.is_a?(Herb::AST::WhitespaceNode)

    next unless child.is_a?(Herb::AST::ERBContentNode) && erb_outputs?(child)

    prism_node = child.prism

    next if prism_node && tag_attributes_prism_node?(prism_node)

    if prism_node && conditional_tag_attributes_prism_node?(prism_node)
      add_security_error(
        child.location,
        "Avoid using conditional `tag.attributes` in attribute position.",
        "Use `<% if ... %><%= tag.attributes(...) %><% end %>` instead."
      )

      next
    end

    add_security_error(
      child.location,
      "ERB output tags (<%= %>) are not allowed in attribute position.",
      "Use control flow (<% %>) with static attributes instead."
    )
  end
end

#visit_html_attribute_name_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


16
17
18
19
20
# File 'lib/herb/engine/validators/security_validator.rb', line 16

def visit_html_attribute_name_node(node)
  validate_attribute_name_security(node)

  super
end

#visit_html_open_tag_node(node) ⇒ Object

Parameters:

  • node (Object)

Returns:

  • (Object)


10
11
12
13
14
# File 'lib/herb/engine/validators/security_validator.rb', line 10

def visit_html_open_tag_node(node)
  validate_tag_security(node)

  super
end