gitlab-styles 9.1.0 → 10.0.0

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.
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