owasp-glue 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGES +27 -0
- data/FEATURES +19 -0
- data/README.md +117 -0
- data/bin/glue +67 -0
- data/lib/glue.rb +317 -0
- data/lib/glue/event.rb +14 -0
- data/lib/glue/filters.rb +41 -0
- data/lib/glue/filters/base_filter.rb +19 -0
- data/lib/glue/filters/jira_one_time_filter.rb +57 -0
- data/lib/glue/filters/remove_all_filter.rb +16 -0
- data/lib/glue/filters/zap_consdensing_filter.rb +76 -0
- data/lib/glue/finding.rb +52 -0
- data/lib/glue/mounters.rb +55 -0
- data/lib/glue/mounters/base_mounter.rb +31 -0
- data/lib/glue/mounters/docker_mounter.rb +44 -0
- data/lib/glue/mounters/filesystem_mounter.rb +20 -0
- data/lib/glue/mounters/git_mounter.rb +52 -0
- data/lib/glue/mounters/iso_mounter.rb +42 -0
- data/lib/glue/mounters/url_mounter.rb +28 -0
- data/lib/glue/options.rb +269 -0
- data/lib/glue/reporters.rb +50 -0
- data/lib/glue/reporters/base_reporter.rb +21 -0
- data/lib/glue/reporters/csv_reporter.rb +19 -0
- data/lib/glue/reporters/jira_reporter.rb +59 -0
- data/lib/glue/reporters/json_reporter.rb +20 -0
- data/lib/glue/reporters/text_reporter.rb +19 -0
- data/lib/glue/scanner.rb +28 -0
- data/lib/glue/tasks.rb +124 -0
- data/lib/glue/tasks/av.rb +42 -0
- data/lib/glue/tasks/base_task.rb +80 -0
- data/lib/glue/tasks/brakeman.rb +58 -0
- data/lib/glue/tasks/bundle-audit.rb +95 -0
- data/lib/glue/tasks/checkmarx.rb +60 -0
- data/lib/glue/tasks/dawnscanner.rb +55 -0
- data/lib/glue/tasks/eslint.rb +69 -0
- data/lib/glue/tasks/fim.rb +60 -0
- data/lib/glue/tasks/findsecbugs.rb +90 -0
- data/lib/glue/tasks/npm.rb +58 -0
- data/lib/glue/tasks/nsp.rb +65 -0
- data/lib/glue/tasks/owasp-dep-check.rb +117 -0
- data/lib/glue/tasks/patterns.json +394 -0
- data/lib/glue/tasks/pmd.rb +63 -0
- data/lib/glue/tasks/retirejs.rb +107 -0
- data/lib/glue/tasks/scanjs-eslintrc +106 -0
- data/lib/glue/tasks/scanjs.rb +31 -0
- data/lib/glue/tasks/sfl.rb +67 -0
- data/lib/glue/tasks/snyk.rb +81 -0
- data/lib/glue/tasks/test.rb +47 -0
- data/lib/glue/tasks/zap.rb +99 -0
- data/lib/glue/tracker.rb +47 -0
- data/lib/glue/util.rb +36 -0
- data/lib/glue/version.rb +3 -0
- metadata +294 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
class Glue::PMD < Glue::BaseTask
|
7
|
+
|
8
|
+
Glue::Tasks.add self
|
9
|
+
include Glue::Util
|
10
|
+
|
11
|
+
def initialize(trigger, tracker)
|
12
|
+
super(trigger, tracker)
|
13
|
+
@name = "PMD"
|
14
|
+
@description = "PMD Source Code Analyzer"
|
15
|
+
@stage = :code
|
16
|
+
@labels << "code"
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
@tracker.options[:pmd_checks] ||= "java-basic,java-sunsecure"
|
21
|
+
Dir.chdir @tracker.options[:pmd_path] do
|
22
|
+
@results = Nokogiri::XML(runsystem(true,'bin/run.sh', 'pmd', '-d', "#{@trigger.path}", '-f', 'xml', '-R', "#{@tracker.options[:pmd_checks]}")).xpath('//file')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def analyze
|
27
|
+
begin
|
28
|
+
@results.each do |result|
|
29
|
+
attributes = result.at_xpath('violation').attributes
|
30
|
+
description = result.children.children.to_s.strip
|
31
|
+
detail = "Ruleset: #{attributes['ruleset']}"
|
32
|
+
source = {:scanner => @name, :file => result.attributes['name'].to_s.split(Pathname.new(@trigger.path).cleanpath.to_s)[1][1..-1], :line => attributes['beginline'].to_s, :code => "package: #{attributes['package'].to_s}\nclass: #{attributes['class'].to_s}\nmethod: #{attributes['method'].to_s}" }
|
33
|
+
case attributes['priority'].value.to_i
|
34
|
+
when 3
|
35
|
+
sev = 1
|
36
|
+
when 2
|
37
|
+
sev = 2
|
38
|
+
when 1
|
39
|
+
sev = 3
|
40
|
+
else
|
41
|
+
sev = 0
|
42
|
+
end
|
43
|
+
fprint = fingerprint("#{description}#{detail}#{source}#{sev}")
|
44
|
+
|
45
|
+
report description, detail, source, sev, fprint
|
46
|
+
end
|
47
|
+
rescue Exception => e
|
48
|
+
Glue.warn e.message
|
49
|
+
Glue.warn e.backtrace
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def supported?
|
54
|
+
unless @tracker.options.has_key?(:pmd_path) and File.exist?("#{@tracker.options[:pmd_path]}/bin/run.sh")
|
55
|
+
Glue.notify "#{@tracker.options[:pmd_path]}"
|
56
|
+
Glue.notify "Install PMD from: https://pmd.github.io/"
|
57
|
+
return false
|
58
|
+
else
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'json'
|
3
|
+
require 'glue/util'
|
4
|
+
require 'jsonpath'
|
5
|
+
require 'pathname'
|
6
|
+
require 'pry'
|
7
|
+
|
8
|
+
class Glue::RetireJS < Glue::BaseTask
|
9
|
+
|
10
|
+
Glue::Tasks.add self
|
11
|
+
include Glue::Util
|
12
|
+
|
13
|
+
def initialize(trigger, tracker)
|
14
|
+
super(trigger, tracker)
|
15
|
+
@name = "RetireJS"
|
16
|
+
@description = "Dependency analysis for JavaScript"
|
17
|
+
@stage = :code
|
18
|
+
@labels << "code" << "javascript"
|
19
|
+
@results = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
exclude_dirs = ['node_modules','bower_components']
|
24
|
+
exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
|
25
|
+
directories_with?('package.json', exclude_dirs).each do |dir|
|
26
|
+
Glue.notify "#{@name} scanning: #{dir}"
|
27
|
+
@results << runsystem(false, 'retire', '-c', '--outputformat', 'json', '--path', "#{dir}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def analyze
|
32
|
+
begin
|
33
|
+
@results.each do |result|
|
34
|
+
parsed_json = JSON.parse(result)
|
35
|
+
vulnerabilities = parse_retire_json(parsed_json) if parsed_json
|
36
|
+
|
37
|
+
vulnerabilities.each do |vuln|
|
38
|
+
report "Package #{vuln[:package]} has known security issues", vuln[:detail], vuln[:source], vuln[:severity], fingerprint("#{vuln[:package]}#{vuln[:source]}#{vuln[:severity]}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue JSON::ParserError => e
|
42
|
+
Glue.debug e.message
|
43
|
+
rescue Exception => e
|
44
|
+
Glue.warn e.message
|
45
|
+
Glue.warn e.backtrace
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_retire_json result
|
50
|
+
Glue.debug "Retire JSON Raw Result: #{result}"
|
51
|
+
vulnerabilities = []
|
52
|
+
# This is very ugly, but so is the json retire.js spits out
|
53
|
+
# Loop through each component/version combo and pull all results for it
|
54
|
+
JsonPath.on(result, '$..component').uniq.each do |comp|
|
55
|
+
JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
|
56
|
+
vuln_hash = {}
|
57
|
+
vuln_hash[:package] = "#{comp}-#{version}"
|
58
|
+
|
59
|
+
version_results = JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')]").select { |r| r['version'] == version }.uniq
|
60
|
+
|
61
|
+
# If we see the parent-->component relationship, dig through the dependency tree to try and make a dep map
|
62
|
+
deps = []
|
63
|
+
obj = version_results[0]
|
64
|
+
while !obj['parent'].nil?
|
65
|
+
deps << obj['parent']['component']
|
66
|
+
obj = obj['parent']
|
67
|
+
end
|
68
|
+
if deps.length > 0
|
69
|
+
vuln_hash[:source] = { :scanner => @name, :file => "#{deps.reverse.join('->')}->#{comp}-#{version}", :line => nil, :code => nil }
|
70
|
+
end
|
71
|
+
|
72
|
+
vuln_hash[:severity] = 'unknown'
|
73
|
+
# pull detail/severity
|
74
|
+
version_results.each do |version_result|
|
75
|
+
JsonPath.on(version_result, '$..vulnerabilities').uniq.each do |vuln|
|
76
|
+
vuln_hash[:severity] = severity(vuln[0]['severity'])
|
77
|
+
vuln_hash[:detail] = vuln[0]['info'].join('\n')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
vulnerabilities << vuln_hash
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Loop through the separately reported 'file' findings so we can tag the source (no dep map here)
|
86
|
+
result.select { |r| !r['file'].nil? }.each do |file_result|
|
87
|
+
JsonPath.on(file_result, '$..component').uniq.each do |comp|
|
88
|
+
JsonPath.on(file_result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
|
89
|
+
source_path = relative_path(file_result['file'], @trigger.path)
|
90
|
+
vulnerabilities.select { |v| v[:package] == "#{comp}-#{version}" }.first[:source] = { :scanner => @name, :file => source_path.to_s, :line => nil, :code => nil }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
return vulnerabilities
|
95
|
+
end
|
96
|
+
|
97
|
+
def supported?
|
98
|
+
supported=runsystem(false, "retire", "--help")
|
99
|
+
if supported =~ /command not found/
|
100
|
+
Glue.notify "Install RetireJS"
|
101
|
+
return false
|
102
|
+
else
|
103
|
+
return true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
{ "ecmaFeatures" : {
|
2
|
+
"modules" : true
|
3
|
+
},
|
4
|
+
"env" : {
|
5
|
+
"browser" : true,
|
6
|
+
"es6" : true /** all es6 features except modules */
|
7
|
+
},
|
8
|
+
"plugins" : [
|
9
|
+
"scanjs-rules",
|
10
|
+
"no-unsafe-innerhtml"
|
11
|
+
],
|
12
|
+
"rules" : {
|
13
|
+
/** useful rules from eslint **/
|
14
|
+
|
15
|
+
/** no-unsafe-innerhtml rule **/
|
16
|
+
"no-unsafe-innerhtml/no-unsafe-innerhtml" : 2,
|
17
|
+
|
18
|
+
/** ScanJS rules **/
|
19
|
+
"scanjs-rules/assign_to_hostname" : 1,
|
20
|
+
"scanjs-rules/assign_to_href" : 1,
|
21
|
+
"scanjs-rules/assign_to_location" : 1,
|
22
|
+
"scanjs-rules/assign_to_mozAudioChannel" : 1,
|
23
|
+
"scanjs-rules/assign_to_mozAudioChannelType" : 1,
|
24
|
+
"scanjs-rules/assign_to_onmessage" : 1,
|
25
|
+
"scanjs-rules/assign_to_pathname" : 1,
|
26
|
+
"scanjs-rules/assign_to_protocol" : 1,
|
27
|
+
"scanjs-rules/assign_to_search" : 1,
|
28
|
+
"scanjs-rules/assign_to_src" : 1,
|
29
|
+
"scanjs-rules/call_Function" : 1,
|
30
|
+
"scanjs-rules/call_addEventListener" : 1,
|
31
|
+
"scanjs-rules/call_addEventListener_cellbroadcastmsgchanged" : 1,
|
32
|
+
"scanjs-rules/call_addEventListener_deviceproximity" : 1,
|
33
|
+
"scanjs-rules/call_addEventListener_message" : 1,
|
34
|
+
"scanjs-rules/call_addEventListener_moznetworkdownload" : 1,
|
35
|
+
"scanjs-rules/call_addEventListener_moznetworkupload" : 1,
|
36
|
+
"scanjs-rules/call_connect" : 1,
|
37
|
+
"scanjs-rules/call_eval" : 1,
|
38
|
+
"scanjs-rules/call_execScript" : 1,
|
39
|
+
"scanjs-rules/call_generateCRMFRequest" : 1,
|
40
|
+
"scanjs-rules/call_getDeviceStorage_apps" : 1,
|
41
|
+
"scanjs-rules/call_getDeviceStorage_crashes" : 1,
|
42
|
+
"scanjs-rules/call_getDeviceStorage_music" : 1,
|
43
|
+
"scanjs-rules/call_getDeviceStorage_pictures" : 1,
|
44
|
+
"scanjs-rules/call_getDeviceStorage_sdcard" : 1,
|
45
|
+
"scanjs-rules/call_getDeviceStorage_videos" : 1,
|
46
|
+
"scanjs-rules/call_hide" : 1,
|
47
|
+
"scanjs-rules/call_mozSetMessageHandler" : 1,
|
48
|
+
"scanjs-rules/call_mozSetMessageHandler_activity" : 1,
|
49
|
+
"scanjs-rules/call_mozSetMessageHandler_wappush_received" : 1,
|
50
|
+
"scanjs-rules/call_open_attention" : 1,
|
51
|
+
"scanjs-rules/call_open_remote=true" : 1,
|
52
|
+
"scanjs-rules/call_parseFromString" : 1,
|
53
|
+
"scanjs-rules/call_setAttribute_mozapp" : 1,
|
54
|
+
"scanjs-rules/call_setAttribute_mozbrowser" : 1,
|
55
|
+
"scanjs-rules/call_setImmediate" : 1,
|
56
|
+
"scanjs-rules/call_setInterval" : 1,
|
57
|
+
"scanjs-rules/call_setMessageHandler_connect" : 1,
|
58
|
+
"scanjs-rules/call_setTimeout" : 1,
|
59
|
+
"scanjs-rules/call_write" : 1,
|
60
|
+
"scanjs-rules/call_writeln" : 1,
|
61
|
+
"scanjs-rules/identifier_indexedDB" : 1,
|
62
|
+
"scanjs-rules/identifier_localStorage" : 1,
|
63
|
+
"scanjs-rules/identifier_sessionStorage" : 1,
|
64
|
+
"scanjs-rules/new_Function" : 1,
|
65
|
+
"scanjs-rules/new_MozActivity" : 1,
|
66
|
+
"scanjs-rules/new_MozSpeakerManager" : 1,
|
67
|
+
"scanjs-rules/new_Notification" : 1,
|
68
|
+
"scanjs-rules/object_mozSystem" : 1,
|
69
|
+
"scanjs-rules/property_addIdleObserver" : 1,
|
70
|
+
"scanjs-rules/property_createContextualFragment" : 1,
|
71
|
+
"scanjs-rules/property_geolocation" : 1,
|
72
|
+
"scanjs-rules/property_getDataStores" : 1,
|
73
|
+
"scanjs-rules/property_getDeviceStorage" : 1,
|
74
|
+
"scanjs-rules/property_getUserMedia" : 1,
|
75
|
+
"scanjs-rules/property_indexedDB" : 1,
|
76
|
+
"scanjs-rules/property_lastKnownHomeNetwork" : 1,
|
77
|
+
"scanjs-rules/property_lastKnownNetwork" : 1,
|
78
|
+
"scanjs-rules/property_localStorage" : 1,
|
79
|
+
"scanjs-rules/property_mgmt" : 1,
|
80
|
+
"scanjs-rules/property_mozAlarms" : 1,
|
81
|
+
"scanjs-rules/property_mozBluetooth" : 1,
|
82
|
+
"scanjs-rules/property_mozCameras" : 1,
|
83
|
+
"scanjs-rules/property_mozCellBroadcast" : 1,
|
84
|
+
"scanjs-rules/property_mozContacts" : 1,
|
85
|
+
"scanjs-rules/property_mozDownloadManager" : 1,
|
86
|
+
"scanjs-rules/property_mozFMRadio" : 1,
|
87
|
+
"scanjs-rules/property_mozInputMethod" : 1,
|
88
|
+
"scanjs-rules/property_mozKeyboard" : 1,
|
89
|
+
"scanjs-rules/property_mozMobileConnection" : 1,
|
90
|
+
"scanjs-rules/property_mozMobileConnections" : 1,
|
91
|
+
"scanjs-rules/property_mozMobileMessage" : 1,
|
92
|
+
"scanjs-rules/property_mozNetworkStats" : 1,
|
93
|
+
"scanjs-rules/property_mozNfc" : 1,
|
94
|
+
"scanjs-rules/property_mozNotification" : 1,
|
95
|
+
"scanjs-rules/property_mozPermissionSettings" : 1,
|
96
|
+
"scanjs-rules/property_mozPhoneNumberService" : 1,
|
97
|
+
"scanjs-rules/property_mozPower" : 1,
|
98
|
+
"scanjs-rules/property_mozSettings" : 1,
|
99
|
+
"scanjs-rules/property_mozTCPSocket" : 1,
|
100
|
+
"scanjs-rules/property_mozTelephony" : 1,
|
101
|
+
"scanjs-rules/property_mozTime" : 1,
|
102
|
+
"scanjs-rules/property_mozVoicemail" : 1,
|
103
|
+
"scanjs-rules/property_mozWifiManager" : 1,
|
104
|
+
"scanjs-rules/property_sessionStorage" : 1
|
105
|
+
}
|
106
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
|
3
|
+
class Glue::ScanJS < Glue::BaseTask
|
4
|
+
|
5
|
+
# WIP
|
6
|
+
# Glue::Tasks.add self
|
7
|
+
|
8
|
+
def initialize(trigger, tracker)
|
9
|
+
super(trigger)
|
10
|
+
@name = "ScanJS"
|
11
|
+
@description = "Source analysis for JavaScript"
|
12
|
+
@stage = :code
|
13
|
+
@labels << "code" << "javascript"
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
Glue.notify "#{@name}"
|
18
|
+
rootpath = @trigger.path
|
19
|
+
@result=`scanner.js -t "#{rootpath}"`
|
20
|
+
end
|
21
|
+
|
22
|
+
def analyze
|
23
|
+
puts @result
|
24
|
+
end
|
25
|
+
|
26
|
+
def supported?
|
27
|
+
# In future, verify tool is available.
|
28
|
+
return true
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'json'
|
3
|
+
require 'glue/util'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
class Glue::SFL < Glue::BaseTask
|
7
|
+
|
8
|
+
Glue::Tasks.add self
|
9
|
+
include Glue::Util
|
10
|
+
|
11
|
+
def initialize(trigger, tracker)
|
12
|
+
super(trigger,tracker)
|
13
|
+
@name = "SFL"
|
14
|
+
@description = "Sentive Files Lookup"
|
15
|
+
@stage = :code
|
16
|
+
@labels << "code"
|
17
|
+
# Glue.debug "initialized SFL"
|
18
|
+
@patterns = read_patterns_file!
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
# Glue.notify "#{@name}"
|
23
|
+
@files = Find.find(@trigger.path)
|
24
|
+
Glue.debug "Found #{@files.count} files"
|
25
|
+
end
|
26
|
+
|
27
|
+
def analyze
|
28
|
+
begin
|
29
|
+
@files.each do |file|
|
30
|
+
@patterns.each do |pattern|
|
31
|
+
case pattern['part']
|
32
|
+
when 'filename'
|
33
|
+
if pattern_matched?(File.basename(file), pattern)
|
34
|
+
report pattern['caption'], pattern['description'], @name + ":" + file, 'unknown', 'TBD'
|
35
|
+
end
|
36
|
+
when 'extension'
|
37
|
+
if pattern_matched?(File.extname(file), pattern)
|
38
|
+
report pattern['caption'], pattern['description'], @name + ":" + file, 'unknown', 'TBD'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
rescue Exception => e
|
44
|
+
Glue.warn e.message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def supported?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def pattern_matched?(txt, pattrn)
|
53
|
+
case pattrn['type']
|
54
|
+
when 'match'
|
55
|
+
return txt == pattrn['pattern']
|
56
|
+
when 'regex'
|
57
|
+
regex = Regexp.new(pattrn['pattern'], Regexp::IGNORECASE)
|
58
|
+
return !regex.match(txt).nil?
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_patterns_file!
|
63
|
+
JSON.parse(File.read("#{File.dirname(__FILE__)}/patterns.json"))
|
64
|
+
rescue JSON::ParserError => e
|
65
|
+
Glue.warn "Cannot parse pattern file: #{e.message}"
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'redcarpet'
|
4
|
+
|
5
|
+
class Glue::Snyk < Glue::BaseTask
|
6
|
+
|
7
|
+
Glue::Tasks.add self
|
8
|
+
include Glue::Util
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger, tracker)
|
12
|
+
@name = "Snyk"
|
13
|
+
@description = "Snyk.io JS dependency checker"
|
14
|
+
@stage = :code
|
15
|
+
@labels << "code" << "javascript"
|
16
|
+
@results = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
exclude_dirs = ['node_modules','bower_components']
|
21
|
+
exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
|
22
|
+
directories_with?('package.json', exclude_dirs).each do |dir|
|
23
|
+
Glue.notify "#{@name} scanning: #{dir}"
|
24
|
+
Dir.chdir(dir) do
|
25
|
+
@results << JSON.parse(runsystem(true, "snyk", "test", "--json"))["vulnerabilities"]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def analyze
|
31
|
+
begin
|
32
|
+
markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML.new(link_attributes: {target: "_blank"}), autolink: true, tables: true
|
33
|
+
|
34
|
+
@results.each do |dir_result|
|
35
|
+
# We build a single finding for each uniq result ID, adding the unique info (upgrade path and files) as a list
|
36
|
+
dir_result.uniq {|r| r['id']}.each do |result|
|
37
|
+
description = "#{result['name']}@#{result['version']} - #{result['title']}"
|
38
|
+
|
39
|
+
# Use Redcarpet to render the Markdown details to something pretty for web display
|
40
|
+
detail = markdown.render(result['description']).gsub('h2>','strong>').gsub('h3>', 'strong>')
|
41
|
+
upgrade_paths = [ "Upgrade Path:\n" ]
|
42
|
+
files = []
|
43
|
+
|
44
|
+
# Pull the list of files and upgrade paths from all results matching this ID
|
45
|
+
# This uses the same form as the retirejs task so it all looks nice together
|
46
|
+
dir_result.select{|r| r['id'] == result['id']}.each do |res|
|
47
|
+
res['upgradePath'].each_with_index do |upgrade, i|
|
48
|
+
upgrade_paths << "#{res['from'][i]} -> #{upgrade}"
|
49
|
+
end
|
50
|
+
files << res['from'].join('->')
|
51
|
+
end
|
52
|
+
|
53
|
+
source = {
|
54
|
+
:scanner => @name,
|
55
|
+
:file => files.join('<br>'),
|
56
|
+
:line => nil,
|
57
|
+
:code => upgrade_paths.uniq.join("\n"),
|
58
|
+
}
|
59
|
+
sev = severity(result['severity'])
|
60
|
+
fprint = fingerprint("#{description}#{detail}#{source}#{sev}")
|
61
|
+
|
62
|
+
report description, detail, source, sev, fprint
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue Exception => e
|
66
|
+
Glue.warn e.message
|
67
|
+
Glue.warn e.backtrace
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def supported?
|
72
|
+
supported = find_executable0('snyk')
|
73
|
+
unless supported
|
74
|
+
Glue.notify "Install Snyk: 'npm install -g snyk'"
|
75
|
+
return false
|
76
|
+
else
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|