gitlab-styles 9.1.0 → 10.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +14 -2
  3. data/.rubocop.yml +2 -1
  4. data/.rubocop_todo.yml +12 -0
  5. data/.tests_mapping.yml +10 -0
  6. data/Gemfile +9 -4
  7. data/README.md +9 -8
  8. data/gitlab-styles.gemspec +7 -7
  9. data/lefthook.yml +11 -3
  10. data/lib/gitlab/styles/version.rb +1 -1
  11. data/lib/rubocop/cop/active_record_dependent.rb +0 -5
  12. data/lib/rubocop/cop/active_record_serialize.rb +0 -6
  13. data/lib/rubocop/cop/avoid_return_from_blocks.rb +4 -4
  14. data/lib/rubocop/cop/gem_fetcher.rb +18 -20
  15. data/lib/rubocop/cop/gitlab_security/deep_munge.rb +36 -0
  16. data/lib/rubocop/cop/gitlab_security/json_serialization.rb +133 -0
  17. data/lib/rubocop/cop/gitlab_security/public_send.rb +47 -0
  18. data/lib/rubocop/cop/gitlab_security/redirect_to_params_update.rb +38 -0
  19. data/lib/rubocop/cop/gitlab_security/send_file_params.rb +40 -0
  20. data/lib/rubocop/cop/gitlab_security/sql_injection.rb +41 -0
  21. data/lib/rubocop/cop/gitlab_security/system_command_injection.rb +38 -0
  22. data/lib/rubocop/cop/in_batches.rb +0 -2
  23. data/lib/rubocop/cop/line_break_after_guard_clauses.rb +3 -5
  24. data/lib/rubocop/cop/line_break_around_conditional_block.rb +5 -0
  25. data/lib/rubocop/cop/migration/update_large_table.rb +1 -0
  26. data/lib/rubocop/cop/polymorphic_associations.rb +0 -5
  27. data/lib/rubocop/cop/rails/include_url_helper.rb +0 -2
  28. data/lib/rubocop/cop/redirect_with_status.rb +44 -30
  29. data/lib/rubocop/cop/rspec/empty_line_after_shared_example.rb +1 -1
  30. data/rubocop-bundler.yml +10 -0
  31. data/rubocop-capybara.yml +8 -0
  32. data/rubocop-default.yml +1 -1
  33. data/rubocop-layout.yml +48 -4
  34. data/rubocop-lint.yml +131 -3
  35. data/rubocop-naming.yml +5 -0
  36. data/rubocop-performance.yml +32 -0
  37. data/rubocop-rails.yml +25 -0
  38. data/rubocop-rspec.yml +1 -5
  39. data/rubocop-security.yml +19 -1
  40. data/rubocop-style.yml +18 -3
  41. metadata +38 -29
  42. data/lib/gitlab/styles/rubocop/model_helpers.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50eab9da6dd93cfbda3c8a8877e6ac2f8ce8274d6f92116973e6c9c1ee590d5f
4
- data.tar.gz: f7e187d901c89bb4d4ba4a40d72523b10c5148ef965d60b243e8c09c9bc21642
3
+ metadata.gz: be58fa1f46b81e0e8a73c3c85843048680518c23e846bcf54a1df301c53cfa18
4
+ data.tar.gz: 95f2466fb2ae6f5dcfcfce8b7e9343101588b9627f0c9c061c53beac597126e9
5
5
  SHA512:
6
- metadata.gz: a9d90f34b3a3e6d97c6a791c5f91a26318b956751a77b2827f5370e15f8717bdf30cf30ddad0c63afd1f2335707d76dd948d1c7898c0d762f9b19389dea55d49
7
- data.tar.gz: 9634b21fcc052557e12ebd277279089e4cfd325b88860c45a5250da554b0f14e378b7cdf8c9648d019649cd1396b327fc7cf10d48462622f00f1af888cd61b31
6
+ metadata.gz: cb13dbe44128173fc03b4dddf53cfdfb67a6e8d37ffc6f4c32a4e30c33ead6e686b6023c2cff9fe7fbf95600d518b508fb9b9b68ffbb07dc8686765f65f3f638
7
+ data.tar.gz: 6a2774df245a7c42725e8989a8683b4953d91f70f059c0ceb41450b1c305b375f4e150ecb9418a56f60833ce166eca335ce94ba3a03fb7cf29575c7b72cbd54f
data/.gitlab-ci.yml CHANGED
@@ -25,15 +25,27 @@ styles:
25
25
  - bundle exec rubocop --debug --parallel
