Class: ActiveRecordSaferLookupQuery::Checker

Inherits:
Object
  • Object
show all
Defined in:
lib/active_record_safer_lookup_query/checker.rb

Defined Under Namespace

Classes: Finding, Whitelist

Constant Summary collapse

DEFAULT_PATHS =
%w[
  app/controllers
  app/graphql
  app/api
  app/forms
  app/services
].freeze
DEFAULT_EXCLUDE_PATTERNS =
[
  %r{\Aapp/controllers/debug/}
].freeze
DEFAULT_WHITELIST_FILES =
%w[
  .activerecord-safer-lookup-query.yml
  .activerecord-safer-lookup-query.yaml
].freeze
IGNORE_MARKER =
/(?:active_record_safer_lookup_query|activerecord_safer_query|global_find_audit|tenant_scope_audit):\s*ignore/
SEVERITY_RANK =
{
  'LOW' => 1,
  'MEDIUM' => 2,
  'HIGH' => 3
}.freeze
CONST_RECEIVER =
/(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
PLAIN_SCOPE_CHAIN =
/(?:\.[a-z_][A-Za-z0-9_!?]*)*/
MODEL_CHAIN =
/#{CONST_RECEIVER}#{PLAIN_SCOPE_CHAIN}/
EXTERNAL_SOURCE =
/(?:params\s*(?:\[|\.|\.dig)|input\s*(?:\[|\.|\.dig)|args\s*(?:\[|\.|\.dig)|context\s*\[|session\s*\[|cookies\s*\[|request\.|headers\s*\[)/
RISKY_ID_VARIABLE =
/(?:\bid\b|[a-z][a-z0-9_]*(?:_id|_ids|_uuid|_slug|_code)\b)/
NATURAL_KEY =
/(?:email|uid|issuer|code|subdomain|slug|token|account|client_id|external_id)/
NATURAL_KEY_VALUE =
/(?:#{EXTERNAL_SOURCE}|row\b|metadata\b|attributes_hash\b|saml_setting\b|response\b|payload\b|csv\b|id_token\b|token\b|issuer\b|code\b|email\b|uid\b)/
FIND_METHOD =
/(?:find|find_by!?|find_or_initialize_by!?|find_or_create_by!?|create_or_find_by!?)/
DESTRUCTIVE_METHOD =
/(?:destroy_all|delete_all|update_all|delete|destroy)\b/
DIRECT_FIND_START =
/\b#{MODEL_CHAIN}\.(?:friendly\.)?#{FIND_METHOD}\b/
DIRECT_FIND_EXTERNAL_INPUT =
/\b#{MODEL_CHAIN}\.(?:friendly\.)?#{FIND_METHOD}\s*\([^)]*#{EXTERNAL_SOURCE}/
DIRECT_FIND_ID_START =
/\b#{MODEL_CHAIN}\.(?:friendly\.)?find\b/
DIRECT_FIND_ID_VARIABLE =
/\b#{MODEL_CHAIN}\.(?:friendly\.)?find\s*\(\s*#{RISKY_ID_VARIABLE}\s*\)/
WHERE_START =
/\b#{MODEL_CHAIN}\.where\b/
WHERE_CHAIN_START =
/\A\.where\b/
WHERE_EXTERNAL_IDS =
/\b#{MODEL_CHAIN}\.where\s*\([^)]*(?:\bid\b|[a-z_]+_id):\s*[^)]*#{EXTERNAL_SOURCE}/
NATURAL_KEY_LOOKUP_START =
/\b#{MODEL_CHAIN}\.(?:find_by!?|find_or_initialize_by!?|find_or_create_by!?|create_or_find_by!?)\b/
NATURAL_KEY_LOOKUP =
/\b#{MODEL_CHAIN}\.(?:find_by!?|find_or_initialize_by!?|find_or_create_by!?|create_or_find_by!?)\s*\([^)]*#{NATURAL_KEY}:\s*[^)]*#{NATURAL_KEY_VALUE}/
DESTRUCTIVE_EXTERNAL_ID_LOOKUP =
/\b#{MODEL_CHAIN}\.where\s*\([^)]*(?:\bid\b|[a-z_]+_id):[^)]*(?:#{EXTERNAL_SOURCE}|#{RISKY_ID_VARIABLE})[^)]*\).*\.#{DESTRUCTIVE_METHOD}/
DESTRUCTIVE_EXTERNAL_LOOKUP =
/\b#{MODEL_CHAIN}\.where\s*\([^)]*(?:#{EXTERNAL_SOURCE}|#{RISKY_ID_VARIABLE})[^)]*\).*\.#{DESTRUCTIVE_METHOD}/
DRAFT_COURSE_SCOPE =
/\bCourse\.(?:draft|closed|where\s*\([^)]*state:\s*[^)]*(?:draft|closed)|glopla_lms)\b/

Instance Method Summary collapse

Constructor Details

#initialize(paths: DEFAULT_PATHS, root: Dir.pwd, whitelist_paths: []) ⇒ Checker

Returns a new instance of Checker.



174
175
176
177
178
# File 'lib/active_record_safer_lookup_query/checker.rb', line 174

def initialize(paths: DEFAULT_PATHS, root: Dir.pwd, whitelist_paths: [])
  @root = Pathname.new(root).expand_path
  @paths = paths.empty? ? DEFAULT_PATHS : paths
  @whitelist = Whitelist.load(root: @root, paths: whitelist_paths)
end

Instance Method Details

#findingsObject



180
181
182
183
184
185
# File 'lib/active_record_safer_lookup_query/checker.rb', line 180

def findings
  ruby_files.flat_map { |path| findings_for(path) }
            .uniq { |finding| [finding.path, finding.line, finding.rule] }
            .reject { |finding| whitelist.match?(finding) }
            .sort_by { |finding| [finding.path, finding.line, finding.rule] }
end