ruby-lsp-brakeman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: de695862008729791988f68c61d452cf4cd8418dc87e6164b02af374a8274089
4
+ data.tar.gz: 3031babb7cd6a29421afafc3aaa002cdab878d1238f20b50725bbeceb1aed72f
5
+ SHA512:
6
+ metadata.gz: fbba4390252e02c1f2686c5738ccbf58fb0988dc69f138caae5338c8d3046e4f8a1d1dd12b9de2328d1c5ca749ef4a58498810a354f10992df35b2046460246e
7
+ data.tar.gz: def0387a4563e972fecdd6e824154d9b127623952f76edfa95c989f52bb7928a3d6e7ce63fd5945cbc47184f0b7f263ccf544138216381db388b93cc95c61a25
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "brakeman"
4
+ require "brakeman/rescanner"
5
+ require "ruby_lsp/addon"
6
+ require "thread"
7
+
8
+ module RubyLsp
9
+ module BrakemanLsp
10
+ class Addon < ::RubyLsp::Addon
11
+ FILE_GLOBS = [
12
+ '**/brakeman.yaml',
13
+ '**/brakeman.yml',
14
+ '**/*.html.erb',
15
+ '**/*.js.erb',
16
+ '**/*.html.haml',
17
+ '**/*.html.slim',
18
+ '**/*.rhtml',
19
+ '**/Gemfile',
20
+ '**/Gemfile.lock',
21
+ '**/gems.rb',
22
+ '**/gems.locked',
23
+ '**/*.gemspec',
24
+ '**/.ruby-version',
25
+ ]
26
+
27
+ def initialize
28
+ super
29
+
30
+ @brakeman = nil
31
+ @changed_queue = Queue.new
32
+ end
33
+
34
+ # Kick off Brakeman scan in the background
35
+ def activate(global_state, message_queue)
36
+ @message_queue = message_queue
37
+
38
+ unless Brakeman.respond_to?(:run)
39
+ notify('Failed to activate Ruby LSP Brakeman')
40
+ return
41
+ end
42
+
43
+ Thread.new do
44
+ @brakeman = Brakeman.run(app_path: global_state.workspace_path, support_rescanning: true)
45
+
46
+ notify("Initial Brakeman scan complete - #{@brakeman.filtered_warnings.length} warnings found")
47
+
48
+ add_warnings(@brakeman.filtered_warnings)
49
+
50
+ rescan
51
+ end
52
+
53
+ register_additional_file_watchers(global_state, message_queue)
54
+
55
+ notify('Activated Ruby LSP Brakeman, running initial scan')
56
+ end
57
+
58
+ # Watch additional files, not just *.rb
59
+ def register_additional_file_watchers(global_state, message_queue)
60
+ # Clients are not required to implement this capability
61
+ return unless global_state.supports_watching_files
62
+
63
+ watchers = FILE_GLOBS.map do |pattern|
64
+ Interface::FileSystemWatcher.new(
65
+ glob_pattern: pattern,
66
+ kind: Constant::WatchKind::CREATE | Constant::WatchKind::CHANGE | Constant::WatchKind::DELETE
67
+ )
68
+ end
69
+
70
+ message_queue << Request.new(
71
+ id: "ruby-lsp-brakeman-file-watcher",
72
+ method: "client/registerCapability",
73
+ params: Interface::RegistrationParams.new(
74
+ registrations: [
75
+ Interface::Registration.new(
76
+ id: "workspace/didChangeWatchedFilesMyGem",
77
+ method: "workspace/didChangeWatchedFiles",
78
+ register_options: Interface::DidChangeWatchedFilesRegistrationOptions.new(
79
+ watchers: watchers,
80
+ ),
81
+ ),
82
+ ],
83
+ ),
84
+ )
85
+ end
86
+
87
+ # Send warnings to the client as diagnostic messages
88
+ def add_warnings(warnings, fixed_warnings = [])
89
+
90
+ # Each "publishDiagnostics" message to the client provides
91
+ # a list of diagnostic messages per file.
92
+ # Here we group the warnings by file and convert the warnings
93
+ # to diagnostics.
94
+ diagnostics = warnings.group_by do |warning|
95
+ warning.file.absolute
96
+ end.each_value do |warnings|
97
+ warnings.map! do |warning|
98
+ warning_to_lsp_diagnostic(warning)
99
+ end
100
+ end
101
+
102
+ # Send diagnostics to client, grouped by file
103
+ diagnostics.each do |path, diags|
104
+ @message_queue << Notification.new(
105
+ method: 'textDocument/publishDiagnostics',
106
+ params: Interface::PublishDiagnosticsParams.new(uri: URI::Generic.from_path(path: path), diagnostics: diags)
107
+ )
108
+ end
109
+
110
+ # If a file used to have warnings, but they are now
111
+ # all fixed, send an empty array to clear old warnings in the
112
+ # client. Otherwise they can hang around.
113
+ fixed_warnings.group_by do |warning|
114
+ warning.file.absolute
115
+ end.each do |path, warnings|
116
+ next if diagnostics[path] # Only clear diagnostics if no warnings for file
117
+
118
+ # Otherwise, send empty message for file to clear
119
+ @message_queue << Notification.new(
120
+ method: 'textDocument/publishDiagnostics',
121
+ params: Interface::PublishDiagnosticsParams.new(uri: URI::Generic.from_path(path: path), diagnostics: [])
122
+ )
123
+ end
124
+ end
125
+
126
+ # Convert a Brakeman warning to a diagnostic
127
+ def warning_to_lsp_diagnostic(warning)
128
+ severity = case warning.confidence
129
+ when 0 # High
130
+ Constant::DiagnosticSeverity::ERROR
131
+ when 1 # Medium
132
+ Constant::DiagnosticSeverity::WARNING
133
+ when 2 # Low
134
+ Constant::DiagnosticSeverity::INFORMATION
135
+ else # Theoretical other levels
136
+ Constant::DiagnosticSeverity::INFORMATION
137
+ end
138
+
139
+ Interface::Diagnostic.new(
140
+ source: "Brakeman",
141
+ message: warning_message(warning),
142
+ severity: severity,
143
+ range: Interface::Range.new(
144
+ start: Interface::Position.new(
145
+ line: warning.line - 1, # Zero indexed lines
146
+ character: 0, # "Start of line"
147
+ ),
148
+ end: Interface::Position.new(
149
+ line: warning.line - 1,
150
+ character: 1000, # "End of line"
151
+ ),
152
+ ),
153
+ code: warning.code,
154
+ code_description: Interface::CodeDescription.new(href: warning.link) # Does not work in VSCode?
155
+ )
156
+ end
157
+
158
+ # Format the warning message
159
+ def warning_message(warning)
160
+ parts = ["[#{warning.warning_type}] #{warning.message}.\n"]
161
+
162
+ if warning.user_input
163
+ parts << "Dangerous value: `#{warning.format_user_input}`"
164
+ end
165
+
166
+ parts.join("\n")
167
+ end
168
+
169
+ def deactivate
170
+ end
171
+
172
+ # Returns the name of the addon
173
+ def name
174
+ "Ruby LSP Brakeman"
175
+ end
176
+
177
+ # When any files change, add them to the queue for rescanning.
178
+ def workspace_did_change_watched_files(changes)
179
+ changed_files = changes.map { |change| URI(change[:uri]).path }
180
+ changed_files.each { |path| @changed_queue << path }
181
+
182
+
183
+ notify("Queued #{changed_files.join(', ')}")
184
+ end
185
+
186
+ # Wait for changed files, then scan them.
187
+ # Can handle multiple changed files (e.g. if files changed during a scan)
188
+ def rescan
189
+ loop do
190
+ # Grab the first file off the top of the queue.
191
+ # Will block until there's a file in the queue.
192
+ first_path = @changed_queue.pop
193
+ changed_files = [first_path]
194
+
195
+ # Get the rest of the files from the queue, if any.
196
+ @changed_queue.length.times do
197
+ changed_files << @changed_queue.pop
198
+ end
199
+
200
+ changed_files.uniq!
201
+
202
+ notify("Rescanning #{changed_files.join(', ')}")
203
+
204
+ # Rescan the changed files
205
+ rescanner = Brakeman::Rescanner.new(@brakeman.options, @brakeman.processor, changed_files)
206
+ rescan = rescanner.recheck
207
+ @brakeman = rescanner.tracker
208
+
209
+ notify("Rescanned #{changed_files.join(', ')}")
210
+
211
+ # Send new/fixed warning information to the client
212
+ add_warnings(rescan.all_warnings, rescan.fixed_warnings)
213
+
214
+ # Log the results
215
+ notify("Warnings: #{rescan.new_warnings.length} new, #{rescan.fixed_warnings.length} fixed, #{rescan.all_warnings.length} total")
216
+ end
217
+ end
218
+
219
+ # Send logging information to the client
220
+ def notify(message)
221
+ @message_queue << Notification.window_log_message("[Brakeman] #{message.to_s}")
222
+ end
223
+ end
224
+ end
225
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-lsp-brakeman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Collins
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: brakeman
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 7.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 7.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-lsp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ description: Brakeman detects security vulnerabilities in Ruby on Rails applications
42
+ via static analysis.
43
+ email: gem@brakeman.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/ruby_lsp/ruby_lsp_brakeman/addon.rb
49
+ homepage: https://github.com/presidentbeef/ruby-lsp-brakeman
50
+ licenses:
51
+ - MIT
52
+ metadata:
53
+ source_code_uri: https://github.com/presidentbeef/ruby-lsp-brakeman
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 3.1.0
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubygems_version: 3.5.16
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Run Brakeman via Ruby Language Server
73
+ test_files: []