ruby-lsp-brakeman 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 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: []