RuboCop Betterment Build Status

Shared rubocop configuration for Betterment Rails apps/engines.

Check out the styleguide for some additional commentary on our cop configurations.

Installation

Gemfile:

gem 'rubocop-betterment'

.rubocop.yml:

inherit_gem:
  rubocop-betterment:
    - config/default.yml

Dependencies

This gem depends on the following other gems:

  • rubocop
  • rubocop-rspec

Custom Cops

All cops are located under lib/rubocop/cop/betterment

Betterment/AuthorizationInController

This cop looks for unsafe handling of id-like parameters in controllers that may lead to mass assignment style vulnerabilities. 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:

class Controller
  def create_params
    params.permit(:user_id, :language)
  end

  def create
    info = params.permit(:user_id)
    Model.new(user_id: info[:user_id], language: params[:language])
    Model.new(user_id: params[:user_id], language: params[:language])
    Model.new(create_params)
  end
end

All three Model.new calls may be susceptible to a mass assignment vulnerability. 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.

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.

This is what a full configuration of this cop may look like:

Betterment/AuthorizationInController:
  # Limit this cop just to controllers
  Include:
    - 'app/controllers/**/*.rb'
  unsafe_parameters:
    - username
    - misc_unsafe_parameter
  unsafe_regex: '.*_id$'

Betterment/UnscopedFind

This cop flags code that passes user input directly into a find-like call that may lead to authorization issues. 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:

class Controller
  def index
    @document = Document.find(params[:document_id])
  end
end

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.

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:

class Controller < UnauthenticatedWebappController
  def index
    @language = Language.find(params[:language])
    @zip = ZipCode.find(params[:zip])
  end
end

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:

Betterment/UnscopedFind:
  unauthenticated_models:
    - Language
    - ZipCode

Betterment/DynamicParams

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:

class Controller
  def create_param_names
    %i(user_id first_name last_name)
  end

  def create
    parameter_name = :user_id
    params.permit(parameter_name)
    params.permit(create_params_names)
    params.permit(%w(blog post comment).flat_map { |p| ["#{p}_name", "#{p}_title"] })
  end
end

All three params.permit calls will be flagged.