Class: Scryer::Rules::WeakCryptoRule
- Inherits:
-
Scryer::Rule
- Object
- Scryer::Rule
- Scryer::Rules::WeakCryptoRule
- Defined in:
- lib/scryer/rules/weak_crypto_rule.rb
Overview
Flags Digest::MD5/Digest::SHA1 used in a context that looks like
password/credential hashing (method or nearby variable name contains
"password"/"passwd") — both are cryptographically broken for that use
case; a fast general-purpose hash lets an attacker who steals the DB
brute-force passwords far faster than a proper password hash
(bcrypt/scrypt/argon2, which are deliberately slow).
Constant Summary collapse
- WEAK_DIGESTS =
%w[MD5 SHA1].freeze
- PASSWORD_HINT =
(?!less)after "password" excludes "passwordless" (and "passwordlessly") specifically — a real identifier that means the opposite of what this heuristic is looking for (e.g. a magic-link or passwordless-auth token,Digest::SHA1.hexdigest(passwordless_token)), not a credential. It doesn't affect any legitimate match: every other "password"-containing identifier this heuristic cares about (password,user_password,password_reset_token,hashed_password, ...) is never immediately followed by the literal substring "less". /password(?!less)|passwd|credential/i.freeze
Instance Attribute Summary
Attributes inherited from Scryer::Rule
Instance Method Summary collapse
Methods inherited from Scryer::Rule
Constructor Details
This class inherits a constructor from Scryer::Rule
Instance Method Details
#scan ⇒ Object
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/scryer/rules/weak_crypto_rule.rb', line 30 def scan findings = [] Ast.each_node(sexp) do |node| next unless Ast.tagged?(node, :top_const_ref, :const_path_ref, :var_ref) digest_name = digest_algorithm_name(node) next unless digest_name # Only flag when something nearby (same statement/line) mentions # password-ish naming — Digest::MD5/SHA1 have plenty of legitimate # non-credential uses (cache keys, ETags, checksums) that shouldn't # be flagged as a crypto weakness. line = Ast.line_of(node) context_line = strip_trailing_comment(Ast.source_line(source, line).to_s) next unless PASSWORD_HINT.match?(context_line) findings << finding( line: line, message: "`Digest::#{digest_name}` is used near what looks like password/credential " \ "handling — #{digest_name} is fast and unsalted by default, making stolen " \ "hashes practical to brute-force.", suggested_fix: "Use `bcrypt` via Rails' `has_secure_password` for password storage " \ "instead of a general-purpose digest — it's deliberately slow and " \ "handles salting automatically. Reserve Digest::#{digest_name} for " \ "non-credential uses (cache keys, checksums)." ) end findings end |