spektr 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ddcc5f8096643294db83930d7df5cace71619eca037464d68e109ff6f3c9f190
4
- data.tar.gz: d2a32908dbe7f10b1ee680a62c3442f44e0529b6ea50e3c352451086c5eb7a97
3
+ metadata.gz: e3ad6c1899df739d3af4a37a6e4494b6fc5daed76a1004130182e89222ce1c46
4
+ data.tar.gz: f4fb6570bfc3da7bb3c762ac5d2cc6278606410e7731489a287aea968a4bd5b8
5
5
  SHA512:
6
- metadata.gz: f0d7a1db5105c0abc1413b81098cb76895b20bf42e94b33087e9c7a87860cbebaa78efd31fa2e569dacbe3c6df71007cc59dfbae7aa755c4ea049a1ce8890bed
7
- data.tar.gz: d93c5cf94158219882efd1f6c9c756b2324e87d42a4dbd768479ea0ad1580e70a36e9fc74a249cab34e8cf06e20588cf7f4516a605192ec3da7a52dc25994add
6
+ metadata.gz: 32f425fb5c3d6c856f852936f5e138f1e9bff68984bcc681a4fe49a80099d3b8401f965b89d4ba64e69e5d92838f0543e9b80a5ce7bd96eea8cb3605a2e654d7
7
+ data.tar.gz: ac96dc9367cf88ea33b9994428ab3c089d70cd090b3d894e00d27c4cccae110ae79b0cca901b2e0eb1b37df67c46e97e5305df64fb4332c56d245959fe0610b8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.2
6
+
7
+ * Remove an accidental `byebug` require that caused scans to fail when development dependencies were not installed
8
+ * Avoid false-positive XSS findings for route helpers in block-form `link_to` calls
9
+ * Preserve dots in reported paths and stable warning fingerprints when scanning with `.` as the root
10
+ * Require `tty-table` 0.10 or newer
11
+
12
+ ## 1.0.1
13
+
14
+ * various nil handling fixes
15
+
5
16
  ## 1.0.0
6
17
 
7
18
  * Add support for ignoring findings from specific checks with a `# spektr:ignore CHECK_NAME` source comment
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,3 +1,3 @@
1
- # Contributor Covenant Code of Conduct
1
+ # Code of Conduct
2
2
 
3
3
  Same as Ruby: [https://www.ruby-lang.org/en/conduct/](https://www.ruby-lang.org/en/conduct/)
@@ -27,7 +27,7 @@ module Spektr
27
27
 
28
28
  def warn!(target, check, location, message, confidence = :high)
29
29
  full_path = target.is_a?(String) ? target : target.path
30
- path = full_path.gsub(@app.root, "")
30
+ path = full_path.delete_prefix(@app.root)
31
31
  return if target.respond_to?(:ignored_at?) && target.ignored_at?(location&.start_line, check)
32
32
  return if dupe?(path, location, message)
33
33
 
@@ -65,7 +65,7 @@ module Spektr
65
65
  end
66
66
  when :keyword_hash_node, :hash_node
67
67
  node.elements.each do |element|
68
- return true if user_input?(element.key)
68
+ return true if element.respond_to?(:key) && user_input?(element.key)
69
69
  return true if user_input?(element.value)
70
70
  end
71
71
  when :array_node
@@ -85,7 +85,7 @@ module Spektr
85
85
  actions.each do |action|
86
86
  next unless action.body
87
87
  action.body.each do |exp|
88
- return true if exp.name == node.name && user_input?(exp)
88
+ return true if exp.respond_to?(:name) && exp.name == node.name && user_input?(exp)
89
89
  end
90
90
  end
91
91
  when :local_variable_read_node
@@ -137,7 +137,7 @@ module Spektr
137
137
  end
138
138
  when :keyword_hash_node, :hash_node
139
139
  node.elements.each do |element|
140
- return true if model_attribute?(element.key)
140
+ return true if element.respond_to?(:key) && model_attribute?(element.key)
141
141
  return true if model_attribute?(element.value)
142
142
  end
143
143
  when :array_node
@@ -176,8 +176,9 @@ module Spektr
176
176
  }
177
177
  end
178
178
  actions.each do |action|
179
+ next unless action.body
179
180
  action.body.each do |exp|
180
- next unless node.respond_to?(:name)
181
+ next unless exp.respond_to?(:name) && node.respond_to?(:name)
181
182
  return model_attribute?(exp.value) if exp.is_a?(Prism::InstanceVariableWriteNode) && exp.name == node.name
182
183
  end
183
184
  end
@@ -34,11 +34,7 @@ module Spektr
34
34
  # TODO: might need to exclude tempfile and ActiveStorage::Filename
35
35
  return if calls.empty?
36
36
  calls.each do |call|
37
- if call.arguments.is_a?(Prism::ArgumentsNode)
38
- argument = call.arguments.arguments.first
39
- else
40
- argument = call.arguments.first
41
- end
37
+ argument = call.arguments.arguments.first if call.arguments.is_a?(Prism::ArgumentsNode)
42
38
  next unless argument
43
39
  if user_input?(argument) || model_attribute?(argument)
44
40
  warn! @target, self, call.location, "Command injection in #{call.name}"
@@ -36,7 +36,7 @@ module Spektr
36
36
  end
37
37
  if argument.is_a?(Prism::KeywordHashNode)
38
38
  argument.elements.each do |element|
39
- if user_input?(element.key)
39
+ if element.respond_to?(:key) && user_input?(element.key)
40
40
  warn! @target, self, call.location, "Unescaped parameter in content_tag at #{element.key.name}"
41
41
  end
42
42
  end