26
26
  parallel:
27
27
  matrix:
28
- - RUBY_VERSION: ['2.7', '3.0']
28
+ - RUBY_VERSION: ['2.7', '3.0', '3.1', '3.2']
29
29
 
30
30
  specs:
31
31
  stage: test
32
32
  script:
33
+ # Disable simplecov for all Ruby version other than 3.0
34
+ - if [[ "$RUBY_VERSION" != "3.0" ]]; then export SIMPLECOV=0; fi
33
35
  - bundle exec rspec
34
36
  parallel:
35
37
  matrix:
36
- - RUBY_VERSION: ['2.7', '3.0']
38
+ - RUBY_VERSION: ['2.7', '3.0', '3.1', '3.2']
39
+ artifacts:
40
+ name: coverage
41
+ expire_in: 31d
42
+ paths:
43
+ - coverage/index.html
44
+ - coverage/assets/
45
+ reports:
46
+ coverage_report:
47
+ coverage_format: cobertura
48
+ path: coverage/coverage.xml
37
49
 
38
50
  include:
39
51
  - project: 'gitlab-org/quality/pipeline-common'
data/.rubocop.yml CHANGED
@@ -1,12 +1,13 @@
1
1
  inherit_from:
2
+ - .rubocop_todo.yml
2
3
  - rubocop-default.yml
3
4
 
4
5
  require:
5
6
  - rubocop/cop/internal_affairs
7
+ - rubocop-rake
6
8
 
7
9
  AllCops:
8
10
  NewCops: disable # https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/issues/40
9
- SuggestExtensions: false # https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/issues/39
10
11
 
11
12
  InternalAffairs/DeprecateCopHelper:
12
13
  Enabled: true
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,12 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2023-01-11 12:59:00 UTC using RuboCop version 1.43.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ InternalAffairs/InheritDeprecatedCopClass:
11
+ Exclude:
12
+ - 'lib/rubocop/cop/gitlab_security/json_serialization.rb'
@@ -0,0 +1,10 @@
1
+ ---
2
+ mapping:
3
+ - source: 'lib/(.+)\.rb'
4
+ test: 'spec/%s_spec.rb'
5
+
6
+ - source: 'rubocop-.*\.yml'
7
+ test: 'spec/yml_spec.rb'
8
+
9
+ - source: '(spec/.*_spec\.rb)'
10
+ test: '%s'
data/Gemfile CHANGED
@@ -6,12 +6,17 @@ source 'https://rubygems.org'
6
6
  gemspec
7
7
 
8
8
  group :development do
9
- gem "lefthook", require: false
9
+ gem 'lefthook', require: false
10
+ gem 'test_file_finder', '~> 0.1.4'
10
11
  end
11
12
 
12
13
  group :test do
13
14
  # Pin these dependencies, otherwise a new rule could break the CI pipelines
14
- gem 'rubocop', '1.38.0'
15
- gem 'rubocop-rspec', '2.15.0'
16
- gem 'rspec-parameterized', '0.5.2', require: false
15
+ gem 'rubocop', '1.43.0'
16
+ gem 'rubocop-rspec', '2.18.1'
17
+ gem 'rspec-parameterized-table_syntax', '1.0.0', require: false
18
+
19
+ gem 'simplecov', '~> 0.22.0', require: false
20
+ gem 'simplecov-html', '~> 0.12.3', require: false
21
+ gem 'simplecov-cobertura', '~> 2.1.0', require: false
17
22
  end
data/README.md CHANGED
@@ -80,13 +80,6 @@ bundle exec rubocop -c .rubocop.yml
80
80
  lefthook install
