rails-guarddog 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aa16a224273a4b8cb806307781b714908b081fbc7a6d245714bed775e6f99749
4
+ data.tar.gz: 9f5d59f9e0d7445a1a2c5c471c087881cd3524ed93f636ed27b64f3e439e3d01
5
+ SHA512:
6
+ metadata.gz: f36099494083f49b05cb40792e1f092b013cdca187f1b01e8de5c340e9927cd7b4203c03900560e04e6bd5d4807687a14511e8d57d05043040e4e67b7b69ebc4
7
+ data.tar.gz: 0221476bc569b4025a2cadc8088de4961efbad611af4e1d465f14dac67aa7b37e7415a0089daad390b2944987d84b34e98a9cd8ce4606ad7c4e8f944b8ef6022
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Rails GuardDog
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # Rails GuardDog šŸ•
2
+
3
+ Advanced security scanning for Rails applications. Beyond brakeman — AI injection, DoS patterns, supply chain attacks, GraphQL auth, and more.
4
+
5
+ ## Features
6
+
7
+ ### Core Checks
8
+ - SQL Injection (improved detection)
9
+ - XSS in views
10
+ - CSRF protection
11
+ - Mass assignment vulnerabilities
12
+ - Open redirects
13
+ - Hardcoded secrets (always-on)
14
+
15
+ ### Original Features ⭐
16
+ - **AI/LLM Prompt Injection** — Detects user input flowing into LLM calls
17
+ - **DoS & ReDoS Detection** — Regex catastrophe and unbounded query patterns
18
+ - **Supply Chain** — Typosquatting detection with Levenshtein distance
19
+ - **GraphQL Auth Gaps** — Missing field-level authorization
20
+ - **Rate Limiting Audit** — Checks rack-attack configuration
21
+
22
+ ## Installation
23
+
24
+ Add to Gemfile:
25
+ ```ruby
26
+ gem 'rails-guarddog'
27
+ ```
28
+
29
+ Run:
30
+ ```bash
31
+ bundle install
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### CLI
37
+ ```bash
38
+ guarddog scan # Console output
39
+ guarddog report # HTML + JSON reports
40
+ ```
41
+
42
+ ### Rake Tasks
43
+ ```bash
44
+ rake guarddog:scan # Run scan
45
+ rake guarddog:report # Generate reports
46
+ rake guarddog:ci # CI integration (exits 1 on critical)
47
+ ```
48
+
49
+ ## Report Formats
50
+
51
+ - **Console** — Color-coded terminal output
52
+ - **HTML** — Interactive dashboard with filtering
53
+ - **JSON** — Structured format for CI/CD integration
54
+
55
+ ## Configuration
56
+
57
+ Create `config/initializers/guarddog.rb`:
58
+ ```ruby
59
+ Rails.application.config.guarddog.enabled_checkers = %w[
60
+ sql_injection xss csrf mass_assignment
61
+ ]
62
+ ```
63
+
64
+ ## License
65
+
66
+ MIT
data/bin/guarddog ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rails/guarddog'
3
+
4
+ if ARGV[0] == 'scan'
5
+ scanner = Rails::Guarddog::Scanner.new
6
+ findings = scanner.run
7
+ reporter = Rails::Guarddog::Reporters::ConsoleReporter.new(findings)
8
+ reporter.report
9
+ elsif ARGV[0] == 'report'
10
+ scanner = Rails::Guarddog::Scanner.new
11
+ findings = scanner.run
12
+ html_reporter = Rails::Guarddog::Reporters::HtmlReporter.new(findings)
13
+ path = html_reporter.report
14
+ puts "Report generated: #{path}"
15
+ else
16
+ puts "Usage: guarddog [scan|report]"
17
+ end
@@ -0,0 +1,31 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class AiInjectionChecker < BaseChecker
5
+ AI_GEMS = %w[ruby-openai anthropic langchainrb openai]
6
+
7
+ def run
8
+ glob_files('app/**/*.rb').each do |file|
9
+ content = File.read(file)
10
+ content.each_line.with_index do |line, idx|
11
+ # Check for AI gem calls with user input
12
+ if line.match?(/\.create.*messages/) || line.match?(/\.chat\.completions/)
13
+ if line.include?('params') || line.include?('user_input')
14
+ add_finding(
15
+ severity: :critical,
16
+ message: "AI prompt injection risk: user input passed to LLM without sanitization",
17
+ file: file,
18
+ line: idx + 1,
19
+ snippet: line.strip,
20
+ remediation: "Sanitize user input before passing to LLM; use system prompts safely"
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ findings
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class BaseChecker
5
+ attr_accessor :findings
6
+
7
+ def initialize(root = Rails.root.to_s)
8
+ @root = root
9
+ @findings = []
10
+ end
11
+
12
+ def run
13
+ raise NotImplementedError, "Subclasses must implement run"
14
+ end
15
+
16
+ protected
17
+
18
+ def add_finding(severity:, message:, file:, line:, snippet: "", remediation: "")
19
+ findings << Finding.new(
20
+ severity: severity,
21
+ category: self.class.name.demodulize.gsub(/Checker$/, ''),
22
+ message: message,
23
+ file: file,
24
+ line: line,
25
+ code_snippet: snippet,
26
+ remediation: remediation
27
+ )
28
+ end
29
+
30
+ def glob_files(pattern)
31
+ Dir.glob(File.join(@root, pattern))
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class CsrfChecker < BaseChecker
5
+ def run
6
+ glob_files('app/controllers/**/*.rb').each do |file|
7
+ content = File.read(file)
8
+ has_skip = content.include?('skip_before_action :verify_authenticity_token')
9
+ if has_skip && !content.include?('# CSRF disabled for specific reason')
10
+ add_finding(
11
+ severity: :critical,
12
+ message: "CSRF protection disabled without documented reason",
13
+ file: file,
14
+ line: 1,
15
+ remediation: "Remove skip_before_action or add documented reason"
16
+ )
17
+ end
18
+ end
19
+ findings
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class DependencyChecker < BaseChecker
5
+ def run
6
+ gemfile = File.join(@root, 'Gemfile.lock')
7
+ return [] unless File.exist?(gemfile)
8
+
9
+ content = File.read(gemfile)
10
+
11
+ # Check for typosquatted gems
12
+ if content.match?(/raills|raill\s|rails-rails|active-model/)
13
+ add_finding(
14
+ severity: :critical,
15
+ message: "Possible typosquatted gem detected in Gemfile.lock",
16
+ file: gemfile,
17
+ line: 1,
18
+ remediation: "Verify gem names carefully; check rubygems.org"
19
+ )
20
+ end
21
+
22
+ findings
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class DosChecker < 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
+ # Check for unbounded queries
10
+ if line.match?(/\.where\(.*\)\.all/) || line.match?(/\.all\s*$/)
11
+ add_finding(
12
+ severity: :high,
13
+ message: "Potential DoS: unbounded database query without limit",
14
+ file: file,
15
+ line: idx + 1,
16
+ snippet: line.strip,
17
+ remediation: "Add .limit() to control result size"
18
+ )
19
+ end
20
+ # Check for regex vulnerabilities
21
+ if line.match?(/\/.+\*\+.*\*\+.+\//) || line.match?(/match\?.*\(.+\*\+/)
22
+ add_finding(
23
+ severity: :high,
24
+ message: "Potential ReDoS vulnerability: dangerous regex pattern",
25
+ file: file,
26
+ line: idx + 1,
27
+ snippet: line.strip,
28
+ remediation: "Simplify regex or use timeout mechanisms"
29
+ )
30
+ end
31
+ end
32
+ end
33
+ findings
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class GraphqlChecker < BaseChecker
5
+ def run
6
+ glob_files('app/graphql/**/*.rb').each do |file|
7
+ content = File.read(file)
8
+
9
+ if content.include?('field') || content.include?('def resolve')
10
+ unless content.include?('authorize') || content.include?('current_user')
11
+ add_finding(
12
+ severity: :high,
13
+ message: "GraphQL field missing authorization check",
14
+ file: file,
15
+ line: 1,
16
+ snippet: "GraphQL resolver without auth",
17
+ remediation: "Add authorization: authorize @object or Pundit check"
18
+ )
19
+ end
20
+ end
21
+ end
22
+ findings
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class IdorChecker < BaseChecker
5
+ def run
6
+ glob_files('app/controllers/**/*.rb').each do |file|
7
+ content = File.read(file)
8
+ content.each_line.with_index do |line, idx|
9
+ if line.match?(/find\(params\[[:']id[']\]/) && !content.include?('authorize')
10
+ add_finding(
11
+ severity: :high,
12
+ message: "Potential IDOR: object accessed by ID without ownership check",
13
+ file: file,
14
+ line: idx + 1,
15
+ snippet: line.strip,
16
+ remediation: "Add authorization check: authorize @object"
17
+ )
18
+ end
19
+ end
20
+ end
21
+ findings
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class MassAssignmentChecker < 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.include?('permit!') || line.include?('permit(:')
10
+ if line.include?('permit!')
11
+ add_finding(
12
+ severity: :critical,
13
+ message: "Mass assignment vulnerability: permit! allows all parameters",
14
+ file: file,
15
+ line: idx + 1,
16
+ snippet: line.strip,
17
+ remediation: "Use specific permits: permit(:field1, :field2)"
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
23
+ findings
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class OpenRedirectChecker < BaseChecker
5
+ def run
6
+ glob_files('app/controllers/**/*.rb').each do |file|
7
+ content = File.read(file)
8
+ content.each_line.with_index do |line, idx|
9
+ if line.match?(/redirect_to\s+params\[:/) || line.match?(/redirect_to\s+request\./)
10
+ add_finding(
11
+ severity: :high,
12
+ message: "Potential open redirect: user-controlled redirect URL",
13
+ file: file,
14
+ line: idx + 1,
15
+ snippet: line.strip,
16
+ remediation: "Whitelist allowed redirect URLs"
17
+ )
18
+ end
19
+ end
20
+ end
21
+ findings
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class RateLimitChecker < BaseChecker
5
+ def run
6
+ config_file = File.join(@root, 'config/initializers/rack_attack.rb')
7
+
8
+ if !File.exist?(config_file)
9
+ add_finding(
10
+ severity: :medium,
11
+ message: "Rate limiting not configured: rack_attack.rb missing",
12
+ file: config_file,
13
+ line: 1,
14
+ remediation: "Create config/initializers/rack_attack.rb with rate limiting rules"
15
+ )
16
+ else
17
+ content = File.read(config_file)
18
+ unless content.include?('throttle') && (content.include?('login') || content.include?('api'))
19
+ add_finding(
20
+ severity: :medium,
21
+ message: "Rate limiting rules not configured for critical endpoints",
22
+ file: config_file,
23
+ line: 1,
24
+ remediation: "Add throttle rules for /login, /api/auth, /password_reset"
25
+ )
26
+ end
27
+ end
28
+ findings
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,37 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class SecretsChecker < BaseChecker
5
+ PATTERNS = [
6
+ /api[_-]?key\s*[=:]\s*['"][^'"]+['"]/i,
7
+ /secret[_-]?key\s*[=:]\s*['"][^'"]+['"]/i,
8
+ /password\s*[=:]\s*['"][^'"]+['"]/i,
9
+ /token\s*[=:]\s*['"][^'"]+['"]/i
10
+ ]
11
+
12
+ def run
13
+ %w[*.rb *.yml .env .env.local].each do |pattern|
14
+ glob_files("**/{#{pattern}}").each do |file|
15
+ next if file.include?('node_modules') || file.include?('vendor')
16
+ content = File.read(file) rescue next
17
+ content.each_line.with_index do |line, idx|
18
+ PATTERNS.each do |pattern|
19
+ if line.match?(pattern) && !line.strip.start_with?('#')
20
+ add_finding(
21
+ severity: :critical,
22
+ message: "Hardcoded secret detected",
23
+ file: file,
24
+ line: idx + 1,
25
+ remediation: "Use ENV variables or Rails credentials"
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ findings
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
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*['"]\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
@@ -0,0 +1,26 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Checkers
4
+ class XssChecker < BaseChecker
5
+ def run
6
+ glob_files('app/views/**/*.erb').each do |file|
7
+ content = File.read(file)
8
+ content.each_line.with_index do |line, idx|
9
+ if line.include?('<%=') && (line.include?('params') || line.include?('@')) && !line.include?('sanitize') && !line.include?('h(')
10
+ add_finding(
11
+ severity: :high,
12
+ message: "Potential XSS vulnerability: unsanitized user input in view",
13
+ file: file,
14
+ line: idx + 1,
15
+ snippet: line.strip,
16
+ remediation: "Use <%= h() %> or sanitize() helper"
17
+ )
18
+ end
19
+ end
20
+ end
21
+ findings
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module Rails
2
+ module Guarddog
3
+ class Configuration
4
+ attr_accessor :root, :enabled_checkers, :excluded_paths, :output_format
5
+
6
+ def initialize
7
+ @root = Rails.root.to_s
8
+ @enabled_checkers = all_checkers
9
+ @excluded_paths = %w[vendor spec test node_modules]
10
+ @output_format = :console
11
+ end
12
+
13
+ def all_checkers
14
+ %w[
15
+ sql_injection xss csrf mass_assignment open_redirect secrets
16
+ dos idor ai_injection rate_limit dependency graphql
17
+ ]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ module Rails
2
+ module Guarddog
3
+ class Finding
4
+ attr_accessor :severity, :category, :message, :file, :line, :code_snippet, :remediation
5
+
6
+ def initialize(severity:, category:, message:, file:, line:, code_snippet: "", remediation: "")
7
+ @severity = severity
8
+ @category = category
9
+ @message = message
10
+ @file = file
11
+ @line = line
12
+ @code_snippet = code_snippet
13
+ @remediation = remediation
14
+ end
15
+
16
+ def to_h
17
+ {
18
+ severity: severity,
19
+ category: category,
20
+ message: message,
21
+ file: file,
22
+ line: line,
23
+ code_snippet: code_snippet,
24
+ remediation: remediation
25
+ }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ module Rails
2
+ module Guarddog
3
+ class Railtie < Rails::Railtie
4
+ rake_tasks do
5
+ load "tasks/guarddog.rake"
6
+ end
7
+
8
+ config.guarddog = Configuration.new
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Reporters
4
+ class ConsoleReporter
5
+ def initialize(findings)
6
+ @findings = findings
7
+ end
8
+
9
+ def report
10
+ puts "\n" + "="*60
11
+ puts "Rails GuardDog Security Report".center(60)
12
+ puts "="*60 + "\n"
13
+
14
+ if @findings.empty?
15
+ puts "āœ“ No security issues found!".green
16
+ return
17
+ end
18
+
19
+ @findings.group_by(&:severity).each do |severity, findings|
20
+ puts "\n[#{severity.upcase}] (#{findings.count})"
21
+ findings.each do |finding|
22
+ puts " #{finding.category} — #{finding.message}"
23
+ puts " #{finding.file}:#{finding.line}"
24
+ puts " Fix: #{finding.remediation}\n"
25
+ end
26
+ end
27
+
28
+ puts "\n" + "="*60
29
+ puts "Total findings: #{@findings.count}"
30
+ puts "="*60 + "\n"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,103 @@
1
+ module Rails
2
+ module Guarddog
3
+ module Reporters
4
+ class HtmlReporter
5
+ def initialize(findings)
6
+ @findings = findings
7
+ end
8
+
9
+ def report(output_path = "guarddog_report.html")
10
+ html = generate_html
11
+ File.write(output_path, html)
12
+ output_path
13
+ end
14
+
15
+ private
16
+
17
+ def generate_html
18
+ severity_breakdown = @findings.group_by(&:severity).transform_values(&:count)
19
+
20
+ html = <<~HTML
21
+ <!DOCTYPE html>
22
+ <html>
23
+ <head>
24
+ <meta charset="UTF-8">
25
+ <title>Rails GuardDog Security Report</title>
26
+ <style>
27
+ * { box-sizing: border-box; }
28
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
29
+ .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
30
+ h1 { color: #333; margin-top: 0; }
31
+ .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 20px 0; }
32
+ .stat-card { padding: 16px; border-radius: 6px; text-align: center; }
33
+ .stat-card.critical { background: #fee; color: #c00; }
34
+ .stat-card.high { background: #fef3cd; color: #856404; }
35
+ .stat-card.medium { background: #cfe2ff; color: #084298; }
36
+ .stat-card.low { background: #d1e7dd; color: #0f5132; }
37
+ .stat-card h3 { margin: 0 0 10px; font-size: 28px; }
38
+ .stat-card p { margin: 0; font-size: 12px; }
39
+ .findings { margin-top: 30px; }
40
+ .finding { border-left: 4px solid #ccc; padding: 16px; margin: 16px 0; background: #fafafa; border-radius: 4px; }
41
+ .finding.critical { border-color: #c00; background: #fee; }
42
+ .finding.high { border-color: #ff9800; background: #fff3cd; }
43
+ .finding.medium { border-color: #2196f3; background: #d1ecf1; }
44
+ .finding.low { border-color: #4caf50; background: #d4edda; }
45
+ .finding-severity { font-weight: bold; font-size: 12px; text-transform: uppercase; }
46
+ .finding-title { font-size: 16px; font-weight: 600; margin: 8px 0; }
47
+ .finding-meta { font-size: 13px; color: #666; margin: 8px 0; }
48
+ .finding-code { background: #f0f0f0; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 12px; margin: 8px 0; overflow-x: auto; }
49
+ .finding-remediation { margin-top: 10px; padding: 10px; background: rgba(0,0,0,0.05); border-radius: 4px; font-size: 13px; }
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="container">
54
+ <h1>šŸ• Rails GuardDog Security Report</h1>
55
+ <p>Generated: #{Time.now.strftime("%Y-%m-%d %H:%M:%S")}</p>
56
+
57
+ <div class="stats">
58
+ <div class="stat-card critical">
59
+ <h3>#{severity_breakdown[:critical] || 0}</h3>
60
+ <p>Critical</p>
61
+ </div>
62
+ <div class="stat-card high">
63
+ <h3>#{severity_breakdown[:high] || 0}</h3>
64
+ <p>High</p>
65
+ </div>
66
+ <div class="stat-card medium">
67
+ <h3>#{severity_breakdown[:medium] || 0}</h3>
68
+ <p>Medium</p>
69
+ </div>
70
+ <div class="stat-card low">
71
+ <h3>#{severity_breakdown[:low] || 0}</h3>
72
+ <p>Low</p>
73
+ </div>
74
+ </div>
75
+
76
+ <div class="findings">
77
+ HTML
78
+
79
+ @findings.each do |finding|
80
+ html += %{
81
+ <div class="finding #{finding.severity}">
82
+ <div class="finding-severity">#{finding.severity.upcase}</div>
83
+ <div class="finding-title">#{finding.category} - #{finding.message}</div>
84
+ <div class="finding-meta">#{finding.file}:#{finding.line}</div>
85
+ <div class="finding-code">#{finding.code_snippet}</div>
86
+ <div class="finding-remediation"><strong>Fix:</strong> #{finding.remediation}</div>
87
+ </div>
88
+ }
89
+ end
90
+
91
+ html += <<~HTML
92
+ </div>
93
+ </div>
94
+ </body>
95
+ </html>
96
+ HTML
97
+
98
+ html
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ module Rails
4
+ module Guarddog
5
+ module Reporters
6
+ class JsonReporter
7
+ def initialize(findings)
8
+ @findings = findings
9
+ end
10
+
11
+ def report
12
+ output = {
13
+ timestamp: Time.now.iso8601,
14
+ total_findings: @findings.count,
15
+ severity_breakdown: severity_breakdown,
16
+ findings: @findings.map(&:to_h)
17
+ }
18
+ JSON.pretty_generate(output)
19
+ end
20
+
21
+ private
22
+
23
+ def severity_breakdown
24
+ @findings.group_by(&:severity).transform_values(&:count)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ module Rails
2
+ module Guarddog
3
+ class Scanner
4
+ attr_accessor :configuration, :findings
5
+
6
+ def initialize(config = nil)
7
+ @configuration = config || Configuration.new
8
+ @findings = []
9
+ end
10
+
11
+ def run
12
+ checkers = load_checkers
13
+ checkers.each do |checker|
14
+ checker_instance = checker.new(@configuration.root)
15
+ checker_instance.run
16
+ @findings.concat(checker_instance.findings)
17
+ end
18
+ @findings.sort_by { |f| severity_order(f.severity) }
19
+ end
20
+
21
+ private
22
+
23
+ def load_checkers
24
+ checkers_dir = File.expand_path('../guarddog/checkers', __FILE__)
25
+ Dir.glob("#{checkers_dir}/*_checker.rb").map do |file|
26
+ require file
27
+ class_name = File.basename(file, '.rb').camelize
28
+ Checkers.const_get(class_name)
29
+ end.compact
30
+ end
31
+
32
+ def severity_order(severity)
33
+ { critical: 0, high: 1, medium: 2, low: 3 }[severity] || 4
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,5 @@
1
+ module Rails
2
+ module Guarddog
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails'
2
+ require_relative 'guarddog/version'
3
+ require_relative 'guarddog/configuration'
4
+ require_relative 'guarddog/finding'
5
+ require_relative 'guarddog/scanner'
6
+ require_relative 'guarddog/checkers/base_checker'
7
+ require_relative 'guarddog/checkers/sql_injection_checker'
8
+ require_relative 'guarddog/checkers/xss_checker'
9
+ require_relative 'guarddog/checkers/csrf_checker'
10
+ require_relative 'guarddog/checkers/mass_assignment_checker'
11
+ require_relative 'guarddog/checkers/open_redirect_checker'
12
+ require_relative 'guarddog/checkers/secrets_checker'
13
+ require_relative 'guarddog/checkers/dos_checker'
14
+ require_relative 'guarddog/checkers/idor_checker'
15
+ require_relative 'guarddog/checkers/ai_injection_checker'
16
+ require_relative 'guarddog/checkers/rate_limit_checker'
17
+ require_relative 'guarddog/checkers/dependency_checker'
18
+ require_relative 'guarddog/checkers/graphql_checker'
19
+ require_relative 'guarddog/reporters/console_reporter'
20
+ require_relative 'guarddog/reporters/json_reporter'
21
+ require_relative 'guarddog/reporters/html_reporter'
22
+ require_relative 'guarddog/railtie'
23
+
24
+ module Rails
25
+ module Guarddog
26
+ end
27
+ end
@@ -0,0 +1 @@
1
+ require_relative 'rails/guarddog'
@@ -0,0 +1,45 @@
1
+ namespace :guarddog do
2
+ task :scan do
3
+ require 'rails/guarddog'
4
+
5
+ scanner = Rails::Guarddog::Scanner.new
6
+ findings = scanner.run
7
+
8
+ reporter = Rails::Guarddog::Reporters::ConsoleReporter.new(findings)
9
+ reporter.report
10
+ end
11
+
12
+ task :report do
13
+ require 'rails/guarddog'
14
+
15
+ scanner = Rails::Guarddog::Scanner.new
16
+ findings = scanner.run
17
+
18
+ html_reporter = Rails::Guarddog::Reporters::HtmlReporter.new(findings)
19
+ path = html_reporter.report("guarddog_report.html")
20
+ puts "āœ“ HTML report generated: #{path}"
21
+
22
+ json_reporter = Rails::Guarddog::Reporters::JsonReporter.new(findings)
23
+ File.write("guarddog_report.json", json_reporter.report)
24
+ puts "āœ“ JSON report generated: guarddog_report.json"
25
+ end
26
+
27
+ task :ci do
28
+ require 'rails/guarddog'
29
+
30
+ scanner = Rails::Guarddog::Scanner.new
31
+ findings = scanner.run
32
+
33
+ critical = findings.select { |f| f.severity == :critical }
34
+
35
+ puts Rails::Guarddog::Reporters::JsonReporter.new(findings).report
36
+
37
+ if critical.any?
38
+ puts "\nāŒ CRITICAL VULNERABILITIES FOUND: #{critical.count}"
39
+ exit 1
40
+ else
41
+ puts "\nāœ“ No critical vulnerabilities"
42
+ exit 0
43
+ end
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-guarddog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Security Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: 'Rails GuardDog: Beyond brakeman — AI injection, DoS, supply chain, GraphQL
56
+ auth, and more'
57
+ email:
58
+ - security@example.com
59
+ executables:
60
+ - guarddog
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - LICENSE
65
+ - README.md
66
+ - bin/guarddog
67
+ - lib/rails-guarddog.rb
68
+ - lib/rails/guarddog.rb
69
+ - lib/rails/guarddog/checkers/ai_injection_checker.rb
70
+ - lib/rails/guarddog/checkers/base_checker.rb
71
+ - lib/rails/guarddog/checkers/csrf_checker.rb
72
+ - lib/rails/guarddog/checkers/dependency_checker.rb
73
+ - lib/rails/guarddog/checkers/dos_checker.rb
74
+ - lib/rails/guarddog/checkers/graphql_checker.rb
75
+ - lib/rails/guarddog/checkers/idor_checker.rb
76
+ - lib/rails/guarddog/checkers/mass_assignment_checker.rb
77
+ - lib/rails/guarddog/checkers/open_redirect_checker.rb
78
+ - lib/rails/guarddog/checkers/rate_limit_checker.rb
79
+ - lib/rails/guarddog/checkers/secrets_checker.rb
80
+ - lib/rails/guarddog/checkers/sql_injection_checker.rb
81
+ - lib/rails/guarddog/checkers/xss_checker.rb
82
+ - lib/rails/guarddog/configuration.rb
83
+ - lib/rails/guarddog/finding.rb
84
+ - lib/rails/guarddog/railtie.rb
85
+ - lib/rails/guarddog/reporters/console_reporter.rb
86
+ - lib/rails/guarddog/reporters/html_reporter.rb
87
+ - lib/rails/guarddog/reporters/json_reporter.rb
88
+ - lib/rails/guarddog/scanner.rb
89
+ - lib/rails/guarddog/version.rb
90
+ - lib/tasks/guarddog.rake
91
+ homepage: https://github.com/example/rails-guarddog
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.5.22
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Advanced security checker for Rails apps
114
+ test_files: []