sastbox_sdk 1.0.0
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/README.md +0 -0
- data/lib/sastbox-sdk.rb +26 -0
- data/lib/sastbox-sdk/codebase.rb +9 -0
- data/lib/sastbox-sdk/cwe_constants.rb +87 -0
- data/lib/sastbox-sdk/cwe_detector.rb +202 -0
- data/lib/sastbox-sdk/opt_parser.rb +45 -0
- data/lib/sastbox-sdk/printer.rb +86 -0
- data/lib/sastbox-sdk/reporter_sarif.rb +112 -0
- data/lib/sastbox-sdk/runner.rb +55 -0
- data/lib/sastbox-sdk/scanner.rb +152 -0
- data/lib/sastbox-sdk/severity_calculator.rb +82 -0
- data/lib/sastbox-sdk/snippet.rb +107 -0
- data/spec/samples/low.php +21 -0
- data/spec/samples/sarif-2.1.0-rtm.5.json +3370 -0
- data/spec/sastbox-sdk/codebase_spec.rb +7 -0
- data/spec/sastbox-sdk/cwe_constants_spec.rb +7 -0
- data/spec/sastbox-sdk/cwe_detector_spec.rb +216 -0
- data/spec/sastbox-sdk/opt_parser_spec.rb +47 -0
- data/spec/sastbox-sdk/printer_spec.rb +59 -0
- data/spec/sastbox-sdk/reporter_sarif_spec.rb +57 -0
- data/spec/sastbox-sdk/runner_spec.rb +92 -0
- data/spec/sastbox-sdk/scanner_spec.rb +238 -0
- data/spec/sastbox-sdk/severity_calculator_spec.rb +126 -0
- data/spec/sastbox-sdk/snippet_spec.rb +175 -0
- data/spec/sastbox-sdk_spec.rb +8 -0
- data/spec/spec_helper.rb +109 -0
- metadata +96 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/sastbox-sdk'
|
3
|
+
|
4
|
+
|
5
|
+
RSpec.describe 'Cwe_detector' do
|
6
|
+
let(:scanner) do
|
7
|
+
SastBox::Scanner.new(
|
8
|
+
name: 'test_name',
|
9
|
+
name_alias: 'test_alias',
|
10
|
+
description: 'test_desc',
|
11
|
+
support: ['test_support'],
|
12
|
+
version: '0.1',
|
13
|
+
tool: 'test_tool'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'cwe_found?' do
|
18
|
+
context 'when issue has pattern' do
|
19
|
+
let(:issue) do
|
20
|
+
{
|
21
|
+
title: 'cmd exec',
|
22
|
+
description: 'cmd exec'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:cwe_id) { 123 }
|
27
|
+
|
28
|
+
before do
|
29
|
+
scanner.alternative_titles(issue)
|
30
|
+
@status = scanner.cwe_found?(issue, ['cmd exec'], cwe_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
it { expect( issue[:cwe_id]).to eq cwe_id }
|
34
|
+
it { expect( @status).to be true }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when issue does not have pattern' do
|
38
|
+
let(:issue) do
|
39
|
+
{
|
40
|
+
title: 'cmd exec',
|
41
|
+
description: 'cmd exec'
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:cwe_id) { 123 }
|
46
|
+
|
47
|
+
before do
|
48
|
+
scanner.alternative_titles(issue)
|
49
|
+
end
|
50
|
+
|
51
|
+
it {expect( scanner.cwe_found?(issue, ['xxx'], cwe_id)).to be false}
|
52
|
+
it {expect( issue[:cwe_id]).to eq nil}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'cwe_start_heuristics?' do
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'guess_cwe' do
|
60
|
+
context 'when cwe_id is already assigned to valid CWE' do
|
61
|
+
let(:issue) do
|
62
|
+
{
|
63
|
+
title: 'sql inj',
|
64
|
+
description: 'sql inj',
|
65
|
+
cwe_id: 123
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
before do
|
70
|
+
scanner.guess_cwe(issue)
|
71
|
+
end
|
72
|
+
|
73
|
+
it {expect(issue[:cwe_id]).to be 123}
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when cwe_id is already assigned to invalid CWE' do
|
77
|
+
let(:issue) do
|
78
|
+
{
|
79
|
+
title: 'sql inj',
|
80
|
+
description: 'sql inj',
|
81
|
+
cwe_id: -1
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
before do
|
86
|
+
scanner.guess_cwe(issue)
|
87
|
+
end
|
88
|
+
|
89
|
+
it {expect(issue[:cwe_id]).to eq SastBox::Cwe::SQL_INJECTION}
|
90
|
+
end
|
91
|
+
|
92
|
+
############################################################################
|
93
|
+
context 'when has sql injection pattern and cwe_id not set' do
|
94
|
+
let(:issue) { { title: 'sql inj', description: 'sql inj' } }
|
95
|
+
before { scanner.guess_cwe(issue) }
|
96
|
+
it 'sql injection' do
|
97
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::SQL_INJECTION
|
98
|
+
end
|
99
|
+
end
|
100
|
+
############################################################################
|
101
|
+
context 'when has xss pattern and cwe_id not set' do
|
102
|
+
let(:issue) { { title: 'xss', description: 'xss' } }
|
103
|
+
before { scanner.guess_cwe(issue) }
|
104
|
+
it 'xss' do
|
105
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::XSS
|
106
|
+
end
|
107
|
+
end
|
108
|
+
############################################################################
|
109
|
+
context 'when has cmd injection pattern and cwe_id not set' do
|
110
|
+
let(:issue) { { title: 'cmd injection', description: 'cmd injection' } }
|
111
|
+
before { scanner.guess_cwe(issue) }
|
112
|
+
it 'cmd injection' do
|
113
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::OS_COMMAND_INJECTION
|
114
|
+
end
|
115
|
+
end
|
116
|
+
############################################################################
|
117
|
+
context 'when has code injection pattern and cwe_id not set' do
|
118
|
+
let(:issue) { { title: 'code injection', description: 'code injection' } }
|
119
|
+
before { scanner.guess_cwe(issue) }
|
120
|
+
it 'code injection' do
|
121
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::CODE_INJECTION
|
122
|
+
end
|
123
|
+
end
|
124
|
+
############################################################################
|
125
|
+
context 'when has session fixation pattern and cwe_id not set' do
|
126
|
+
let(:issue) { { title: 'session fixation', description: 'session fixation' } }
|
127
|
+
before { scanner.guess_cwe(issue) }
|
128
|
+
it 'session fixation' do
|
129
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::SESSION_FIXATION
|
130
|
+
end
|
131
|
+
end
|
132
|
+
############################################################################
|
133
|
+
context 'when has csrf pattern and cwe_id not set' do
|
134
|
+
let(:issue) { { title: 'csrf', description: 'csrf' } }
|
135
|
+
before { scanner.guess_cwe(issue) }
|
136
|
+
it 'csrf' do
|
137
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::CSRF
|
138
|
+
end
|
139
|
+
end
|
140
|
+
############################################################################
|
141
|
+
context 'when has deserialization pattern and cwe_id not set' do
|
142
|
+
let(:issue) { { title: 'deserialization', description: 'deserialization' } }
|
143
|
+
before { scanner.guess_cwe(issue) }
|
144
|
+
it 'deserialization' do
|
145
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::DESERIALIZATION_OF_UNTRUSTED_DATA
|
146
|
+
end
|
147
|
+
end
|
148
|
+
############################################################################
|
149
|
+
context 'when has path traversal pattern and cwe_id not set' do
|
150
|
+
let(:issue) { { title: 'path traversal', description: 'path traversal' } }
|
151
|
+
before { scanner.guess_cwe(issue) }
|
152
|
+
it 'path traversal' do
|
153
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::PATH_TRAVERSAL
|
154
|
+
end
|
155
|
+
end
|
156
|
+
############################################################################
|
157
|
+
context 'when has hardcoded password pattern and cwe_id not set' do
|
158
|
+
let(:issue) { { title: 'hard-coded', description: 'hard-coded' } }
|
159
|
+
before { scanner.guess_cwe(issue) }
|
160
|
+
it 'hard-coded' do
|
161
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::HARD_CODED_PASSWORD
|
162
|
+
end
|
163
|
+
end
|
164
|
+
############################################################################
|
165
|
+
context 'when has null pointer deref pattern and cwe_id not set' do
|
166
|
+
let(:issue) { { title: 'null pointer deref', description: 'null pointer deref' } }
|
167
|
+
before { scanner.guess_cwe(issue) }
|
168
|
+
it 'null pointer deref' do
|
169
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::NULL_POINTER_DEREFERENCE
|
170
|
+
end
|
171
|
+
end
|
172
|
+
############################################################################
|
173
|
+
context 'when has broken crypto pattern and cwe_id not set' do
|
174
|
+
let(:issue) { { title: 'broken crypto', description: 'broken crypto' } }
|
175
|
+
before { scanner.guess_cwe(issue) }
|
176
|
+
it 'broken crypto' do
|
177
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::BROKEN_CRYPTO
|
178
|
+
end
|
179
|
+
end
|
180
|
+
############################################################################
|
181
|
+
context 'when has improper authorization pattern and cwe_id not set' do
|
182
|
+
let(:issue) { { title: 'improper authorization', description: 'improper authorization' } }
|
183
|
+
before { scanner.guess_cwe(issue) }
|
184
|
+
it 'improper authorization' do
|
185
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::IMPROPER_AUTHORIZATION
|
186
|
+
end
|
187
|
+
end
|
188
|
+
############################################################################
|
189
|
+
context 'when has improper authentication pattern and cwe_id not set' do
|
190
|
+
let(:issue) { { title: 'improper authentication', description: 'improper authentication' } }
|
191
|
+
before { scanner.guess_cwe(issue) }
|
192
|
+
it 'improper authentication' do
|
193
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::IMPROPER_AUTHENTICATION
|
194
|
+
end
|
195
|
+
end
|
196
|
+
############################################################################
|
197
|
+
context 'when has improper input validation pattern and cwe_id not set' do
|
198
|
+
let(:issue) { { title: 'input validation', description: 'input validation' } }
|
199
|
+
before { scanner.guess_cwe(issue) }
|
200
|
+
it 'input validation' do
|
201
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::IMPROPER_INPUT_VALIDATION
|
202
|
+
end
|
203
|
+
end
|
204
|
+
############################################################################
|
205
|
+
context 'when has unrestricted file upload pattern and cwe_id not set' do
|
206
|
+
let(:issue) { { title: 'unrestricted file upload', description: 'unrestricted file upload' } }
|
207
|
+
before { scanner.guess_cwe(issue) }
|
208
|
+
it 'unrestricted file upload' do
|
209
|
+
expect(issue[:cwe_id]).to eq SastBox::Cwe::UNRESTRICTED_UPLOAD_OF_FILE_WITH_DANGEROUS_TYPE
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/sastbox-sdk'
|
3
|
+
|
4
|
+
|
5
|
+
RSpec.describe 'Opt_parser' do
|
6
|
+
let(:scanner) do
|
7
|
+
SastBox::Scanner.new(
|
8
|
+
name: 'test_name',
|
9
|
+
name_alias: 'test_alias',
|
10
|
+
description: 'test_desc',
|
11
|
+
support: ['test_support'],
|
12
|
+
version: '0.1',
|
13
|
+
tool: 'test_tool'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'parse_opts' do
|
18
|
+
context 'should parse all the provided options' do
|
19
|
+
subject { scanner.parse_opts(['-c', 'codebase', '-o', 'outputfile', '-v', '-t', '2', '-n']) }
|
20
|
+
|
21
|
+
it 'codebase - is expected to eq "codebase"' do
|
22
|
+
expect(subject.codebase).to eq 'codebase'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "output - is expected to eq outputfile" do
|
26
|
+
expect(subject.output).to eq 'outputfile'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "verbose - is expected to equal true" do
|
30
|
+
expect(subject.verbose).to be true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "timeout - is expected to equal 120" do
|
34
|
+
expect(subject.timeout).to be 120
|
35
|
+
end
|
36
|
+
|
37
|
+
it "info - is expected to eq false" do
|
38
|
+
expect(subject.info).to be false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "color - is expected to equal true" do
|
42
|
+
expect(subject.color).to be true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/sastbox-sdk'
|
3
|
+
|
4
|
+
|
5
|
+
RSpec.describe 'Printer' do
|
6
|
+
let(:scanner) do
|
7
|
+
SastBox::Scanner.new(
|
8
|
+
name: 'test_name',
|
9
|
+
name_alias: 'test_alias',
|
10
|
+
description: 'test_desc',
|
11
|
+
support: ['test_support'],
|
12
|
+
version: '0.1',
|
13
|
+
tool: 'test_tool'
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'print_title' do
|
18
|
+
context 'should print with title format' do
|
19
|
+
it {expect { scanner.print_title('abc') }.to output("[*] abc\n").to_stdout }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'print_normal' do
|
24
|
+
context 'should print with normal format' do
|
25
|
+
it {expect { scanner.print_normal('abc') }.to output("abc\n").to_stdout }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'print_success' do
|
30
|
+
context 'should print with success format' do
|
31
|
+
it {expect { scanner.print_success('abc') }.to output("[SUCCESS] abc\n").to_stdout }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'print_error' do
|
36
|
+
context 'should print with error format' do
|
37
|
+
it {expect { scanner.print_error('abc') }.to output("[ ERROR ] abc\n").to_stdout }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'print_with_label' do
|
42
|
+
context 'should print with label format' do
|
43
|
+
it {expect { scanner.print_with_label('abc', 'def') }.to output("[def] abc\n").to_stdout }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'print_debug' do
|
48
|
+
context 'should print with debug format' do
|
49
|
+
it {expect { scanner.print_debug('abc') }.to output(/DEBUG\|.*\| abc/).to_stdout }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe 'print_warning' do
|
54
|
+
context 'should print with warning format' do
|
55
|
+
it {expect { scanner.print_warning('abc') }.to output("[WARNING] abc\n").to_stdout }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/sastbox-sdk'
|
3
|
+
|
4
|
+
require 'json-schema'
|
5
|
+
|
6
|
+
|
7
|
+
RSpec.describe 'Reporter_sarif' do
|
8
|
+
let(:scanner) do
|
9
|
+
SastBox::Scanner.new(
|
10
|
+
name: 'test_name',
|
11
|
+
name_alias: 'test_alias',
|
12
|
+
description: 'test_desc',
|
13
|
+
support: ['test_support'],
|
14
|
+
version: '0.1',
|
15
|
+
tool: 'test_tool'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:filename) do
|
20
|
+
File.absolute_path(File.join(File.dirname(__FILE__), '..', 'samples/low.php'))
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:schema) do
|
24
|
+
File.absolute_path(File.join(File.dirname(__FILE__), '..', 'samples/sarif-2.1.0-rtm.5.json'))
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:snippet) do
|
28
|
+
scanner.snippet_read(filename, 10)
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
scanner.parse_opts(['-c', 'xxx'])
|
33
|
+
|
34
|
+
scanner.add_issue(
|
35
|
+
title: 'title',
|
36
|
+
description: 'description',
|
37
|
+
references: [],
|
38
|
+
severity: :info,
|
39
|
+
filename: filename,
|
40
|
+
line: 10,
|
41
|
+
cwe_id: -1,
|
42
|
+
tags: [],
|
43
|
+
snippet: snippet
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'generate_sarif_report' do
|
48
|
+
context 'should generate valid SARIF' do
|
49
|
+
subject {scanner.generate_sarif_report() }
|
50
|
+
|
51
|
+
it {expect(subject).to be_a(String)}
|
52
|
+
|
53
|
+
it {expect(JSON::Validator.validate(schema, subject)).to be true}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/sastbox-sdk'
|
3
|
+
|
4
|
+
RSpec.describe 'Runner' do
|
5
|
+
let(:scanner) do
|
6
|
+
SastBox::Scanner.new(
|
7
|
+
name: 'test_name',
|
8
|
+
name_alias: 'test_alias',
|
9
|
+
description: 'test_desc',
|
10
|
+
support: ['test_support'],
|
11
|
+
version: '0.1',
|
12
|
+
tool: 'test_tool'
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:scanner_timeout_60_sec) do
|
17
|
+
SastBox::Scanner.new(
|
18
|
+
name: 'test_name',
|
19
|
+
name_alias: 'test_alias',
|
20
|
+
description: 'test_desc',
|
21
|
+
support: ['test_support'],
|
22
|
+
version: '0.1',
|
23
|
+
tool: 'test_tool'
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
scanner.parse_opts([])
|
29
|
+
scanner_timeout_60_sec.parse_opts(['-t', '1']) # 60 secs
|
30
|
+
|
31
|
+
Thread.report_on_exception = false
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#command?' do
|
35
|
+
context 'should return true if binary exists' do
|
36
|
+
it { expect(scanner.command?('ls')).to eq true }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'should return false if binary not exists' do
|
40
|
+
it { expect(scanner.command?('xxx')).to eq false }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#run_cmd' do
|
45
|
+
context 'should run a valid command and return results' do
|
46
|
+
subject { scanner.run_cmd(['uname']) }
|
47
|
+
|
48
|
+
it { expect(subject).to be_an(Array) }
|
49
|
+
it { expect(subject.length).to be(2) }
|
50
|
+
it { expect(subject[0]).to eq "Linux\n" }
|
51
|
+
it { expect(subject[1]).to eq(nil).or be_an(Process::Status) }
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'should exit with an invalid command' do
|
55
|
+
it do
|
56
|
+
expect { scanner.run_cmd(['xxx'])}.to raise_error(SystemExit) do |error|
|
57
|
+
expect( error.status ).to eq(1)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
it do
|
61
|
+
expect { scanner.run_cmd(['xxx'])}.to raise_error(SystemExit) do |error|
|
62
|
+
expect( error.message ).to eq('exit')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#run_cmd_with_timeout' do
|
69
|
+
context 'should not fail when not exceeded timeout' do
|
70
|
+
subject { scanner_timeout_60_sec.run_cmd_with_timeout(['uname']) }
|
71
|
+
|
72
|
+
it { expect(subject).to be_an(Array) }
|
73
|
+
it { expect(subject.length).to be(2) }
|
74
|
+
it { expect(subject[0]).to eq "Linux\n" }
|
75
|
+
it { expect(subject[1]).to eq(nil).or be_an(Process::Status) }
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'should fail when exceeded timeout' do
|
79
|
+
it do
|
80
|
+
expect { scanner_timeout_60_sec.run_cmd_with_timeout(['sleep', '70'])}.to raise_error(SystemExit) do |error|
|
81
|
+
expect( error.status ).to eq(1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
it do
|
85
|
+
expect { scanner_timeout_60_sec.run_cmd_with_timeout(['sleep', '70'])}.to raise_error(SystemExit) do |error|
|
86
|
+
expect( error.message ).to eq('exit')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|