81
81
  ```
82
82
 
83
- ## Contributing
84
-
85
- Bug reports and merge requests are welcome on GitLab at
86
- https://gitlab.com/gitlab-org/gitlab-styles. This project is intended to be a
87
- safe, welcoming space for collaboration, and contributors are expected to adhere
88
- to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
89
-
90
83
  ## Release Process
91
84
 
92
85
  We release `gitlab-styles` on an ad-hoc basis. There is no regularity to when
@@ -98,10 +91,18 @@ To release a new version:
98
91
  1. Create a Merge Request.
99
92
  1. Use Merge Request template [Release.md](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/.gitlab/merge_request_templates/Release.md).
100
93
  1. Follow the instructions.
101
- 1. A new gem version is [published automatically](https://gitlab.com/gitlab-org/quality/pipeline-common/-/blob/master/ci/gem-release.yml) after the Merge Request has been merged.
94
+ 1. (Optional, but appreciated) Create an MR on `gitlab-org/gitlab` project [with the `New Version of gitlab-styles.md` template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/merge_request_templates/New%20Version%20of%20gitlab-styles.md) to test the new version of `gitlab-styles`, and follow the MR instructions.
95
+ 1. After the Merge Request has been merged, a new gem version is [published automatically](https://gitlab.com/gitlab-org/quality/pipeline-common/-/blob/master/ci/gem-release.yml)
102
96
 
103
97
  See [!123](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/merge_requests/123) as an example.
104
98
 
99
+ ## Contributing
100
+
101
+ Bug reports and merge requests are welcome on GitLab at
102
+ https://gitlab.com/gitlab-org/gitlab-styles. This project is intended to be a
103
+ safe, welcoming space for collaboration, and contributors are expected to adhere
104
+ to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
105
+
105
106
  ## License
106
107
 
107
108
  The gem is available as open source under the terms of the
@@ -22,15 +22,15 @@ Gem::Specification.new do |spec|
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_dependency 'rubocop', '~> 1.38.0'
26
- spec.add_dependency 'rubocop-gitlab-security', '~> 0.1.1'
27
- spec.add_dependency 'rubocop-graphql', '~> 0.14'
28
- spec.add_dependency 'rubocop-performance', '~> 1.14'
29
- spec.add_dependency 'rubocop-rails', '~> 2.15'
30
- spec.add_dependency 'rubocop-rspec', '~> 2.15'
25
+ spec.add_dependency 'rubocop', '~> 1.43.0'
26
+ spec.add_dependency 'rubocop-graphql', '~> 0.18'
27
+ spec.add_dependency 'rubocop-performance', '~> 1.15'
28
+ spec.add_dependency 'rubocop-rails', '~> 2.17'
29
+ spec.add_dependency 'rubocop-rspec', '~> 2.18'
31
30
 
32
31
  spec.add_development_dependency 'bundler', '~> 2.1'
33
32
  spec.add_development_dependency 'gitlab-dangerfiles', '~> 2.11.0'
34
- spec.add_development_dependency 'rake', '~> 10.0'
33
+ spec.add_development_dependency 'rake', '~> 13.0'
35
34
  spec.add_development_dependency 'rspec', '~> 3.0'
35
+ spec.add_development_dependency 'rubocop-rake', '~> 0.6'
36
36
  end
data/lefthook.yml CHANGED
@@ -10,7 +10,15 @@ pre-push:
10
10
  glob: '*.{rb,rake}'
11
11
  run: bundle exec rubocop --parallel --force-exclusion {files}
12
12
 
13
- # Run all tests (warn if there are any missing tools required for tests).
13
+ # Run only relevant specs.
14
14
  rspec:
15
- run: bundle exec rspec -f progress
16
- glob: '*.rb'
15
+ files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD
16
+ run: |
17
+ tests=$(tff --mapping-file .tests_mapping.yml {files})
18
+ if [ "$tests" != "" ]; then
19
+ echo "bundle exec rspec --format progress $tests"
20
+ bundle exec rspec --format progress $tests
21
+ else
22
+ echo "No specs to run."
23
+ exit 0
24
+ fi
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Styles
5
- VERSION = '9.1.0'
5
+ VERSION = '10.0.0'
6
6
  end
7
7
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../gitlab/styles/rubocop/model_helpers'
4
-
5
3
  module Rubocop
6
4
  module Cop
7
5
  # Cop that prevents the use of `dependent: ...` in ActiveRecord models.
8
6
  class ActiveRecordDependent < RuboCop::Cop::Base
9
- include Gitlab::Styles::Rubocop::ModelHelpers
10
-
11
7
  MSG = 'Do not use `dependent:` to remove associated data, ' \
12
8
  'use foreign keys with cascading deletes instead.'
13
9
 
@@ -15,7 +11,6 @@ module Rubocop
15
11
  ALLOWED_OPTIONS = [:restrict_with_error].freeze
16
12
 
17
13
  def on_send(node)
18
- return unless in_model?(node)
19
14
  return unless METHOD_NAMES.include?(node.children[1])
20
15
 
21
16
  node.children.last.each_node(:pair) do |pair|
@@ -1,18 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../gitlab/styles/rubocop/model_helpers'
4
-
5
3
  module Rubocop
6
4
  module Cop
7
5
  # Cop that prevents the use of `serialize` in ActiveRecord models.
8
6
  class ActiveRecordSerialize < RuboCop::Cop::Base
9
- include Gitlab::Styles::Rubocop::ModelHelpers
10
-
11
7
  MSG = 'Do not store serialized data in the database, use separate columns and/or tables instead'
12
8
 
13
9
  def on_send(node)
14
- return unless in_model?(node)
15
-
16
10
  add_offense(node.loc.selector) if node.children[1] == :serialize
17
11
  end
18
12
  end
@@ -23,7 +23,7 @@ module Rubocop
23
23
  class AvoidReturnFromBlocks < RuboCop::Cop::Base
24
24
  MSG = 'Do not return from a block, use next or break instead.'
25
25
  DEF_METHODS = %i[define_method lambda].freeze
26
- WHITELISTED_METHODS = %i[each each_filename times loop].freeze
26
+ ALLOWED_METHODS = %i[each each_filename times loop].freeze
27
27
 
28
28
  def on_block(node)
29
29
  block_body = node.body
@@ -32,7 +32,7 @@ module Rubocop
32
32
  return unless top_block?(node)
33
33
 
34
34
  block_body.each_node(:return) do |return_node|
35
- next if parent_blocks(node, return_node).all? { |block| whitelisted?(block) }
35
+ next if parent_blocks(node, return_node).all? { |block| allowed?(block) }
36
36
 
37
37
  add_offense(return_node)
38
38
  end
@@ -71,8 +71,8 @@ module Rubocop
71
71
  (node.block_type? && DEF_METHODS.include?(node.method_name))
72
72
  end
73
73
 
74
- def whitelisted?(block_node)
75
- WHITELISTED_METHODS.include?(block_node.method_name)
74
+ def allowed?(block_node)
75
+ ALLOWED_METHODS.include?(block_node.method_name)
76
76
  end
77
77
  end
78
78
  end
@@ -6,31 +6,29 @@ module Rubocop
6
6
  # `Gemfile` in order to avoid additional points of failure beyond
7
7
  # rubygems.org.
8
8
  class GemFetcher < RuboCop::Cop::Base
9
- MSG = 'Do not use gems from git repositories, only use gems from RubyGems.'
9
+ MSG = 'Do not use gems from git repositories, only use gems from RubyGems or vendored gems. ' \
10
+ 'See https://docs.gitlab.com/ee/development/gemfile.html#no-gems-fetched-from-git-repositories'
10
11
 
11
- GIT_KEYS = [:git, :github].freeze
12
+ # See https://bundler.io/guides/git.html#custom-git-sources
13
+ GIT_SOURCES = %i[git github gist bitbucket].freeze
12
14
 
13
- def on_send(node)
14
- return unless gemfile?(node)
15
-
16
- func_name = node.children[1]
17
- return unless func_name == :gem
15
+ # @!method gem_option(node)
16
+ def_node_matcher :gem_option, <<~PATTERN
17
+ (send nil? :gem _ ...
18
+ (hash
19
+ <$(pair (sym {#{GIT_SOURCES.map(&:inspect).join(' ')}}) _)
20
+ ...>
21
+ )
22
+ )
23
+ PATTERN
18
24
 
19
- node.children.last.each_node(:pair) do |pair|
20
- key_name = pair.children[0].children[0].to_sym
21
- add_offense(pair.source_range) if GIT_KEYS.include?(key_name)
22
- end
23
- end
25
+ RESTRICT_ON_SEND = %i[gem].freeze
24
26
 
25
- private
27
+ def on_send(node)
28
+ pair_node = gem_option(node)
29
+ return unless pair_node
26
30
 
27
- def gemfile?(node)
28
- node
29
- .location
30
- .expression
31
- .source_buffer
32
- .name
33
- .end_with?("Gemfile")
31
+ add_offense(pair_node)
34
32
  end
35
33
  end
36
34
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Checks for disabling the deep munge security control.
7
+ #
8
+ # Disabling this security setting can leave the application open to unsafe
9
+ # query generation
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # config.action_dispatch.perform_deep_munge = false
15
+ #
16
+ # See CVE-2012-2660, CVE-2012-2694, and CVE-2013-0155.
17
+ class DeepMunge < RuboCop::Cop::Base
18
+ MSG = 'Never disable the deep munge security option.'
19
+
20
+ # @!method disable_deep_munge?(node)
21
+ def_node_matcher :disable_deep_munge?, <<-PATTERN
22
+ (send
23
+ (send (send nil? :config) :action_dispatch) :perform_deep_munge=
24
+ { (false) (send true :!) }
25
+ )
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ return unless disable_deep_munge?(node)
30
+
31
+ add_offense(node.loc.selector)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Checks for `to_json` / `as_json` without allowing via `only`.
7
+ #
8
+ # Either method called on an instance of a `Serializer` class will be
9
+ # ignored. Associations included via `include` are subject to the same
10
+ # rules.
11
+ #
12
+ # @example
13
+ #
14
+ # # bad
15
+ # render json: @user.to_json
16
+ # render json: @user.to_json(except: %i[password])
17
+ # render json: @user.to_json(
18
+ # only: %i[username],
19
+ # include: [:identities]
20
+ # )
21
+ #
22
+ # # acceptable
23
+ # render json: UserSerializer.new.to_json
24
+ #
25
+ # # good
26
+ # render json: @user.to_json(only: %i[name username])
27
+ # render json: @user.to_json(
28
+ # only: %i[username],
29
+ # include: { identities: { only: %i[provider] } }
30
+ # )
31
+ #
32
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/29661
33
+ class JsonSerialization < RuboCop::Cop::Cop
34
+ MSG = "Don't use `%s` without specifying `only`"
35
+
36
+ # Check for `to_json` sent to any object that's not a Hash literal or
37
+ # Serializer instance
38
+ # @!method json_serialization?(node)
39
+ def_node_matcher :json_serialization?, <<~PATTERN
40
+ (send !{nil? hash #serializer?} ${:to_json :as_json} $...)
41
+ PATTERN
42
+
43
+ # Check if node is a `only: ...` pair
44
+ # @!method only_pair?(node)
45
+ def_node_matcher :only_pair?, <<~PATTERN
46
+ (pair (sym :only) ...)
47
+ PATTERN
48
+
49
+ # Check if node is a `include: {...}` pair
50
+ # @!method include_pair?(node)
51
+ def_node_matcher :include_pair?, <<~PATTERN
52
+ (pair (sym :include) (hash $...))
53
+ PATTERN
54
+
55
+ # Check for a `only: [...]` pair anywhere in the node
56
+ # @!method contains_only?(node)
57
+ def_node_search :contains_only?, <<~PATTERN
58
+ (pair (sym :only) (array ...))
59
+ PATTERN
60
+
61
+ # Check for `SomeConstant.new`
62
+ # @!method constant_init(node)
63
+ def_node_search :constant_init, <<~PATTERN
64
+ (send (const nil? $_) :new ...)
65
+ PATTERN
66
+
67
+ def on_send(node)
68
+ matched = json_serialization?(node)
69
+ return unless matched
70
+
71
+ @_has_top_level_only = false
72
+ @method = matched.first
73
+
74
+ if matched.last.nil? || matched.last.empty?
75
+ # Empty `to_json` call
76
+ add_offense(node, location: :selector, message: format_message)
77
+ else
78
+ check_arguments(node, matched)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def format_message
85
+ format(MSG, @method)
86
+ end
87
+
88
+ def serializer?(node)
89
+ constant_init(node).any? { |name| name.to_s.end_with?('Serializer') }
90
+ end
91
+
92
+ def check_arguments(node, matched)
93
+ options = matched.last.first
94
+
95
+ # If `to_json` was given an argument that isn't a Hash, we don't
96
+ # know what to do here, so just move along
97
+ return unless options.hash_type?
98
+
99
+ options.each_child_node do |child_node|
100
+ check_pair(child_node)
101
+ end
102
+
103
+ return unless requires_only?
104
+
105
+ # Add a top-level offense for the entire argument list, but only if
106
+ # we haven't yet added any offenses to the child Hash values (such
107
+ # as `include`)
108
+ add_offense(node.children.last, message: format_message)
109
+ end
110
+
111
+ def check_pair(pair)
112
+ if only_pair?(pair)
113
+ @_has_top_level_only = true
114
+ elsif include_pair?(pair)
115
+ includes = pair.value
116
+
117
+ includes.each_child_node do |child_node|
118
+ next if contains_only?(child_node)
119
+
120
+ add_offense(child_node, message: format_message)
121
+ end
122
+ end
123
+ end
124
+
125
+ def requires_only?
126
+ return false if @_has_top_level_only
127
+
128
+ offenses.count.zero?
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Checks for the use of `public_send`, `send`, and `__send__` methods.
7
+ #
8
+ # If passed untrusted input these methods can be used to execute arbitrary
9
+ # methods on behalf of an attacker.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # myobj.public_send("#{params[:foo]}")
15
+ #
16
+ # # good
17
+ # case params[:foo].to_s
18
+ # when 'choice1'
19
+ # items.choice1
20
+ # when 'choice2'
21
+ # items.choice2
22
+ # when 'choice3'
23
+ # items.choice3
24
+ # end
25
+ class PublicSend < RuboCop::Cop::Base
26
+ MSG = 'Avoid using `%s`.'
27
+
28
+ RESTRICT_ON_SEND = %i[send public_send __send__].freeze
29
+
30
+ # @!method send?(node)
31
+ def_node_matcher :send?, <<-PATTERN
32
+ ({csend | send} _ ${:send :public_send :__send__} ...)
33
+ PATTERN
34
+
35
+ def on_send(node)
36
+ send?(node) do |match|
37
+ next unless node.arguments?
38
+
39
+ add_offense(node.loc.selector, message: format(MSG, match))
40
+ end
41
+ end
42
+
43
+ alias_method :on_csend, :on_send
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Check for use of redirect_to(params.update())
7
+ #
8
+ # Passing user params to the redirect_to method provides an open redirect
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # redirect_to(params.update(action: 'main'))
14
+ #
15
+ # # good
16
+ # redirect_to(allowed(params))
17
+ #
18
+ class RedirectToParamsUpdate < RuboCop::Cop::Base
19
+ MSG = 'Avoid using `redirect_to(params.%<name>s(...))`. ' \
20
+ 'Only pass allowed arguments into redirect_to() (e.g. not including `host`)'
21
+
22
+ # @!method redirect_to_params_update_node(node)
23
+ def_node_matcher :redirect_to_params_update_node, <<-PATTERN
24
+ (send nil? :redirect_to $(send (send nil? :params) ${:update :merge} ...))
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ selected, name = redirect_to_params_update_node(node)
29
+ return unless name
30
+
31
+ message = format(MSG, name: name)
32
+
33
+ add_offense(selected, message: message)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Check for use of send_file(..., params[], ...)
7
+ #
8
+ # Passing user params to the send_file() method allows directory traversal
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # send_file("/tmp/myproj/" + params[:filename])
14
+ #
15
+ # # good (verify directory)
16
+
17
+ # basename = File.expand_path("/tmp/myproj")
18
+ # filename = File.expand_path(File.join(basename, @file.public_filename))
19
+ # raise if basename != filename
20
+ # send_file filename, disposition: 'inline'
21
+ #
22
+ class SendFileParams < RuboCop::Cop::Base
23
+ MSG = 'Do not pass user provided params directly to send_file(), ' \
24
+ 'verify the path with file.expand_path() first.'
25
+
26
+ # @!method params_node?(node)
27
+ def_node_search :params_node?, <<-PATTERN
28
+ (send (send nil? :params) ... )
29
+ PATTERN
30
+
31
+ def on_send(node)
32
+ return unless node.command?(:send_file)
33
+ return unless node.arguments.any? { |e| params_node?(e) }
34
+
35
+ add_offense(node.loc.selector)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module GitlabSecurity
6
+ # Check for use of where("name = '#{params[:name]}'")
7
+ #
8
+ # Passing user input to where() without parameterization can result in SQL Injection
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # u = User.where("name = '#{params[:name]}'")
14
+ #
15
+ # # good (parameters)
16
+ # u = User.where("name = ? AND id = ?", params[:name], params[:id])
17
+ # u = User.where(name: params[:name], id: params[:id])
18
+ #
19
+ class SqlInjection < RuboCop::Cop::Base
20
+ MSG = 'Parameterize all user-input passed to where(), do not directly embed user input in SQL queries.'
21
+
22
+ # @!method where_user_input?(node)
23
+ def_node_matcher :where_user_input?, <<-PATTERN
24
+ (send _ :where ...)
25
+ PATTERN
26
+
27
+ # @!method string_var_string?(node)
28
+ def_node_matcher :string_var_string?, <<-PATTERN
29
+ (dstr (str ...) (begin ...) (str ...) ...)
30
+ PATTERN
31
+
32
+ def on_send(node)
33
+ return unless where_user_input?(node)
34
+ return unless node.arguments.any? { |e| string_var_string?(e) }
35
+
36
+ add_offense(node.loc.selector)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end