spektr 0.5.6 → 1.0.1

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: e26ec9d89007e489dcb99102598d93d72d3e6d1acc43451dddc2ecc2dc5f653f
4
- data.tar.gz: 663f275b4b5e710928729698133f071a8b0bdac65d6598b5aed1959d212f26d6
3
+ metadata.gz: 8a830f6b6b9be5ac380e1cbf4a755b9171b77f9835d9e3f62c5f62c411ba529e
4
+ data.tar.gz: 97a48a8cdaf9d5ab568dcc7d201daf1433b07c1a6043901c300147185ef99dec
5
5
  SHA512:
6
- metadata.gz: 816d3c285a9f54ebe75cf9759e301d0dfd169a1a32dfa018afe1e51ed71dd243a3432519c0036ddb4f2a32ec37d50241bbbff12a5eccf61fb538ad3df34fc13e
7
- data.tar.gz: 0c7ce498c2875fd179cb32f949321ef67aaa82036cbfb76637bc1066f8d06e4264cf8a1f3b4035623c52b9104cc964ce650f8297cd618219b73d7186093aac5e
6
+ metadata.gz: 162e450e6584062cf616d010401090d8e367041042aca79b3b1a6268948e3a99185bbe08ee4fa1928a13a3a45b9cdd5a05f165a282c4abec47f52e23c059b363
7
+ data.tar.gz: d61db3f6438d1ddcd64564815096beb67007539a543c90a0768423d41ede3bf571ea8acd7d167339ee4dde5074677d49719d0fb14fcebabe9fe9d22bc30b2ac9
data/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.0.1
6
+
7
+ * various nil handling fixes
8
+
9
+ ## 1.0.0
10
+
11
+ * Add support for ignoring findings from specific checks with a `# spektr:ignore CHECK_NAME` source comment
12
+ * Add File.binread to check
13
+
5
14
  ## 0.5.6
6
15
 
7
16
  * fix nil handling positives
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/)
data/README.md CHANGED
@@ -36,8 +36,21 @@ spektr path/to/app
36
36
 
37
37
  To see the available options, you can run `spektr --help`.
38
38
 
39
- To ignore a finding, you can use the `--ignore` flag with a comma separated list of fingerprints from the report.
39
+ To ignore findings from the command line, use the `--ignore` flag with a comma-separated list of fingerprints from the report.
40
40
 
41
+ Findings can also be ignored in source code by placing `# spektr:ignore`, followed by one or more comma-separated check names, on the line immediately above them:
42
+
43
+ Use the check names shown in the report:
44
+
45
+ ```ruby
46
+ # spektr:ignore Arbitrary code execution -- input is validated before this method is called
47
+ eval(params[:code])
48
+
49
+ # spektr:ignore Arbitrary code execution, Dangerous send -- both uses are safe
50
+ eval(send(params[:method]))
51
+ ```
52
+
53
+ The directive only applies to the named checks on the next line; a directive without a check name has no effect. Keeping a reason after `--` is recommended.
41
54
 
42
55
  ### Railsgoat Example output
43
56
 
@@ -28,6 +28,7 @@ module Spektr
28
28
  def warn!(target, check, location, message, confidence = :high)
29
29
  full_path = target.is_a?(String) ? target : target.path
30
30
  path = full_path.gsub(@app.root, "")
31
+ return if target.respond_to?(:ignored_at?) && target.ignored_at?(location&.start_line, check)
31
32
  return if dupe?(path, location, message)
32
33
 
33
34
  @app.warnings << Warning.new(path, full_path, check, location, message, confidence)
@@ -64,7 +65,7 @@ module Spektr
64
65
  end
65
66
  when :keyword_hash_node, :hash_node
66
67
  node.elements.each do |element|
67
- return true if user_input?(element.key)
68
+ return true if element.respond_to?(:key) && user_input?(element.key)
68
69
  return true if user_input?(element.value)
69
70
  end
70
71
  when :array_node
@@ -84,7 +85,7 @@ module Spektr
84
85
  actions.each do |action|
85
86
  next unless action.body
86
87
  action.body.each do |exp|
87
- 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)
88
89
  end
89
90
  end
90
91
  when :local_variable_read_node
@@ -136,7 +137,7 @@ module Spektr
136
137
  end