@@ -12,6 +12,7 @@ module Spektr
12
12
  return unless super
13
13
  [:eval, :instance_eval, :class_eval, :module_eval].each do |name|
14
14
  @target.find_calls(name).each do |call|
15
+ next unless call.arguments
15
16
  call.arguments.arguments.each do |argument|
16
17
  if user_input?(argument)
17
18
  warn! @target, self, call.location, "User input in eval"
@@ -1,6 +1,10 @@
1
1
  module Spektr
2
2
  class Checks
3
3
  class LinkToHref < Base
4
+ # Route helpers build the href themselves and escape any interpolated
5
+ # values, so user input reaching one is not an XSS sink.
6
+ ROUTE_HELPER = /_url$|_path$/
7
+
4
8
  def initialize(app, target)
5
9
  super
6
10
  @name = "XSS in href param of link_to"
@@ -14,9 +18,11 @@ module Spektr
14
18
  block_locations = []
15
19
  @target.find_calls_with_block(:link_to).each do |call|
16
20
  block_locations << call.location
17
- next unless call.arguments.arguments.first
18
- ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{call.arguments.arguments.first.inspect}"
19
- if user_input? call.arguments.arguments.first
21
+ href = call.arguments.arguments.first
22
+ next unless href
23
+ ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{href.inspect}"
24
+ next if route_helper?(href)
25
+ if user_input? href
20
26
  warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
21
27
  end
22
28
  end
@@ -24,18 +30,21 @@ module Spektr
24
30
  @target.find_calls(:link_to).each do |call|
25
31
  next if block_locations.include? call.location
26
32
  next unless call.arguments
27
- ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{call.arguments.arguments[1].inspect}"
28
- next unless call.arguments.arguments[1]
29
- require 'byebug'
30
- if call.arguments.arguments[1] && call.arguments.arguments[1].respond_to?(:name)
31
- name = call.arguments.arguments[1].name
32
- end
33
- next if name && name =~ /_url$|_path$/
34
- if user_input? call.arguments.arguments[1]
33
+ href = call.arguments.arguments[1]
34
+ ::Spektr.logger.debug "#{@target.path} #{call.location.start_line} #{href.inspect}"
35
+ next unless href
36
+ next if route_helper?(href)
37
+ if user_input? href
35
38
  warn! @target, self, call.location, "Cross-Site Scripting: Unsafe user supplied value in link_to"
36
39
  end
37
40
  end
38
41
  end
42
+
43
+ private
44
+
45
+ def route_helper?(node)
46
+ node.respond_to?(:name) && node.name.to_s =~ ROUTE_HELPER
47
+ end
39
48
  end
40
49
  end
41
50
  end
@@ -11,6 +11,7 @@ module Spektr
11
11
  @ast = Prism.parse(content)
12
12
  @path = path
13
13
  return unless @ast
14
+ @name = ""
14
15
  @parent = ""
15
16
  @parent_modules = []
16
17
  @methods = []
@@ -89,12 +90,12 @@ module Spektr
89
90
  @parent = node.superclass.receiver.name.to_s
90
91
  when Prism::ConstantPathNode, Prism::ConstantReadNode
91
92
  @parent = node.superclass.name.to_s
92
- @parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent)
93
- if node.superclass.respond_to?(:parent) && node.superclass.parent.respond_to?(:parent)
93
+ @parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent) && node.superclass.parent
94
+ if node.superclass.respond_to?(:parent) && node.superclass.parent.respond_to?(:parent) && node.superclass.parent&.parent
94
95
  @parent.prepend("#{node.superclass.parent.parent.name}::")
95
96
  end
96
97
  end
97
- if node.is_a?(Prism::ClassNode) && node.constant_path && node.constant_path.respond_to?(:parent)
98
+ if node.is_a?(Prism::ClassNode) && node.constant_path && node.constant_path.respond_to?(:parent) && node.constant_path.parent
98
99
  @parent = node.constant_path.parent.name.to_s
99
100
  end
100
101
  @parent = @parent.prepend("#{@parent_modules.map(&:name).join('::')}::") if @parent_modules.any?
@@ -102,7 +103,7 @@ module Spektr
102
103
  end
103
104
 
104
105
  def visit_module_node(node)
105
- @parent_modules << node.constant_path.parent.name if node.constant_path && node.constant_path.respond_to?(:parent)
106
+ @parent_modules << node.constant_path.parent.name if node.constant_path && node.constant_path.respond_to?(:parent) && node.constant_path.parent
106
107
  @parent_modules << node
107
108
  super
108
109
  end
@@ -1,3 +1,3 @@
1
1
  module Spektr
2
- VERSION = '1.0.0'
2
+ VERSION = '1.0.2'
3
3
  end
data/spektr.gemspec CHANGED
@@ -36,7 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency 'tty-color'
37
37
  spec.add_dependency 'tty-option'
38
38
  spec.add_dependency 'tty-spinner'
39
- spec.add_dependency 'tty-table'
39
+ spec.add_dependency 'tty-table', '>= 0.10'
40
40
  spec.add_dependency 'zeitwerk', '>= 2.6'
41
41
 
42
42
  spec.add_development_dependency 'byebug'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spektr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Molnar
@@ -141,14 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: '0'
144
+ version: '0.10'
145
145
  type: :runtime
146
146
  prerelease: false
147
147
  version_requirements: !ruby/object:Gem::Requirement
148
148
  requirements:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
- version: '0'
151
+ version: '0.10'
152
152
  - !ruby/object:Gem::Dependency
153
153
  name: zeitwerk
154
154
  requirement: !ruby/object:Gem::Requirement