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 +4 -4
- data/README.md +22 -3
- data/lib/rails/guarddog/checkers/base_checker.rb +18 -2
- data/lib/rails/guarddog/checkers/dos_checker.rb +6 -0
- data/lib/rails/guarddog/checkers/sql_injection_checker.rb +29 -26
- data/lib/rails/guarddog/scanner.rb +37 -1
- data/lib/rails/guarddog/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c9e1e9aac1c7e2bf1e56d36ff7265b4ab1db06538f123ae5fa06c2914c6f02c
|
|
4
|
+
data.tar.gz: daefaec237fbe6a1fc0112082200bfda9a6745fff4c9d6381843ee03c5cb6fe1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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 ||
|
|
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,
|
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.
|
|
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-
|
|
11
|
+
date: 2026-08-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|