brakeman-llm 0.0.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.
- checksums.yaml +7 -0
- data/bin/brakeman-llm +10 -0
- data/docs/warning_types/CVE-2010-3933/index.markdown +17 -0
- data/docs/warning_types/CVE-2011-0446/index.markdown +17 -0
- data/docs/warning_types/CVE-2011-3186/index.markdown +17 -0
- data/docs/warning_types/attribute_restriction/index.markdown +32 -0
- data/docs/warning_types/authentication/index.markdown +23 -0
- data/docs/warning_types/authentication_whitelist/index.markdown +13 -0
- data/docs/warning_types/basic_auth/index.markdown +14 -0
- data/docs/warning_types/basic_authentication/index.markdown +25 -0
- data/docs/warning_types/command_injection/index.markdown +26 -0
- data/docs/warning_types/content_tag/index.markdown +30 -0
- data/docs/warning_types/cross-site_request_forgery/index.markdown +19 -0
- data/docs/warning_types/cross_site_request_forgery/index.markdown +18 -0
- data/docs/warning_types/cross_site_scripting/index.markdown +64 -0
- data/docs/warning_types/cross_site_scripting_to_json/index.markdown +55 -0
- data/docs/warning_types/dangerous_eval/index.markdown +13 -0
- data/docs/warning_types/dangerous_evaluation/index.markdown +14 -0
- data/docs/warning_types/dangerous_send/index.markdown +44 -0
- data/docs/warning_types/default_routes/index.markdown +27 -0
- data/docs/warning_types/denial_of_service/index.markdown +42 -0
- data/docs/warning_types/dynamic_render_path/index.markdown +14 -0
- data/docs/warning_types/dynamic_render_paths/index.markdown +17 -0
- data/docs/warning_types/evaluation/index.markdown +14 -0
- data/docs/warning_types/file_access/index.markdown +23 -0
- data/docs/warning_types/format_validation/index.markdown +15 -0
- data/docs/warning_types/http_verb_confusion/index.markdown +42 -0
- data/docs/warning_types/information_disclosure/index.markdown +20 -0
- data/docs/warning_types/link_to/index.markdown +19 -0
- data/docs/warning_types/link_to_href/index.markdown +19 -0
- data/docs/warning_types/mass_assignment/index.markdown +67 -0
- data/docs/warning_types/model_validation/index.markdown +14 -0
- data/docs/warning_types/path_traversal/index.markdown +57 -0
- data/docs/warning_types/redirect/index.markdown +60 -0
- data/docs/warning_types/remote_code_execution/index.markdown +17 -0
- data/docs/warning_types/remote_code_execution_yaml_load/index.markdown +19 -0
- data/docs/warning_types/session_manipulation/index.markdown +28 -0
- data/docs/warning_types/session_setting/index.markdown +24 -0
- data/docs/warning_types/session_settings/index.markdown +18 -0
- data/docs/warning_types/sql_injection/index.markdown +41 -0
- data/docs/warning_types/ssl_verification_bypass/index.markdown +41 -0
- data/docs/warning_types/unmaintained_dependency/index.markdown +33 -0
- data/docs/warning_types/unsafe_deserialization/index.markdown +17 -0
- data/docs/warning_types/unscoped_find/index.markdown +25 -0
- data/lib/brakeman-llm.rb +226 -0
- metadata +120 -0
data/lib/brakeman-llm.rb
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'brakeman'
|
2
|
+
require 'brakeman/warning'
|
3
|
+
require 'brakeman/options'
|
4
|
+
require 'ruby_llm'
|
5
|
+
|
6
|
+
# Override Brakeman::Warning to add LLM analysis of warning
|
7
|
+
module Brakeman
|
8
|
+
class Warning
|
9
|
+
attr_accessor :llm_analysis
|
10
|
+
|
11
|
+
alias old_to_hash to_hash
|
12
|
+
|
13
|
+
def to_hash(...)
|
14
|
+
old_to_hash(...).tap do |h|
|
15
|
+
h[:llm_analysis] = self.llm_analysis
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Brakeman
|
22
|
+
|
23
|
+
# Simple wrapper for RubyLLM
|
24
|
+
class LLM
|
25
|
+
attr_accessor :assume_model_exists, :instructions, :model, :prompt, :provider
|
26
|
+
attr_reader :llm
|
27
|
+
|
28
|
+
# Configure RubyLLM
|
29
|
+
def initialize(model:, provider:, instructions: nil, prompt: nil, assume_model_exists: false, **kwargs)
|
30
|
+
@llm = RubyLLM.context do |config|
|
31
|
+
kwargs.each do |k, v|
|
32
|
+
case k
|
33
|
+
when :api_key, :api_base, :use_system_role
|
34
|
+
config.send("#{provider}_#{k}=", v)
|
35
|
+
else
|
36
|
+
config.send("#{k}=", v)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
config.log_level = :error unless kwargs.key? :log_level
|
41
|
+
end
|
42
|
+
|
43
|
+
RubyLLM.logger.level = Logger::ERROR if @llm.config.log_level == :error
|
44
|
+
|
45
|
+
@instructions = instructions || 'You are a world-class application security expert with deep expertise in Ruby and Ruby on Rails security.'
|
46
|
+
|
47
|
+
@prompt = prompt || <<~PROMPT
|
48
|
+
Analyze the following security warning resulting from analyzing a Ruby on Rails application with the static analysis security tool Brakeman.
|
49
|
+
Explain the security vulnerability and potential fixes. Jump straight into the explanation, do not have a casual introduction.
|
50
|
+
Do not ask follow-up questions, as this is not an interactive prompt.
|
51
|
+
Keep the explanation to less than 400 words.
|
52
|
+
Ignore 'fingerprint' and 'warning_code' fields and do not explain them.
|
53
|
+
PROMPT
|
54
|
+
|
55
|
+
@model = model
|
56
|
+
@provider = provider
|
57
|
+
@assume_model_exists = assume_model_exists
|
58
|
+
end
|
59
|
+
|
60
|
+
# Analyze single Brakeman warning.
|
61
|
+
# Results analysis as a string.
|
62
|
+
def analyze_warning(warning)
|
63
|
+
chat = @llm.chat(model: @model, provider: @provider, assume_model_exists: @assume_model_exists)
|
64
|
+
chat.with_instructions(@instructions)
|
65
|
+
|
66
|
+
llm_input = <<~INPUT
|
67
|
+
#{@prompt}
|
68
|
+
#{help_doc(warning)}
|
69
|
+
|
70
|
+
The following is a Brakeman security warning in JSON format that describes a potential security vulnerability:
|
71
|
+
#{warning.to_json}
|
72
|
+
INPUT
|
73
|
+
|
74
|
+
response = chat.ask llm_input
|
75
|
+
|
76
|
+
response.content
|
77
|
+
end
|
78
|
+
|
79
|
+
def help_doc(warning)
|
80
|
+
if warning.link.match %r{https://brakemanscanner.org/(.+)/}
|
81
|
+
doc = File.join(__dir__, '..', $1, "index.markdown")
|
82
|
+
|
83
|
+
if File.exist? doc
|
84
|
+
content = File.read doc
|
85
|
+
"Here is background information about this type of vulnerability: #{content}"
|
86
|
+
else
|
87
|
+
puts "No file: #{doc}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module Options
|
94
|
+
class << self
|
95
|
+
alias old_create create_option_parser
|
96
|
+
|
97
|
+
def create_option_parser(options)
|
98
|
+
parser = old_create(options)
|
99
|
+
|
100
|
+
parser.separator ""
|
101
|
+
parser.separator "LLM Options:"
|
102
|
+
|
103
|
+
# Add LLM options
|
104
|
+
parser.on '--llm-model MODEL' do |model|
|
105
|
+
options[:llm] ||= {}
|
106
|
+
options[:llm][:model] = model
|
107
|
+
end
|
108
|
+
|
109
|
+
parser.on '--llm-provider PROVIDER', 'LLM provider (openai, ollama, gemini, etc.)' do |provider|
|
110
|
+
options[:llm] ||= {}
|
111
|
+
options[:llm][:provider] = provider
|
112
|
+
end
|
113
|
+
|
114
|
+
parser.on '--llm-api_key API_KEY', 'LLM provider API key' do |api_key|
|
115
|
+
options[:llm] ||= {}
|
116
|
+
options[:llm][:api_key] = api_key
|
117
|
+
end
|
118
|
+
|
119
|
+
parser.on '--llm-api_base BASE_URL', 'LLM provider base URL' do |url|
|
120
|
+
options[:llm] ||= {}
|
121
|
+
options[:llm][:api_base] = url
|
122
|
+
end
|
123
|
+
|
124
|
+
parser.on '--[no-]llm-disclaimer [DISCLAIMER]', 'Disclaimer to add to each generated message' do |disclaimer|
|
125
|
+
options[:llm] ||= {}
|
126
|
+
|
127
|
+
if disclaimer
|
128
|
+
options[:llm][:disclaimer] = disclaimer
|
129
|
+
else
|
130
|
+
options[:llm][:disclaimer] = :none
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
parser.separator ""
|
135
|
+
|
136
|
+
parser
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class << self
|
142
|
+
alias old_run run
|
143
|
+
|
144
|
+
def ensure_llm_options(options)
|
145
|
+
return if options[:llm]
|
146
|
+
|
147
|
+
# Check config file, but only grab LLM options
|
148
|
+
config_options = self.load_options(options)
|
149
|
+
|
150
|
+
if config_options[:llm]
|
151
|
+
options[:llm] = config_options[:llm]
|
152
|
+
else
|
153
|
+
raise 'Missing LLM configuration'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def run(options)
|
158
|
+
ensure_llm_options(options)
|
159
|
+
|
160
|
+
# Suppress report output until after analysis
|
161
|
+
output_formats = get_output_formats(options)
|
162
|
+
output_files = options.delete(:output_files)
|
163
|
+
options.delete(:output_format)
|
164
|
+
print_report = options.delete(:print_report)
|
165
|
+
|
166
|
+
# Actually run scan
|
167
|
+
tracker = old_run(options)
|
168
|
+
|
169
|
+
# Set up LLM
|
170
|
+
llm_opts = options.delete(:llm) || {}
|
171
|
+
|
172
|
+
disclaimer = llm_opts.delete(:disclaimer) || '(The above message is auto-generated and may contain errors.)'
|
173
|
+
if disclaimer == :none
|
174
|
+
disclaimer = false
|
175
|
+
end
|
176
|
+
|
177
|
+
llm_opts[:log_level] = :debug if @debug
|
178
|
+
llm = llm_opts.delete(:llm) || Brakeman::LLM.new(**llm_opts)
|
179
|
+
|
180
|
+
set_analysis = output_formats.include? :to_json
|
181
|
+
|
182
|
+
notify 'Asking LLM for extended descriptions...'
|
183
|
+
|
184
|
+
warnings = tracker.warnings
|
185
|
+
total = warnings.length
|
186
|
+
|
187
|
+
# Update warnings with LLM analysis
|
188
|
+
warnings.each_with_index do |warning, index|
|
189
|
+
unless @quiet or options[:report_progress] == false
|
190
|
+
$stderr.print " #{index}/#{total} warnings processed\r"
|
191
|
+
end
|
192
|
+
|
193
|
+
if set_analysis
|
194
|
+
warning.llm_analysis = llm.analyze_warning(warning)
|
195
|
+
|
196
|
+
if disclaimer
|
197
|
+
warning.llm_analysis << "\n\n" << disclaimer
|
198
|
+
end
|
199
|
+
else
|
200
|
+
warning.message << "\n\n" << llm.analyze_warning(warning)
|
201
|
+
|
202
|
+
if disclaimer
|
203
|
+
warning.message << "\n\n" << disclaimer
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Move message to end of the warning output for text report
|
209
|
+
# because LLMs can be quite wordy
|
210
|
+
tracker.options[:text_fields] ||= [:confidence, :category, :check, :code, :file, :line, :message]
|
211
|
+
tracker.options[:output_formats] = output_formats
|
212
|
+
|
213
|
+
if output_files
|
214
|
+
notify "Generating report..."
|
215
|
+
|
216
|
+
write_report_to_files tracker, output_files
|
217
|
+
elsif print_report
|
218
|
+
notify "Generating report..."
|
219
|
+
|
220
|
+
write_report_to_formats tracker, output_formats
|
221
|
+
end
|
222
|
+
|
223
|
+
tracker
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brakeman-llm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Collins
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-08-30 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: brakeman
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '7.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '7.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ruby_llm
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.6'
|
33
|
+
- - "<="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '2.0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.6'
|
43
|
+
- - "<="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.0'
|
46
|
+
description: Brakeman detects security vulnerabilities in Ruby on Rails applications
|
47
|
+
via static analysis.
|
48
|
+
email: gem@brakeman.org
|
49
|
+
executables:
|
50
|
+
- brakeman-llm
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- bin/brakeman-llm
|
55
|
+
- docs/warning_types/CVE-2010-3933/index.markdown
|
56
|
+
- docs/warning_types/CVE-2011-0446/index.markdown
|
57
|
+
- docs/warning_types/CVE-2011-3186/index.markdown
|
58
|
+
- docs/warning_types/attribute_restriction/index.markdown
|
59
|
+
- docs/warning_types/authentication/index.markdown
|
60
|
+
- docs/warning_types/authentication_whitelist/index.markdown
|
61
|
+
- docs/warning_types/basic_auth/index.markdown
|
62
|
+
- docs/warning_types/basic_authentication/index.markdown
|
63
|
+
- docs/warning_types/command_injection/index.markdown
|
64
|
+
- docs/warning_types/content_tag/index.markdown
|
65
|
+
- docs/warning_types/cross-site_request_forgery/index.markdown
|
66
|
+
- docs/warning_types/cross_site_request_forgery/index.markdown
|
67
|
+
- docs/warning_types/cross_site_scripting/index.markdown
|
68
|
+
- docs/warning_types/cross_site_scripting_to_json/index.markdown
|
69
|
+
- docs/warning_types/dangerous_eval/index.markdown
|
70
|
+
- docs/warning_types/dangerous_evaluation/index.markdown
|
71
|
+
- docs/warning_types/dangerous_send/index.markdown
|
72
|
+
- docs/warning_types/default_routes/index.markdown
|
73
|
+
- docs/warning_types/denial_of_service/index.markdown
|
74
|
+
- docs/warning_types/dynamic_render_path/index.markdown
|
75
|
+
- docs/warning_types/dynamic_render_paths/index.markdown
|
76
|
+
- docs/warning_types/evaluation/index.markdown
|
77
|
+
- docs/warning_types/file_access/index.markdown
|
78
|
+
- docs/warning_types/format_validation/index.markdown
|
79
|
+
- docs/warning_types/http_verb_confusion/index.markdown
|
80
|
+
- docs/warning_types/information_disclosure/index.markdown
|
81
|
+
- docs/warning_types/link_to/index.markdown
|
82
|
+
- docs/warning_types/link_to_href/index.markdown
|
83
|
+
- docs/warning_types/mass_assignment/index.markdown
|
84
|
+
- docs/warning_types/model_validation/index.markdown
|
85
|
+
- docs/warning_types/path_traversal/index.markdown
|
86
|
+
- docs/warning_types/redirect/index.markdown
|
87
|
+
- docs/warning_types/remote_code_execution/index.markdown
|
88
|
+
- docs/warning_types/remote_code_execution_yaml_load/index.markdown
|
89
|
+
- docs/warning_types/session_manipulation/index.markdown
|
90
|
+
- docs/warning_types/session_setting/index.markdown
|
91
|
+
- docs/warning_types/session_settings/index.markdown
|
92
|
+
- docs/warning_types/sql_injection/index.markdown
|
93
|
+
- docs/warning_types/ssl_verification_bypass/index.markdown
|
94
|
+
- docs/warning_types/unmaintained_dependency/index.markdown
|
95
|
+
- docs/warning_types/unsafe_deserialization/index.markdown
|
96
|
+
- docs/warning_types/unscoped_find/index.markdown
|
97
|
+
- lib/brakeman-llm.rb
|
98
|
+
homepage: https://github.com/presidentbeef/brakeman-llm
|
99
|
+
licenses:
|
100
|
+
- MIT
|
101
|
+
metadata:
|
102
|
+
source_code_uri: https://github.com/presidentbeef/brakeman-llm
|
103
|
+
rdoc_options: []
|
104
|
+
require_paths:
|
105
|
+
- lib
|
106
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.1.0
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubygems_version: 3.6.2
|
118
|
+
specification_version: 4
|
119
|
+
summary: Enhance Brakeman warnings with LLM-based descriptions
|
120
|
+
test_files: []
|