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 +7 -0
- data/lib/ruby_lsp/ruby_lsp_brakeman/addon.rb +225 -0
- metadata +73 -0
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: []
|