rails-guarddog 0.1.12 → 0.1.14

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97cdcd83511d9b89a1786584d4a21dcf403123b3ea3911e7341a85a3bda88446
4
- data.tar.gz: e6634444d93c30e0ddf147cb51db25ee7f04f6f2ee9a17f09d9c1de7b59d7da0
3
+ metadata.gz: 7c9e1e9aac1c7e2bf1e56d36ff7265b4ab1db06538f123ae5fa06c2914c6f02c
4
+ data.tar.gz: daefaec237fbe6a1fc0112082200bfda9a6745fff4c9d6381843ee03c5cb6fe1
5
5
  SHA512:
6
- metadata.gz: c1e72af9e429215d30599e42276ced36f320f052002274827beb6b4060adfa29f907c42af0a88da4a13eead365ef84e32cbb079994534084112e60151671a09f
7
- data.tar.gz: 2af6a3569f894d95fc2e944ebe14bce13696bc89e0120f13aa5c3cdd9e305e44ecbaba77a6e212dc1911ae1bc2efba267c58093e141bb5c544e59c39979aa3d3
6
+ metadata.gz: 2696caa1c34f51eff8b9fcfc7b2eae1048a8e4599414c2d928bd77397160a9e5ec043ff7424dd16576667ccd6371e34fe43e4a914f54869dd332b6089857e0e8
7
+ data.tar.gz: 44f71976bf0c41729c4a563ab0f493d3e43ad7d01a044792e27cddb4534ce5360f3fbbb5bede037c8fe51ce31bb1223993ae11184be5ef94e2f72676babeadcd
data/README.md CHANGED
@@ -104,7 +104,7 @@ That's it! Scan your entire Rails app for security vulnerabilities.
104
104
  - **DoS/ReDoS** — Unbounded queries, dangerous regex patterns ⭐ ENHANCED in v0.1.8
105
105
  - **Supply Chain** — Typosquatted gems using Levenshtein distance ⭐ IMPROVED in v0.1.8
106
106
 
107
- > **False-positive protection (v0.1.10 - v0.1.12)**
107
+ > **False-positive protection (v0.1.10 - v0.1.14)**
108
108
  > - **Secrets**: Patterns use word boundaries — `no_token:` or `reset_password_instructions:` in locale
109
109
  > files will **not** trigger the secrets checker.
110
110
  > - **Secrets**: Secret values must be space-free (`[^\s'"]{6,}`) — human-readable sentences like
@@ -114,6 +114,9 @@ That's it! Scan your entire Rails app for security vulnerabilities.
114
114
  > `ignored_checks`).
115
115
  > - **DoS**: Smart multiline `.limit()` check detection (v0.1.12) — lookahead up to 3 lines handles chained
116
116
  > `.limit()` calls on subsequent lines or reassignment to the same variable, e.g. `things = Thing.all; things = things.limit(100)` or `.limit(...)` on the next line.
117
+ > - **Ignore File**: Support for `.guarddogignore` (v0.1.13) — allows excluding complete files/directories or ignoring specific checkers for specific files/directories.
118
+ > - **SQL Injection**: Precise argument-boundary matching (v0.1.14) — prevents false positives when string interpolation is used safely in query bind arguments (e.g. `where("column ILIKE ?", "#{value}%")`).
119
+ > - **DoS**: Pagy pagination support (v0.1.14) — query calls inside `pagy(...)` or `pagy_countless(...)` helpers are recognized as paginated and are not flagged as unbounded.
117
120
 
118
121
 
119
122
  ## 📊 Example Output
@@ -121,7 +124,7 @@ That's it! Scan your entire Rails app for security vulnerabilities.
121
124
  ### Console Report
