betterlint 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 540ac996157d2947a10918bc50b385456ef3f861ec04159968da0733e0a35e02
4
+ data.tar.gz: 5c2d73e4041a2224abfdbd39a6777ae0ac7ccb99c1e03ba8c9bfd5d0d3ba4cad
5
+ SHA512:
6
+ metadata.gz: 80a04cb42b4ce134acc6481e79e885ff97e1ce93498599458dea26fc810c64798dedab977ef7dfc0da8aef456a4ac194384db1191fbd818f491b0b10db9336b5
7
+ data.tar.gz: 26c35bc52635ed689783177f15020091e577df1abb779a9c95547ae428388f41af84420b471034ca283746e9dd2022ef99e98e885553c977964006de9efafde2
data/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Betterlint
2
+
3
+ Shared rubocop configuration for Betterment Rails apps/engines.
4
+
5
+ Check out the [styleguide](STYLEGUIDE.md) for some additional commentary on our cop configurations.
6
+
7
+ ## Installation
8
+
9
+ Gemfile:
10
+
11
+ ```ruby
12
+ gem 'betterlint'
13
+ ```
14
+
15
+ .rubocop.yml:
16
+
17
+ ```yml
18
+ inherit_gem:
19
+ betterlint:
20
+ - config/default.yml
21
+ ```
22
+
23
+ ## Dependencies
24
+
25
+ This gem depends on the following other gems:
26
+
27
+ - rubocop
28
+ - rubocop-rspec
29
+
30
+ ## Custom Cops
31
+
32
+ All cops are located under [`lib/rubocop/cop/betterment`](lib/rubocop/cop/betterment)
33
+
34
+ ### Betterment/AuthorizationInController
35
+
36
+ This cop looks for unsafe handling of id-like parameters in controllers that may lead to [insecure direct object reference vulnerabilities](https://portswigger.net/web-security/access-control/idor). It does this by tracking methods that retrieve input from the client and variables that hold onto these values. Any models initialized or updated using these values will then be flagged by the cop. Take this example controller:
37
+
38
+ ```ruby
39
+ class Controller
40
+ def create_params
41
+ params.permit(:user_id, :language)
42
+ end
43
+
44
+ def create
45
+ info = params.permit(:user_id)
46
+ Model.new(user_id: info[:user_id], language: params[:language])
47
+ Model.new(user_id: params[:user_id], language: params[:language])
48
+ Model.new(create_params)
49
+ end
50
+ end
51
+ ```
52
+
53
+ All three `Model.new` calls may be susceptible to an insecure direct object reference vulnerability. This may end up letting attackers read and write content belonging to other users. To address these vulnerabilities, some form of authorization will be needed to ensure that the user issuing this request is allowed to create a `Model` that references the specific `user_id`. To get a better understanding of what this cop flags and doesn't flag, take a look at its [spec](spec/rubocop/cop/betterment/authorization_in_controller_spec.rb).
54
+
55
+ In cases where more fine-grained control over what parameters are considered sensitive is desired, two configuration options can be used: `unsafe_parameters` and `unsafe_regex`. By default this cop will flag unsafe uses of any parameters whose names end in `_id`, but additional parameters can be specified by configuring `unsafe_parameters`. In cases where the default pattern of `.*_id` is insufficient or incorrect, this regex can be swapped out by specifying the `unsafe_regex` configuration option. In total, this cop will flag any parameters whose names are on the `unsafe_parameters` list or matches the `unsafe_regex` pattern.
56
+
57
+ This is what a full configuration of this cop may look like:
58
+
59
+ ```yaml
60
+ Betterment/AuthorizationInController:
61
+ # Limit this cop just to controllers
62
+ Include:
63
+ - 'app/controllers/**/*.rb'
64
+ unsafe_parameters:
65
+ - username
66
+ - misc_unsafe_parameter
67
+ unsafe_regex: '.*_id$'
68
+ ```
69
+
70
+ ### Betterment/UnscopedFind
71
+
72
+ This cop flags code that passes user input directly into a `find`-like call that may lead to authorization issues (such as [indirect object reference vulnerabilities](https://portswigger.net/web-security/access-control/idor)). For example, a controller that uses user input to find a document will need to ensure that the user is authorized to access that document. Take the following sample:
73
+
74
+ ```ruby
75
+ class Controller
76
+ def index
77
+ @document = Document.find(params[:document_id])
78
+ end
79
+ end
80
+ ```
81
+
82
+ In this case, `@document` may not belong to the user and authorization will have to be done somewhere else, potentially introducing a vulnerability. One way to address this violation is to replace the `Document.find(...)` call with a `current_user.documents.find(...)` call. This fails fast when `current_user` is not authorized to access the document, without an extra authorization check that a `Document.find` call would require.
83
+
84
+ When dealing with models whose data is not ever considered private, it may make sense to add them to the `unauthenticated_models` configuration option. For example, reference data such as `ZipCode` or `Language` may be represented using models, but may not make sense to enforce any form of authentication. Take the sample controller below:
85
+
86
+ ```ruby
87
+ class Controller < UnauthenticatedWebappController
88
+ def index
89
+ @language = Language.find(params[:language])
90
+ @zip = ZipCode.find(params[:zip])
91
+ end
92
+ end
93
+ ```
94
+
95
+ There is nothing specific to a user or otherwise anything sensitive about `Language` or `ZipCode`. The cop can be configured to treat these models as unauthenticated so that calling `find`-like methods with them will not trigger any violations:
96
+
97
+ ```yaml
98
+ Betterment/UnscopedFind:
99
+ unauthenticated_models:
100
+ - Language
101
+ - ZipCode
102
+ ```
103
+
104
+ ### Betterment/DynamicParams
105
+
106
+ This cop flags code that accesses parameters whose names may be dynamically generated, such as a list of parameters in an a global variable or a return value from a method. In some cases, dynamically accessing parameter names can obscure what the client is expected to send and may make it difficult to reason about the code, both manually and programmatically. For example:
107
+
108
+ ```ruby
109
+ class Controller
110
+ def create_param_names
111
+ %i(user_id first_name last_name)
112
+ end
113
+
114
+ def create
115
+ parameter_name = :user_id
116
+ params.permit(parameter_name)
117
+ params.permit(create_params_names)
118
+ params.permit(%w(blog post comment).flat_map { |p| ["#{p}_name", "#{p}_title"] })
119
+ end
120
+ end
121
+ ```
122
+
123
+ All three `params.permit` calls will be flagged.
124
+
125
+ ### Betterment/UnsafeJob
126
+
127
+ This cop flags delayed jobs (e.g. ActiveJob, delayed_job) whose classes accept sensitive data via a `perform` or `initialize` method. Jobs are serialized in plaintext, so any sensitive data they accept will be accessible in plaintext to everyone with database access. Instead, consider passing ActiveRecord instances that appropriately handle sensitive data (e.g. encrypted at rest and decrypted when the data is needed) or avoid passing in this data entirely.
128
+
129
+ ```ruby
130
+ class RegistrationJob < ApplicationJob
131
+ def perform(user:, password:, authorization_token:)
132
+ # do something to the user with the password and authorization_token
133
+ end
134
+ end
135
+ ```
136
+
137
+ When a `RegistrationJob` gets queued, this job will get serialized, leaving both `password` and `authorization_token` accessible in plaintext. `Betterment/UnsafeJob` can be configured to flag parameters like these to discourage their use. Some ways to remediate this might be to stop passing in `password`, and to encrypt `authorization_token` and storing it alongside the user object. For example:
138
+
139
+ ```ruby
140
+ class RegistrationJob < ApplicationJob
141
+ def perform(user:)
142
+ authorization_token = user.authorization_token.decrypt
143
+ # do something with the authorization_token
144
+ end
145
+ end
146
+ ```
147
+
148
+ By default, this job will look at classes whose name ends with `Job` but this can be replaced with any regex. This cop can also be configured to take an arbitrary list of parameter names so that any Job found accepting these parameters will be flagged.
149
+
150
+ ```yaml
151
+ Betterment/UnsafeJob:
152
+ class_regex: .*Job$
153
+ sensitive_params:
154
+ - password
155
+ - authorization_token
156
+ ```
157
+
158
+ It may make sense to consult your application's values for `Rails.application.config.filter_parameters`; if the application is filtering specific parameters from being logged, it might be a good idea to prevent these values from being stored in plaintext in a database as well.
data/STYLEGUIDE.md ADDED
@@ -0,0 +1,111 @@
1
+ # Style Guide
2
+
3
+ ## Block Delimeters for multi-line chaining
4
+
5
+ Prefer {...} over do...end for multi-line chained blocks.
6
+
7
+ We use the enforced style `braces_for_chaining`.
8
+
9
+ For example:
10
+
11
+ ### BAD:
12
+
13
+ ```ruby
14
+ array_of_things.each do |thing|
15
+ thing if thing.condition?
16
+ end.compact
17
+ ```
18
+
19
+ ### GOOD:
20
+
21
+ ```ruby
22
+ array_of_things.each { |thing|
23
+ thing if thing.condition?
24
+ }.compact
25
+ ```
26
+
27
+ ## Timeout.timeout needs custom exception
28
+
29
+ If we use `Timeout.timeout` without a custom exception, rescue blocks may be prevented from executing.
30
+
31
+ For example:
32
+
33
+ ### BAD:
34
+
35
+ ```ruby
36
+ Timeout.timeout(run_timeout)
37
+ ```
38
+
39
+ ### GOOD:
40
+
41
+ ```ruby
42
+ Timeout.timeout(run_timeout, SomeModule::SomeError)
43
+ ```
44
+
45
+ ## Rails/OutputSafety
46
+
47
+ We explictly enabled the `Rails/OutputSafety` cop to ensure its usage. It prevents usage of `raw`, `html_safe`, or `safe_concat` unless they are explicitly disabled.
48
+
49
+ This [blog post](https://engineering.betterment.qa/2017/05/15/unsafe-html-rendering.html) explains our feelings on unsafe HTML rendering.
50
+
51
+ ## Use parentheses for percent literal delimeters
52
+
53
+ We enforce usage of parentheses for all percent literal delimeters besides `%r` (the macro for regexps) for which we use curly braces.
54
+
55
+ ### GOOD:
56
+
57
+ ```ruby
58
+ %w(one two three)
59
+ %i(one two three)
60
+ %r{(\w+)-(\d+)}
61
+ ```
62
+
63
+ ### BAD:
64
+
65
+ ```ruby
66
+ %w[one two three]
67
+ %i[one two three]
68
+ %w!one two three!
69
+ %r((\w+)-(\d+))
70
+ ```
71
+
72
+ ## Naming/VariableNumber
73
+
74
+ We enforce the style "snake_case", which means that we prefer to name variables that end in a number with an extra underscore.
75
+
76
+ ### GOOD:
77
+
78
+ ```ruby
79
+ user_1 = User.first
80
+ user_2 = User.second
81
+ ```
82
+
83
+ ### BAD:
84
+
85
+ ```ruby
86
+ user1 = User.first
87
+ user2 = User.second
88
+ ```
89
+
90
+ The snake case style is more readable.
91
+
92
+ ## Betterment/ServerErrorAssertion
93
+
94
+ In RSpec tests, we prevent HTTP response status assertions against server error codes (e.g., 500). While it’s acceptable to
95
+ “under-build” APIs under assumption of controlled and well-behaving clients, these exceptions should be treated as undefined behavior and
96
+ thus do not need request spec coverage. In cases where the server must communicate an expected failure to the client, an appropriate
97
+ semantic status code must be used (e.g., 403, 422, etc.).
98
+
99
+ ### GOOD:
100
+
101
+ ```ruby
102
+ expect(response).to have_http_status :forbidden
103
+ expect(response).to have_http_status 422
104
+ ```
105
+
106
+ ### BAD:
107
+
108
+ ```ruby
109
+ expect(response).to have_http_status :internal_server_error
110
+ expect(response).to have_http_status 500
111
+ ```
@@ -0,0 +1,290 @@
1
+ # please keep this file alphabetically ordered!
2
+
3
+ require:
4
+ - rubocop/cop/betterment.rb
5
+ - rubocop-performance
6
+ - rubocop-rails
7
+ - rubocop-rake
8
+ - rubocop-rspec
9
+
10
+ AllCops:
11
+ Exclude:
12
+ - 'bin/**/*'
13
+ - 'db/**/*'
14
+ - 'vendor/**/*'
15
+ - 'frontend/**/*'
16
+ - 'build/**/*'
17
+ - 'node_modules/**/*'
18
+ - 'tmp/**/*'
19
+ - 'Gemfile'
20
+ DisplayStyleGuide: true
21
+ DisplayCopNames: true
22
+
23
+ Betterment/ServerErrorAssertion:
24
+ Description: 'Detects assertions on 5XX HTTP statuses.'
25
+ Include:
26
+ - 'spec/requests/**/*_spec.rb'
27
+
28
+ Betterment/AuthorizationInController:
29
+ Description: 'Detects unsafe handling of id-like parameters in controllers.'
30
+ Enabled: false
31
+
32
+ Betterment/UnsafeJob:
33
+ Enabled: false
34
+ sensitive_params:
35
+ - password
36
+ - social_security_number
37
+ - ssn
38
+
39
+ Betterment/SitePrismLoaded:
40
+ Include:
41
+ - 'spec/features/**/*_spec.rb'
42
+ - 'spec/system/**/*_spec.rb'
43
+
44
+ Layout/ParameterAlignment:
45
+ Enabled: false
46
+
47
+ Layout/CaseIndentation:
48
+ IndentOneStep: true
49
+
50
+ Layout/ClosingParenthesisIndentation:
51
+ Enabled: false
52
+
53
+ Layout/FirstArrayElementIndentation:
54
+ EnforcedStyle: consistent
55
+
56
+ Layout/LineLength:
57
+ Max: 140
58
+
59
+ Layout/MultilineMethodCallIndentation:
60
+ EnforcedStyle: indented
61
+
62
+ Layout/MultilineOperationIndentation:
63
+ EnforcedStyle: indented
64
+
65
+ # Disabling because of a bug in rubocop: https://github.com/rubocop-hq/rubocop/issues/6918
66
+ Layout/RescueEnsureAlignment:
67
+ Enabled: false
68
+
69
+ Lint/AmbiguousBlockAssociation:
70
+ Exclude:
71
+ - 'spec/**/*'
72
+
73
+ Lint/AmbiguousOperator:
74
+ Exclude:
75
+ - 'spec/**/*'
76
+
77
+ Lint/AmbiguousRegexpLiteral:
78
+ Exclude:
79
+ - 'spec/**/*'
80
+
81
+ Lint/BooleanSymbol:
82
+ Exclude:
83
+ - 'spec/**/*'
84
+
85
+ Metrics/AbcSize:
86
+ Exclude:
87
+ - 'spec/**/*'
88
+ - 'webvalve/**/*'
89
+
90
+ Metrics/BlockLength:
91
+ Enabled: false
92
+
93
+ Metrics/ClassLength:
94
+ Max: 250
95
+ Exclude:
96
+ - 'webvalve/**/*'
97
+
98
+ Metrics/CyclomaticComplexity:
99
+ Max: 10
100
+ Exclude:
101
+ - 'spec/**/*'
102
+ - 'webvalve/**/*'
103
+
104
+ Metrics/MethodLength:
105
+ Enabled: false
106
+
107
+ Metrics/ModuleLength:
108
+ Max: 250
109
+ Exclude:
110
+ - 'webvalve/**/*'
111
+
112
+ Metrics/ParameterLists:
113
+ Max: 5
114
+ CountKeywordArgs: false
115
+
116
+ Metrics/PerceivedComplexity:
117
+ Exclude:
118
+ - 'spec/**/*'
119
+ - 'webvalve/**/*'
120
+
121
+ Naming/HeredocDelimiterNaming:
122
+ Enabled: false
123
+
124
+ Naming/PredicateName:
125
+ NamePrefix:
126
+ - is_
127
+ ForbiddenPrefixes:
128
+ - is_
129
+
130
+ Naming/VariableNumber:
131
+ EnforcedStyle: 'snake_case'
132
+
133
+ Performance/RedundantMatch:
134
+ Enabled: false
135
+
136
+ Rails:
137
+ Enabled: true
138
+
139
+ Rails/ApplicationRecord:
140
+ Enabled: false
141
+
142
+ Rails/Delegate:
143
+ Enabled: false
144
+
145
+ Rails/FindEach:
146
+ Enabled: false
147
+
148
+ Rails/HttpPositionalArguments:
149
+ Enabled: true
150
+
151
+ Rails/OutputSafety:
152
+ Enabled: true
153
+
154
+ RSpec/Capybara/FeatureMethods:
155
+ Enabled: false
156
+
157
+ RSpec/ContextWording:
158
+ Enabled: false
159
+
160
+ RSpec/DescribeClass:
161
+ Enabled: false
162
+
163
+ RSpec/DescribedClass:
164
+ EnforcedStyle: 'described_class'
165
+
166
+ RSpec/EmptyLineAfterExampleGroup:
167
+ Enabled: false
168
+
169
+ RSpec/EmptyLineAfterFinalLet:
170
+ Enabled: false
171
+
172
+ RSpec/EmptyLineAfterHook:
173
+ Enabled: false
174
+
175
+ RSpec/EmptyLineAfterSubject:
176
+ Enabled: false
177
+
178
+ RSpec/ExampleLength:
179
+ Enabled: false
180
+
181
+ RSpec/ExampleWording:
182
+ Enabled: false
183
+
184
+ RSpec/ExpectChange:
185
+ EnforcedStyle: 'block'
186
+
187
+ RSpec/FilePath:
188
+ Enabled: false
189
+
190
+ RSpec/HookArgument:
191
+ Enabled: false
192
+
193
+ RSpec/ItBehavesLike:
194
+ Enabled: false
195
+
196
+ RSpec/LeadingSubject:
197
+ Enabled: false
198
+
199
+ RSpec/LetBeforeExamples:
200
+ Enabled: false
201
+
202
+ RSpec/LetSetup:
203
+ Enabled: false
204
+
205
+ RSpec/MessageSpies:
206
+ Enabled: false
207
+
208
+ RSpec/MultipleExpectations:
209
+ Enabled: false
210
+
211
+ RSpec/MultipleMemoizedHelpers:
212
+ Enabled: false
213
+
214
+ RSpec/NamedSubject:
215
+ Enabled: false
216
+
217
+ RSpec/NestedGroups:
218
+ Enabled: false
219
+
220
+ RSpec/PredicateMatcher:
221
+ Enabled: false
222
+
223
+ RSpec/ScatteredLet:
224
+ Enabled: false
225
+
226
+ RSpec/ScatteredSetup:
227
+ Enabled: false
228
+
229
+ Style/AccessModifierDeclarations:
230
+ Enabled: false
231
+
232
+ Style/BlockDelimiters:
233
+ EnforcedStyle: braces_for_chaining
234
+
235
+ Style/ClassAndModuleChildren:
236
+ Enabled: false
237
+
238
+ Style/Documentation:
239
+ Enabled: false
240
+
241
+ Style/FrozenStringLiteralComment:
242
+ Enabled: false
243
+
244
+ Style/GuardClause:
245
+ Enabled: false
246
+
247
+ Style/Lambda:
248
+ Enabled: false
249
+
250
+ Style/LambdaCall:
251
+ Exclude:
252
+ - 'app/views/**/*.jbuilder'
253
+
254
+ Style/MissingElse:
255
+ Enabled: true
256
+ EnforcedStyle: case
257
+
258
+ Style/PercentLiteralDelimiters:
259
+ PreferredDelimiters:
260
+ default: '()'
261
+ '%i': '()'
262
+ '%I': '()'
263
+ '%r': '{}'
264
+ '%w': '()'
265
+ '%W': '()'
266
+
267
+ Style/SafeNavigation:
268
+ Enabled: false
269
+
270
+ Style/SignalException:
271
+ Enabled: false
272
+
273
+ Style/StringLiterals:
274
+ Enabled: false
275
+
276
+ Style/SymbolProc:
277
+ Enabled: false
278
+
279
+ # Use a trailing comma to keep diffs clean when elements are inserted or removed
280
+ Style/TrailingCommaInArguments:
281
+ EnforcedStyleForMultiline: comma
282
+
283
+ Style/TrailingCommaInArrayLiteral:
284
+ EnforcedStyleForMultiline: comma
285
+
286
+ Style/TrailingCommaInHashLiteral:
287
+ EnforcedStyleForMultiline: comma
288
+
289
+ Style/YodaCondition:
290
+ Enabled: false