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,60 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'nokogiri'
|
4
|
+
|
5
|
+
class Glue::Checkmarx < Glue::BaseTask
|
6
|
+
|
7
|
+
# Glue::Tasks.add self
|
8
|
+
include Glue::Util
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger, tracker)
|
12
|
+
@name = "Checkmarx"
|
13
|
+
@description = "CxSAST"
|
14
|
+
@stage = :code
|
15
|
+
@labels << "code"
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
rootpath = @trigger.path
|
20
|
+
runsystem(true, "runCxConsole.sh", "scan", "-v",
|
21
|
+
"-CxUser", "#{@tracker.options[:checkmarx_user]}",
|
22
|
+
"-CxPassword", "#{@tracker.options[:checkmarx_password]}",
|
23
|
+
"-CxServer", "#{@tracker.options[:checkmarx_server]}",
|
24
|
+
"-LocationType", "folder",
|
25
|
+
"-LocationPath", "#{rootpath}",
|
26
|
+
"-ProjectName", "#{@tracker.options[:checkmarx_project]}",
|
27
|
+
"-ReportXML", "#{rootpath}checkmarx_results.xml",
|
28
|
+
"-Log", "#{@tracker.options[:checkmarx_log]}"
|
29
|
+
)
|
30
|
+
@results = Nokogiri::XML(File.read("#{rootpath}checkmarx_results.xml")).xpath '//Result'
|
31
|
+
end
|
32
|
+
|
33
|
+
def analyze
|
34
|
+
begin
|
35
|
+
@results.each do |result|
|
36
|
+
description = result.parent.attributes['name'].value.gsub('_', ' ')
|
37
|
+
detail = result.attributes['DeepLink'].value
|
38
|
+
source = { :scanner => @name, :file => result.attributes['FileName'].value, :line => result.attributes['Line'].value.to_i, :code => result.at_xpath('Path/PathNode/Snippet/Line/Code').text }
|
39
|
+
sev = severity(result.parent.attributes['Severity'].value)
|
40
|
+
fprint = fingerprint("#{description}#{source}#{sev}")
|
41
|
+
|
42
|
+
report description, detail, source, sev, fprint
|
43
|
+
end
|
44
|
+
rescue Exception => e
|
45
|
+
Glue.warn e.message
|
46
|
+
Glue.warn e.backtrace
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def supported?
|
51
|
+
supported=runsystem(true, "runCxConsole.sh", "--help")
|
52
|
+
if supported =~ /command not found/
|
53
|
+
Glue.notify "Install CxConsolePlugin"
|
54
|
+
return false
|
55
|
+
else
|
56
|
+
return true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Glue::DawnScanner < Glue::BaseTask
|
6
|
+
|
7
|
+
Glue::Tasks.add self
|
8
|
+
include Glue::Util
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger, tracker)
|
12
|
+
@name = "DawnScanner"
|
13
|
+
@description = "DawnScanner ruby analyzer"
|
14
|
+
@stage = :code
|
15
|
+
@labels << "code"
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
Dir.chdir("#{@trigger.path}") do
|
20
|
+
@results_file = Tempfile.new(['dawnresults', 'xml'])
|
21
|
+
runsystem(true, "dawn", "-F", "#{@results_file.path}", "-j", ".")
|
22
|
+
@results = JSON.parse(File.read("#{@results_file.path}"))['vulnerabilities']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def analyze
|
27
|
+
begin
|
28
|
+
@results.each do |result|
|
29
|
+
description = result['name'].gsub('\n',' ')
|
30
|
+
detail = "#{result['message']}\n#{result['remediation']}\n#{result['cve_link']}"
|
31
|
+
source = {:scanner => @name, :file => nil, :line => nil, :code => nil}
|
32
|
+
sev = severity(result['severity'])
|
33
|
+
fprint = fingerprint("#{description}#{detail}#{source}#{sev}")
|
34
|
+
|
35
|
+
report description, detail, source, sev, fprint
|
36
|
+
end
|
37
|
+
rescue Exception => e
|
38
|
+
Glue.warn e.message
|
39
|
+
Glue.warn e.backtrace
|
40
|
+
ensure
|
41
|
+
File.unlink @results_file
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def supported?
|
46
|
+
supported=runsystem(true, "dawn", "--version")
|
47
|
+
if supported =~ /command not found/
|
48
|
+
Glue.notify "Install dawnscanner: 'gem install dawnscanner'"
|
49
|
+
return false
|
50
|
+
else
|
51
|
+
return true
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'json'
|
3
|
+
require 'glue/util'
|
4
|
+
|
5
|
+
class Glue::ESLint < Glue::BaseTask
|
6
|
+
|
7
|
+
Glue::Tasks.add self
|
8
|
+
include Glue::Util
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger,tracker)
|
12
|
+
@name = "ESLint/ScanJS"
|
13
|
+
@description = "Source analysis for JavaScript"
|
14
|
+
@stage = :code
|
15
|
+
@labels << "code" << "javascript"
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
rootpath = @trigger.path
|
20
|
+
currentpath = File.expand_path File.dirname(__FILE__)
|
21
|
+
Glue.debug "ESLint Config Path: #{currentpath}"
|
22
|
+
@result = `eslint -c #{currentpath}/scanjs-eslintrc --no-color --quiet --format json #{rootpath}`
|
23
|
+
end
|
24
|
+
|
25
|
+
def analyze
|
26
|
+
# puts @result
|
27
|
+
begin
|
28
|
+
parsed = JSON.parse(@result)
|
29
|
+
parsed.each do |result|
|
30
|
+
findings = {}
|
31
|
+
prints = []
|
32
|
+
messages = []
|
33
|
+
result['messages'].each do |msg|
|
34
|
+
message = msg['message']
|
35
|
+
findings[message] = {} if findings[message].nil?
|
36
|
+
findings[message][:detail] = msg['ruleId']
|
37
|
+
if messages.include?(message)
|
38
|
+
findings[message][:source] = "#{findings[message][:source]},#{msg['line']}" unless findings[message][:source].include?(",#{msg['line']}")
|
39
|
+
else
|
40
|
+
findings[message][:source] = "#{result['filePath']} Line: #{msg['line']}"
|
41
|
+
messages << message
|
42
|
+
end
|
43
|
+
findings[message][:severity] = severity(msg['severity'].to_s)
|
44
|
+
end
|
45
|
+
findings.each do |key, value|
|
46
|
+
print = fingerprint("#{key}#{value[:detail]}#{value[:source]}#{value[:sev]}")
|
47
|
+
unless prints.include?(print)
|
48
|
+
prints << print
|
49
|
+
report key, value[:detail], value[:source], value[:severity], print
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue Exception => e
|
54
|
+
Glue.warn e.message
|
55
|
+
Glue.warn e.backtrace
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def supported?
|
60
|
+
supported=runsystem(true, "eslint", "-c", "~/.scanjs-eslintrc")
|
61
|
+
if supported =~ /command not found/
|
62
|
+
Glue.notify "Install eslint and the scanjs .eslintrc"
|
63
|
+
return false
|
64
|
+
else
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# https://github.com/jessek/hashdeep/releases/tag/release-4.4
|
2
|
+
|
3
|
+
require 'glue/tasks/base_task'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
class Glue::FIM < Glue::BaseTask
|
7
|
+
|
8
|
+
Glue::Tasks.add self
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger,tracker)
|
12
|
+
@name = "FIM"
|
13
|
+
@description = "File integrity monitor"
|
14
|
+
@stage = :file
|
15
|
+
@result = ''
|
16
|
+
@labels << "filesystem"
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
rootpath = @trigger.path
|
21
|
+
if File.exists?("/area81/tmp/#{rootpath}/filehash")
|
22
|
+
Glue.notify "File Hashes found, comparing to file system"
|
23
|
+
cmd="hashdeep -j99 -r -a -vv -k /area81/tmp/#{rootpath}/filehash #{rootpath}"
|
24
|
+
|
25
|
+
# Ugly stdout parsing
|
26
|
+
r=/(.*): No match/
|
27
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
28
|
+
while line = stdout.gets
|
29
|
+
if line.match r
|
30
|
+
@result << line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
Glue.notify "No existing baseline - generating initial hashes"
|
36
|
+
cmd="mkdir -p /area81/tmp/#{rootpath}; hashdeep -j99 -r #{rootpath} > /area81/tmp/#{rootpath}/filehash"
|
37
|
+
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
38
|
+
while line = stdout.gets
|
39
|
+
puts "."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
@result = ''
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def analyze
|
47
|
+
list = @result.split(/\n/)
|
48
|
+
list.each do |v|
|
49
|
+
# v.slice! installdir
|
50
|
+
Glue.notify v
|
51
|
+
report "File changed.", v, @name, :low
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def supported?
|
56
|
+
# In future, verify tool is available.
|
57
|
+
return true
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'mkmf'
|
6
|
+
|
7
|
+
MakeMakefile::Logging.instance_variable_set(:@logfile, File::NULL)
|
8
|
+
|
9
|
+
class Glue::FindSecurityBugs < Glue::BaseTask
|
10
|
+
|
11
|
+
Glue::Tasks.add self
|
12
|
+
include Glue::Util
|
13
|
+
|
14
|
+
def initialize(trigger, tracker)
|
15
|
+
super(trigger, tracker)
|
16
|
+
@name = "FindSecurityBugs"
|
17
|
+
@description = "FindSecurityBugs plugin for FindBugs"
|
18
|
+
@stage = :code
|
19
|
+
@labels << "code"
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
@results_file = Tempfile.new(['findsecbugs','xml'])
|
24
|
+
|
25
|
+
unless File.exist?("#{@trigger.path}/.git/config")
|
26
|
+
Dir.chdir(@trigger.path) do
|
27
|
+
runsystem(true, "git", "init")
|
28
|
+
runsystem(true, "git", "add", "*")
|
29
|
+
runsystem(true, "git", "commit", "-am", "fake commit for mvn compile")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
directories_with?('pom.xml').each do |dir|
|
34
|
+
Dir.chdir(dir) do
|
35
|
+
runsystem(true, "mvn", "compile", "-fn")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Dir.chdir(@tracker.options[:findsecbugs_path]) do
|
40
|
+
runsystem(true, "/bin/sh", "#{@tracker.options[:findsecbugs_path]}/findsecbugs.sh", "-effort:max", "-quiet", "-xml:withMessages", "-output", "#{@results_file.path}", "#{@trigger.path}")
|
41
|
+
@results = Nokogiri::XML(File.read(@results_file)).xpath '//BugInstance'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def analyze
|
46
|
+
begin
|
47
|
+
@results.each do |result|
|
48
|
+
description = result.xpath('ShortMessage').text
|
49
|
+
bug_type = result.attributes['type'].value
|
50
|
+
detail = "Class: #{result.at_xpath('Method').attributes['classname'].value}, Method: #{result.at_xpath('Method').attributes['name'].value}\n#{result.xpath('LongMessage').text}\nhttps://find-sec-bugs.github.io/bugs.htm##{bug_type}"
|
51
|
+
|
52
|
+
file = result.at_xpath('SourceLine').attributes['sourcepath'].value
|
53
|
+
trigger_path = Pathname.new(@trigger.path)
|
54
|
+
real_path = nil
|
55
|
+
trigger_path.find {|path| real_path = path if path.fnmatch "*/#{file}"}
|
56
|
+
file = real_path.relative_path_from(trigger_path).to_s unless real_path.nil?
|
57
|
+
|
58
|
+
line = result.at_xpath('SourceLine[@primary="true"]').attributes['start'].value
|
59
|
+
code = "#{result.at_xpath('String').attributes['value'].value}"
|
60
|
+
source = {:scanner => @name, :file => file, :line => line, :code => code}
|
61
|
+
sev = result.attributes['priority'].value
|
62
|
+
fprint = fingerprint("#{description}#{detail}#{source}")
|
63
|
+
|
64
|
+
report description, detail, source, sev, fprint
|
65
|
+
end
|
66
|
+
rescue Exception => e
|
67
|
+
Glue.warn e.message
|
68
|
+
Glue.warn e.backtrace
|
69
|
+
ensure
|
70
|
+
File.unlink @results_file
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def supported?
|
75
|
+
unless find_executable0('mvn') and File.exist?("#{@trigger.path}/pom.xml")
|
76
|
+
Glue.notify "FindSecurityBugs support requires maven and pom.xml"
|
77
|
+
Glue.notify "Please install maven somewhere in your PATH and include a valid pom.xml in the project root"
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
|
81
|
+
unless @tracker.options.has_key?(:findsecbugs_path) and File.exist?("#{@tracker.options[:findsecbugs_path]}/findsecbugs.sh")
|
82
|
+
Glue.notify "#{@tracker.options[:findsecbugs_path]}"
|
83
|
+
Glue.notify "Download and unpack the latest findsecbugs-cli release: https://github.com/find-sec-bugs/find-sec-bugs/releases"
|
84
|
+
return false
|
85
|
+
else
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
require 'find'
|
4
|
+
require 'pry'
|
5
|
+
|
6
|
+
class Glue::Npm < Glue::BaseTask
|
7
|
+
|
8
|
+
Glue::Tasks.add self
|
9
|
+
include Glue::Util
|
10
|
+
|
11
|
+
def initialize(trigger, tracker)
|
12
|
+
super(trigger, tracker)
|
13
|
+
@name = "NPM"
|
14
|
+
@description = "Node Package Manager"
|
15
|
+
@stage = :file
|
16
|
+
@labels << "file" << "javascript"
|
17
|
+
@results = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
exclude_dirs = ['node_modules','bower_components']
|
22
|
+
exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
|
23
|
+
directories_with?('package.json', exclude_dirs).each do |dir|
|
24
|
+
Glue.notify "#{@name} scanning: #{dir}"
|
25
|
+
Dir.chdir(dir) do
|
26
|
+
if @tracker.options.has_key?(:npm_registry)
|
27
|
+
registry = "--registry #{@tracker.options[:npm_registry]}"
|
28
|
+
else
|
29
|
+
registry = nil
|
30
|
+
end
|
31
|
+
@command = "npm install -q --ignore-scripts #{registry}"
|
32
|
+
@results << runsystem(true, @command)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def analyze
|
38
|
+
begin
|
39
|
+
if @results.include? false
|
40
|
+
Glue.warn 'Error installing javascript dependencies with #{@command}'
|
41
|
+
end
|
42
|
+
rescue Exception => e
|
43
|
+
Glue.warn e.message
|
44
|
+
Glue.warn e.backtrace
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def supported?
|
49
|
+
supported = find_executable0('npm')
|
50
|
+
unless supported
|
51
|
+
Glue.notify "Install npm: https://nodejs.org/en/download/"
|
52
|
+
return false
|
53
|
+
else
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'glue/util'
|
3
|
+
|
4
|
+
class Glue::NodeSecurityProject < Glue::BaseTask
|
5
|
+
|
6
|
+
Glue::Tasks.add self
|
7
|
+
include Glue::Util
|
8
|
+
|
9
|
+
def initialize(trigger, tracker)
|
10
|
+
super(trigger, tracker)
|
11
|
+
@name = "NodeSecurityProject"
|
12
|
+
@description = "Node Security Project"
|
13
|
+
@stage = :code
|
14
|
+
@labels << "code"
|
15
|
+
@results = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
exclude_dirs = ['node_modules','bower_components']
|
20
|
+
exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
|
21
|
+
directories_with?('package.json', exclude_dirs).each do |dir|
|
22
|
+
Glue.notify "#{@name} scanning: #{dir}"
|
23
|
+
Dir.chdir(dir) do
|
24
|
+
res = runsystem(true, "nsp", "check", "--output", "json")
|
25
|
+
@results << JSON.parse(res)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def analyze
|
31
|
+
begin
|
32
|
+
@results.each do |dir_result|
|
33
|
+
# This block iterates through each package name found and selects the unique nsp advisories
|
34
|
+
# regardless of version, and builds a Glue finding hash for each unique package/advisory combo.
|
35
|
+
dir_result.uniq {|finding| finding['module']}.each do |package|
|
36
|
+
dir_result.select {|f| f['module'] == package['module']}.uniq {|m| m['advisory']}.each do |unique_finding|
|
37
|
+
description = "#{unique_finding['module']} - #{unique_finding['title']}"
|
38
|
+
detail = "Upgrade to versions: #{unique_finding['patched_versions']}\n#{unique_finding['advisory']}"
|
39
|
+
source = {
|
40
|
+
:scanner => 'NodeSecurityProject',
|
41
|
+
:file => "#{unique_finding['module']} - #{unique_finding['vulnerable_versions']}",
|
42
|
+
:line => nil,
|
43
|
+
:code => nil
|
44
|
+
}
|
45
|
+
report description, detail, source, 'medium', fingerprint("#{description}#{detail}#{source}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
rescue Exception => e
|
50
|
+
Glue.warn e.message
|
51
|
+
Glue.warn e.backtrace
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def supported?
|
56
|
+
supported=runsystem(true, "nsp", "--version")
|
57
|
+
if supported =~ /command not found/
|
58
|
+
Glue.notify "Install nodesecurity: 'npm install -g nsp'"
|
59
|
+
return false
|
60
|
+
else
|
61
|
+
return true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|