137
138
  when :keyword_hash_node, :hash_node
138
139
  node.elements.each do |element|
139
- return true if model_attribute?(element.key)
140
+ return true if element.respond_to?(:key) && model_attribute?(element.key)
140
141
  return true if model_attribute?(element.value)
141
142
  end
142
143
  when :array_node
@@ -175,8 +176,9 @@ module Spektr
175
176
  }
176
177
  end
177
178
  actions.each do |action|
179
+ next unless action.body
178
180
  action.body.each do |exp|
179
- next unless node.respond_to?(:name)
181
+ next unless exp.respond_to?(:name) && node.respond_to?(:name)
180
182
  return model_attribute?(exp.value) if exp.is_a?(Prism::InstanceVariableWriteNode) && exp.name == node.name
181
183
  end
182
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"
@@ -16,7 +16,7 @@ module Spektr
16
16
  def run
17
17
  return unless super
18
18
  targets = ["Dir", "File", "IO", "Kernel", "Net::FTP", "Net::HTTP", "PStore", "Pathname", "Shell"]
19
- methods = [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
19
+ methods = [:[], :binread, :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
20
20
  targets.each do |target|
21
21
  methods.each do |method|
22
22
  check_calls_for_user_input(@target.find_calls(method, target.to_sym))
@@ -1,13 +1,17 @@
1
1
  module Spektr
2
2
  module Targets
3
3
  class Base < Prism::Visitor
4
+ IGNORE_DIRECTIVE = /^\s*#\s*spektr:ignore\s+(.+)$/i
5
+
4
6
  attr_accessor :path, :name, :options, :ast, :parent, :parent_modules, :methods, :calls, :interpolated_xstrings, :lvars
5
7
 
6
8
  def initialize(path, content)
7
9
  Spektr.logger.debug "loading #{path}"
10
+ @source_lines = content.lines
8
11
  @ast = Prism.parse(content)
9
12
  @path = path
10
13
  return unless @ast
14
+ @name = ""
11
15
  @parent = ""
12
16
  @parent_modules = []
13
17
  @methods = []
@@ -20,6 +24,16 @@ module Spektr
20
24
  @name = @name.prepend("#{@parent_modules.map(&:name).join('::')}::") if @name && @parent_modules.any?
21
25
  end
22
26
 
27
+ def ignored_at?(line_number, check)
28
+ return false unless line_number && line_number > 1
29
+
30
+ directive = @source_lines[line_number - 2]&.match(IGNORE_DIRECTIVE)
31
+ return false unless directive
32
+
33
+ ignored_checks = directive[1].split(/\s+--\s+/, 2).first.split(',').map(&:strip)
34
+ ignored_checks.include?(check.name)
35
+ end
36
+
23
37
  def method_definitions
24
38
  @method_definitions ||= find_methods(node: @ast)
25
39
  end
@@ -76,12 +90,12 @@ module Spektr
76
90
  @parent = node.superclass.receiver.name.to_s
77
91
  when Prism::ConstantPathNode, Prism::ConstantReadNode
78
92
  @parent = node.superclass.name.to_s
79
- @parent.prepend("#{node.superclass.parent.name}::") if node.superclass.respond_to?(:parent)
80
- 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
81
95
  @parent.prepend("#{node.superclass.parent.parent.name}::")
82
96
  end
83
97
  end
84
- 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
85
99
  @parent = node.constant_path.parent.name.to_s
86
100
  end
87
101
  @parent = @parent.prepend("#{@parent_modules.map(&:name).join('::')}::") if @parent_modules.any?
@@ -89,7 +103,7 @@ module Spektr
89
103
  end
90
104
 
91
105
  def visit_module_node(node)
92
- @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
93
107
  @parent_modules << node
94
108
  super
95
109
  end
@@ -1,3 +1,3 @@
1
1
  module Spektr
2
- VERSION = '0.5.6'
2
+ VERSION = '1.0.1'
3
3
  end
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: 0.5.6
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Molnar
@@ -334,7 +334,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
334
334
  - !ruby/object:Gem::Version
335
335
  version: '0'
336
336
  requirements: []
337
- rubygems_version: 3.6.9
337
+ rubygems_version: 4.0.3
338
338
  specification_version: 4
339
339
  summary: Rails static code analyzer for security issues
340
340
  test_files: []