scryer 0.3.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +322 -0
  3. data/README.md +546 -43
  4. data/lib/generators/scryer/USAGE +10 -2
  5. data/lib/generators/scryer/templates/scryer_initializer.rb +15 -0
  6. data/lib/scryer/ai_fix_suggester.rb +29 -9
  7. data/lib/scryer/ast.rb +95 -6
  8. data/lib/scryer/authorization_watcher.rb +156 -0
  9. data/lib/scryer/baseline.rb +75 -0
  10. data/lib/scryer/cli.rb +209 -11
  11. data/lib/scryer/dependency_audit.rb +108 -5
  12. data/lib/scryer/finding.rb +6 -0
  13. data/lib/scryer/fix_verifier.rb +82 -0
  14. data/lib/scryer/minitest.rb +48 -0
  15. data/lib/scryer/performance_rules/inefficient_save_loop_rule.rb +32 -0
  16. data/lib/scryer/performance_rules/missing_pagination_rule.rb +1 -0
  17. data/lib/scryer/performance_rules/n_plus_one_query_rule.rb +1 -0
  18. data/lib/scryer/performance_rules/unbounded_table_scan_rule.rb +1 -0
  19. data/lib/scryer/report_renderer.rb +637 -44
  20. data/lib/scryer/rspec.rb +55 -0
  21. data/lib/scryer/rule.rb +22 -2
  22. data/lib/scryer/rules/action_cable_forgery_protection_rule.rb +50 -0
  23. data/lib/scryer/rules/active_storage_inline_disposition_rule.rb +50 -0
  24. data/lib/scryer/rules/active_storage_missing_content_type_validation_rule.rb +79 -0
  25. data/lib/scryer/rules/authentication_bypass_rule.rb +95 -0
  26. data/lib/scryer/rules/command_injection_rule.rb +3 -0
  27. data/lib/scryer/rules/consider_all_requests_local_rule.rb +51 -0
  28. data/lib/scryer/rules/cors_misconfiguration_rule.rb +100 -0
  29. data/lib/scryer/rules/csrf_protection_rule.rb +60 -11
  30. data/lib/scryer/rules/force_ssl_rule.rb +48 -0
  31. data/lib/scryer/rules/graphql_missing_query_limits_rule.rb +106 -0
  32. data/lib/scryer/rules/hardcoded_basic_auth_rule.rb +51 -0
  33. data/lib/scryer/rules/hardcoded_secret_key_base_rule.rb +58 -0
  34. data/lib/scryer/rules/hardcoded_secret_rule.rb +3 -0
  35. data/lib/scryer/rules/host_authorization_disabled_rule.rb +50 -0
  36. data/lib/scryer/rules/idor_rule.rb +165 -0
  37. data/lib/scryer/rules/insecure_cookie_serializer_rule.rb +47 -0
  38. data/lib/scryer/rules/job_raw_params_rule.rb +131 -0
  39. data/lib/scryer/rules/jwt_insecure_rule.rb +123 -0
  40. data/lib/scryer/rules/mass_assignment_rule.rb +34 -10
  41. data/lib/scryer/rules/missing_authorization_rule.rb +103 -0
  42. data/lib/scryer/rules/missing_policy_scope_rule.rb +134 -0
  43. data/lib/scryer/rules/open_redirect_rule.rb +3 -0
  44. data/lib/scryer/rules/path_traversal_rule.rb +110 -0
  45. data/lib/scryer/rules/security_headers_rule.rb +133 -0
  46. data/lib/scryer/rules/sql_injection_rule.rb +3 -0
  47. data/lib/scryer/rules/ssrf_rule.rb +139 -0
  48. data/lib/scryer/rules/unsafe_deserialization_rule.rb +3 -0
  49. data/lib/scryer/rules/verbose_production_log_level_rule.rb +53 -0
  50. data/lib/scryer/rules/weak_crypto_rule.rb +37 -2
  51. data/lib/scryer/rules/weak_session_cookie_rule.rb +51 -0
  52. data/lib/scryer/rules/xss_unsafe_html_rule.rb +41 -0
  53. data/lib/scryer/style_rules/frozen_string_literal_rule.rb +1 -0
  54. data/lib/scryer/version.rb +1 -1
  55. data/lib/scryer.rb +15 -0
  56. data/lib/tasks/scryer.rake +138 -13
  57. metadata +41 -12