122
125
  ```
123
126
  ============================================================
124
- Rails GuardDog Security Report v0.1.12
127
+ Rails GuardDog Security Report v0.1.14
125
128
  ============================================================
126
129
 
127
130
  [CRITICAL] (5 findings)
@@ -225,6 +228,22 @@ GuardDog already silences `secrets` in `spec/` and `test/` by default. To add
225
228
  more suppressions or override the defaults, set `ignored_checks` in your
226
229
  initializer (see example above).
227
230
 
231
+ ### Ignoring findings with `.guarddogignore`
232
+
233
+ You can add a `.guarddogignore` file in the root of your Rails project to exclude entire files/directories or ignore specific checkers for specific files.
234
+
235
+ - To exclude a file or directory completely (silence all checkers), list the path:
236
+ ```
237
+ db/seeds.rb
238
+ vendor/
239
+ ```
240
+
241
+ - To skip only specific checkers for certain files or directories, separate the path and the comma-separated list of checkers with a `:`:
242
+ ```
243
+ db/seeds.rb: secrets
244
+ app/controllers/admin/*: sql_injection, csrf
245
+ ```
246
+
228
247
  ---
229
248
 
230
249
 
@@ -292,6 +311,6 @@ MIT License - Free to use and modify.
292
311
 
293
312
  ---
294
313
 
295
- **Rails GuardDog v0.1.12 — Production Ready**
314
+ **Rails GuardDog v0.1.14 — Production Ready**
296
315
 
297
316
  *Beyond brakeman. Detect what others miss.* 🐕🔒
@@ -43,9 +43,10 @@ module Rails
43
43
 
44
44
  ignored = @configuration.ignored_checks || {}
45
45
  name = checker_identifier
46
+ rel_file = relative_path(file)
46
47
 
47
48
  ignored.any? do |path_pattern, checker_names|
48
- file.include?(path_pattern.to_s) &&
49
+ rel_file.include?(path_pattern.to_s) &&
49
50
  checker_names.map(&:to_s).include?(name)
50
51
  end
51
52
  end
@@ -63,9 +64,24 @@ module Rails
63
64
  def glob_files(pattern)
64
65
  excluded = @configuration&.excluded_paths || []
65
66
  Dir.glob(File.join(@root, pattern)).reject do |file|
66
- excluded.any? { |path| file.include?(path) }
67
+ rel_file = relative_path(file)
68
+ excluded.any? { |path| rel_file.include?(path) }
67
69
  end
68
70
  end
71
+
72
+ private
73
+
74
+ def relative_path(file)
75
+ file_normalized = file.to_s.gsub('\\', '/')
76
+ root_normalized = @root.to_s.gsub('\\', '/')
77
+
78
+ if file_normalized.start_with?(root_normalized)
79
+ file_normalized.sub(/\A#{Regexp.escape(root_normalized)}\/?/, '')
80
+ else
81
+ file_normalized
82
+ end
83
+ end
84
+
69
85
  end
70
86
  end
71
87
  end
@@ -74,6 +74,12 @@ module Rails
74
74
  # UNBOUNDED_QUERY_PATTERN's end-of-expression anchor, but double-check.
75
75
  return true if current_line.match?(/\.limit\s*\(/)
76
76
 
77
+ # Ignore queries wrapped inside pagy pagination helper calls (single or multi-line)
78
+ return true if current_line.match?(/\bpagy(?:_countless)?\s*\(/)
79
+ if current_idx > 0
80
+ return true if lines[current_idx - 1].match?(/\bpagy(?:_countless)?\b/)
81
+ end
82
+
77
83
  # Extract the variable being assigned, supporting local vars, instance
78
84
  # vars (@foo) and class-instance vars (@@foo).
79
85
  assigned_var = current_line.match(/([@]{0,2}\w+)\s*=\s*[A-Z]/)&.[](1)
@@ -1,26 +1,29 @@
1
- module Rails
2
- module Guarddog
3
- module Checkers
4
- class SqlInjectionChecker < BaseChecker
5
- def run
6
- glob_files('app/**/*.rb').each do |file|
7
- content = File.read(file)
8
- content.each_line.with_index do |line, idx|
9
- if line.match?(/\.where\s*\(\s*['"].*#\{/) || line.match?(/\.find_by_sql\s*\(/)
10
- add_finding(
11
- severity: :high,
12
- message: "Potential SQL injection: using string interpolation in queries",
13
- file: file,
14
- line: idx + 1,
15
- snippet: line.strip,
16
- remediation: "Use parameterized queries: .where('column = ?', value)"
17
- )
18
- end
19
- end
20
- end
21
- findings
22
- end
23
- end
24
- end
25
- end
26
- end
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class SqlInjectionChecker < BaseChecker
5
+ def run
6
+ glob_files('app/**/*.rb').each do |file|
7
+ content = File.read(file)
8
+ content.each_line.with_index do |line, idx|
9
+ # Matches string interpolation ONLY in the first argument (the SQL template string).
10
+ # We check that #{ is reached before the closing quote of the first string argument.
11
+ if line.match?(/\.where\s*\(\s*(?:"[^"]*#\{|'[^']*#\{)/) ||
12
+ line.match?(/\.find_by_sql\s*\(\s*(?:"[^"]*#\{|'[^']*#\{)/)
13
+ add_finding(
14
+ severity: :high,
15
+ message: "Potential SQL injection: using string interpolation in queries",
16
+ file: file,
17
+ line: idx + 1,
18
+ snippet: line.strip,
19
+ remediation: "Use parameterized queries: .where('column = ?', value)"
20
+ )
21
+ end
22
+ end
23
+ end
24
+ findings
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -4,11 +4,12 @@ module Rails
4
4
  attr_accessor :configuration, :findings
5
5
 
6
6
  def initialize(config = nil)
7
- @configuration = config || Configuration.new
7
+ @configuration = config || default_configuration
8
8
  @findings = []
9
9
  end
10
10
 
11
11
  def run
12
+ load_ignore_file
12
13
  load_checkers.each do |checker_class|
13
14
  checker = checker_class.new(@configuration)
14
15
  checker.run
@@ -19,6 +20,41 @@ module Rails
19
20
 
20
21
  private
21
22
 
23
+ def default_configuration
24
+ if defined?(Rails) && Rails.application && Rails.application.config.respond_to?(:guarddog)
25
+ Rails.application.config.guarddog
26
+ else
27
+ Configuration.new
28
+ end
29
+ end
30
+
31
+ def load_ignore_file
32
+ ignore_file_path = File.join(@configuration.root, '.guarddogignore')
33
+ return unless File.exist?(ignore_file_path)
34
+
35
+ @configuration.ignored_checks ||= {}
36
+ @configuration.excluded_paths ||= []
37
+
38
+ File.foreach(ignore_file_path).each_with_index do |line, idx|
39
+ line = line.strip
40
+ next if line.empty? || line.start_with?('#')
41
+
42
+ if line.include?(':')
43
+ path_pattern, checkers_str = line.split(':', 2)
44
+ path_pattern = path_pattern.strip.gsub('\\', '/')
45
+ checkers = checkers_str.split(',').map { |c| c.strip.downcase }
46
+
47
+ @configuration.ignored_checks[path_pattern] ||= []
48
+ @configuration.ignored_checks[path_pattern].concat(checkers)
49
+ @configuration.ignored_checks[path_pattern].uniq!
50
+ else
51
+ path_pattern = line.gsub('\\', '/')
52
+ @configuration.excluded_paths << path_pattern
53
+ @configuration.excluded_paths.uniq!
54
+ end
55
+ end
56
+ end
57
+
22
58
  def load_checkers
23
59
  checker_map = {
24
60
  'sql_injection' => Checkers::SqlInjectionChecker,
@@ -1,5 +1,5 @@
1
1
  module Rails
2
2
  module Guarddog
3
- VERSION = "0.1.12"
3
+ VERSION = "0.1.14"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-guarddog
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Security Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-13 00:00:00.000000000 Z
11
+ date: 2026-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties