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.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +14 -2
- data/.rubocop.yml +2 -1
- data/.rubocop_todo.yml +12 -0
- data/.tests_mapping.yml +10 -0
- data/Gemfile +9 -4
- data/README.md +9 -8
- data/gitlab-styles.gemspec +7 -7
- data/lefthook.yml +11 -3
- data/lib/gitlab/styles/version.rb +1 -1
- data/lib/rubocop/cop/active_record_dependent.rb +0 -5
- data/lib/rubocop/cop/active_record_serialize.rb +0 -6
- data/lib/rubocop/cop/avoid_return_from_blocks.rb +4 -4
- data/lib/rubocop/cop/gem_fetcher.rb +18 -20
- data/lib/rubocop/cop/gitlab_security/deep_munge.rb +36 -0
- data/lib/rubocop/cop/gitlab_security/json_serialization.rb +133 -0
- data/lib/rubocop/cop/gitlab_security/public_send.rb +47 -0
- data/lib/rubocop/cop/gitlab_security/redirect_to_params_update.rb +38 -0
- data/lib/rubocop/cop/gitlab_security/send_file_params.rb +40 -0
- data/lib/rubocop/cop/gitlab_security/sql_injection.rb +41 -0
- data/lib/rubocop/cop/gitlab_security/system_command_injection.rb +38 -0
- data/lib/rubocop/cop/in_batches.rb +0 -2
- data/lib/rubocop/cop/line_break_after_guard_clauses.rb +3 -5
- data/lib/rubocop/cop/line_break_around_conditional_block.rb +5 -0
- data/lib/rubocop/cop/migration/update_large_table.rb +1 -0
- data/lib/rubocop/cop/polymorphic_associations.rb +0 -5
- data/lib/rubocop/cop/rails/include_url_helper.rb +0 -2
- data/lib/rubocop/cop/redirect_with_status.rb +44 -30
- data/lib/rubocop/cop/rspec/empty_line_after_shared_example.rb +1 -1
- data/rubocop-bundler.yml +10 -0
- data/rubocop-capybara.yml +8 -0
- data/rubocop-default.yml +1 -1
- data/rubocop-layout.yml +48 -4
- data/rubocop-lint.yml +131 -3
- data/rubocop-naming.yml +5 -0
- data/rubocop-performance.yml +32 -0
- data/rubocop-rails.yml +25 -0
- data/rubocop-rspec.yml +1 -5
- data/rubocop-security.yml +19 -1
- data/rubocop-style.yml +18 -3
- metadata +38 -29
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be58fa1f46b81e0e8a73c3c85843048680518c23e846bcf54a1df301c53cfa18
|
4
|
+
data.tar.gz: 95f2466fb2ae6f5dcfcfce8b7e9343101588b9627f0c9c061c53beac597126e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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'
|
data/.tests_mapping.yml
ADDED
data/Gemfile
CHANGED
@@ -6,12 +6,17 @@ source 'https://rubygems.org'
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
group :development do
|
9
|
-
gem
|
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.
|
15
|
-
gem 'rubocop-rspec', '2.
|
16
|
-
gem 'rspec-parameterized', '0.
|
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.
|
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
|
data/gitlab-styles.gemspec
CHANGED
@@ -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.
|
26
|
-
spec.add_dependency 'rubocop-
|
27
|
-
spec.add_dependency 'rubocop-
|
28
|
-
spec.add_dependency 'rubocop-
|
29
|
-
spec.add_dependency 'rubocop-
|
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', '~>
|
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
|
13
|
+
# Run only relevant specs.
|
14
14
|
rspec:
|
15
|
-
|
16
|
-
|
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
|
@@ -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
|
-
|
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|
|
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
|
75
|
-
|
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
|
-
|
12
|
+
# See https://bundler.io/guides/git.html#custom-git-sources
|
13
|
+
GIT_SOURCES = %i[git github gist bitbucket].freeze
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
27
|
+
def on_send(node)
|
28
|
+
pair_node = gem_option(node)
|
29
|
+
return unless pair_node
|
26
30
|
|
27
|
-
|
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
|