@@ -14,6 +14,7 @@ module Scryer
14
14
  self.category = "performance"
15
15
  self.default_severity = "warning"
16
16
  self.title = "Per-record save/update inside a loop"
17
+ self.confidence = "medium"
17
18
 
18
19
  LOOP_METHODS = %w[each each_with_index].freeze
19
20
  # Genuinely argless in normal use — a bare `:call` node, never wrapped
@@ -24,7 +25,25 @@ module Scryer
24
25
  # avoids double-counting the inner call node these wrap.
25
26
  ARG_METHODS = %w[update update! update_attribute update_attributes].freeze
26
27
 
28
+ # `db/seeds.rb` (and the multi-file `db/seeds/*.rb` convention Rails
29
+ # supports via `Rails.application.load_seed`) is a one-time, manually
30
+ # run setup script, not a request-handling hot path — the whole
31
+ # premise of this rule (per-record round-trips scale badly as load
32
+ # grows) doesn't apply to a script a developer runs once at setup
33
+ # time for a small, fixed reference dataset (roles, plans, countries,
34
+ # ...). Flagging `roles.each { |r| r.save! }` there is just noise, so
35
+ # this rule skips findings whose file is exactly this path or under
36
+ # this directory. Deliberately file-path-based rather than trying to
37
+ # infer "small dataset" from the AST (which isn't reliably knowable —
38
+ # a seed file can still iterate a CSV of arbitrary size), and narrow
39
+ # to this one well-known Rails convention rather than any file that
40
+ # merely "looks like a script" elsewhere in the app.
41
+ SEED_FILE = "db/seeds.rb"
42
+ SEED_DIR_PREFIX = "db/seeds/"
43
+
27
44
  def scan
45
+ return [] if seed_file?
46
+
28
47
  findings = []
29
48
 
30
49
  Ast.each_node(sexp) do |node|
@@ -58,6 +77,19 @@ module Scryer
58
77
 
59
78
  private
60
79
 
80
+ # `Scanner` builds `file` by globbing `File.join(root, dir, "**", "*.rb")`
81
+ # with `dir` defaulting to `"."`, so the relative path it hands rules is
82
+ # actually `"./db/seeds.rb"`, not `"db/seeds.rb"` — a plain `==`/
83
+ # `start_with?` against the un-prefixed string silently never matches
84
+ # against a real scan (verified: without stripping the prefix here,
85
+ # `db/seeds.rb` still fired in a real `Scryer::Scanner.new(root:,
86
+ # dirs: ["."]).call` run). Strip a leading `./` before comparing so
87
+ # this matches how the path actually arrives, not an assumed form.
88
+ def seed_file?
89
+ normalized = file.to_s.delete_prefix("./")
90
+ normalized == SEED_FILE || normalized.start_with?(SEED_DIR_PREFIX)
91
+ end
92
+
61
93
  def block_param_name(block_node)
62
94
  return nil unless Ast.tagged?(block_node, :do_block, :brace_block)
63
95
 
@@ -12,6 +12,7 @@ module Scryer
12
12
  self.category = "performance"
13
13
  self.default_severity = "warning"
14
14
  self.title = "Possible unbounded result set on an index action"
15
+ self.confidence = "medium"
15
16
 
16
17
  QUERY_METHODS = %w[all where].freeze
17
18
  BOUND_METHODS = %w[limit page per paginate find_each find_in_batches first take].freeze
@@ -21,6 +21,7 @@ module Scryer
21
21
  self.category = "performance"
22
22
  self.default_severity = "warning"
23
23
  self.title = "Possible N+1 query inside a loop"
24
+ self.confidence = "medium"
24
25
 
25
26
  QUERY_METHODS = %w[where all find find_by find_by! order limit].freeze
26
27
  LOOP_METHODS = %w[each map collect each_with_index].freeze
@@ -14,6 +14,7 @@ module Scryer
14
14
  self.category = "performance"
15
15
  self.default_severity = "warning"
16
16
  self.title = "Full query result loaded into memory before iterating"
17
+ self.confidence = "medium"
17
18
 
18
19
  QUERY_METHODS = %w[all where order].freeze
19
20