gemsurance 0.0.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gemsurance'
4
+ require 'optparse'
5
+
6
+ options = {}
7
+
8
+ opts = OptionParser.new do |opts|
9
+ opts.banner = "Usage: gemsurance [options]"
10
+
11
+ opts.separator ""
12
+ opts.separator "Options:"
13
+
14
+ opts.on("--pre", "Consider pre-release gem versions") do |lib|
15
+ options[:pre] = true
16
+ end
17
+
18
+ opts.on_tail("-h", "--help", "Show this help") do
19
+ puts opts
20
+ exit
21
+ end
22
+
23
+ opts.on_tail("--version", "Show version") do
24
+ puts "Gemsurance version #{Gemsurance::VERSION}"
25
+ exit
26
+ end
27
+ end
28
+
29
+ opts.parse!(ARGV)
30
+
31
+ Gemsurance::Runner.new(options).run
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'git'
3
+ require 'erb'
4
+
5
+ require 'gemsurance/gem_info_retriever'
6
+ require 'gemsurance/html_formatter'
7
+ require 'gemsurance/runner'
8
+ require 'gemsurance/version'
9
+ require 'gemsurance/vulnerability'
@@ -0,0 +1,77 @@
1
+ module Gemsurance
2
+ class GemInfoRetriever
3
+ class GemInfo
4
+ STATUS_OUTDATED = 'outdated'
5
+ STATUS_CURRENT = 'current'
6
+ STATUS_VULNERABLE = 'vulnerable'
7
+
8
+ attr_reader :name, :current_version, :newest_version, :vulnerabilities
9
+
10
+ def initialize(name, current_version, newest_version, status = STATUS_CURRENT)
11
+ @name = name
12
+ @current_version = current_version
13
+ @newest_version = newest_version
14
+ @status = status
15
+ @vulnerabilities = []
16
+ end
17
+
18
+ def add_vulnerability!(vulnerability)
19
+ @status = STATUS_VULNERABLE
20
+ @vulnerabilities << vulnerability
21
+ end
22
+
23
+ def outdated?
24
+ @status == STATUS_OUTDATED
25
+ end
26
+
27
+ def current?
28
+ @status == STATUS_CURRENT
29
+ end
30
+
31
+ def vulnerable?
32
+ @status == STATUS_VULNERABLE
33
+ end
34
+
35
+ def ==(other)
36
+ @name == other.name &&
37
+ @current_version == other.current_version &&
38
+ @newest_version == other.newest_version &&
39
+ @status == other.instance_variable_get(:@status) &&
40
+ @vulnerabilities == other.vulnerabilities
41
+ end
42
+ end
43
+
44
+ def initialize(specs, bundle_definition)
45
+ @specs = specs
46
+ @bundle_definition = bundle_definition
47
+ end
48
+
49
+ def retrieve(options = {})
50
+ gem_infos = []
51
+
52
+ @specs.each do |current_spec|
53
+ active_spec = @bundle_definition.index[current_spec.name].sort_by { |b| b.version }
54
+
55
+ if !current_spec.version.prerelease? && !options[:pre] && active_spec.size > 1
56
+ active_spec = active_spec.delete_if { |b| b.respond_to?(:version) && b.version.prerelease? }
57
+ end
58
+
59
+ active_spec = active_spec.last
60
+ next if active_spec.nil?
61
+
62
+ gem_outdated = Gem::Version.new(active_spec.version) > Gem::Version.new(current_spec.version)
63
+ git_outdated = current_spec.git_version != active_spec.git_version
64
+
65
+ # TODO: handle git versions
66
+ # spec_version = "#{active_spec.version}#{active_spec.git_version}"
67
+ # current_version = "#{current_spec.version}#{current_spec.git_version}"
68
+ if gem_outdated || git_outdated
69
+ gem_infos << GemInfo.new(active_spec.name, current_spec.version, active_spec.version, GemInfo::STATUS_OUTDATED)
70
+ else
71
+ gem_infos << GemInfo.new(active_spec.name, current_spec.version, current_spec.version)
72
+ end
73
+ end
74
+ gem_infos
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,11 @@
1
+ module Gemsurance
2
+ class HtmlFormatter
3
+ def initialize(gem_infos)
4
+ @gem_infos = gem_infos
5
+ end
6
+
7
+ def format
8
+ ERB.new(File.read(File.join(File.dirname(File.expand_path(__FILE__)), 'templates/output.html.erb'))).result(binding)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ module Gemsurance
2
+ class Runner
3
+ def initialize(options = {})
4
+ @formatter = options.delete(:formatter) || :html
5
+ @output_file = options.delete(:output_file) || 'gemsurance_report.html'
6
+ @options = options
7
+ end
8
+
9
+ def run
10
+ bundled_gem_infos = retrieve_bundled_gem_infos
11
+
12
+ retrieve_vulnerability_data
13
+
14
+ add_vulnerability_data(bundled_gem_infos)
15
+
16
+ generate_report(bundled_gem_infos)
17
+
18
+ exit 1 if bundled_gem_infos.any? { |info| info.vulnerable? }
19
+ end
20
+
21
+ private
22
+
23
+ def retrieve_bundled_gem_infos
24
+ puts "Retrieving gem version information..."
25
+
26
+ current_specs = Bundler.load.specs
27
+ definition = Bundler.definition(true)
28
+ definition.resolve_remotely!
29
+
30
+ GemInfoRetriever.new(current_specs, definition).retrieve(:pre => @options[:pre])
31
+ end
32
+
33
+ def retrieve_vulnerability_data
34
+ puts "Retrieving latest vulnerability data..."
35
+ if File.exists?('./tmp/vulnerabilities')
36
+ g = Git.open('./tmp/vulnerabilities')
37
+ g.pull
38
+ else
39
+ Git.clone('https://github.com/rubysec/ruby-advisory-db', './tmp/vulnerabilities')
40
+ end
41
+ end
42
+
43
+ def add_vulnerability_data(gem_infos)
44
+ puts "Reading vulnerability data..."
45
+ gem_infos.each do |gem_info|
46
+ vulnerability_directory = "./tmp/vulnerabilities/gems/#{gem_info.name}"
47
+ if File.exists?(vulnerability_directory)
48
+ Dir.foreach(vulnerability_directory) do |yaml_file|
49
+ next if yaml_file =~ /\A\./
50
+ vulnerability = Vulnerability.new(File.read(File.join(vulnerability_directory, yaml_file)))
51
+ # are we impacted? if so, add details to gem_data
52
+ unless vulnerability.patched_versions.any? { |version| Gem::Requirement.new(version).satisfied_by?(gem_info.current_version) }
53
+ gem_info.add_vulnerability!(vulnerability)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ def generate_report(gem_infos)
61
+ puts "Generating report..."
62
+ output_data = Gemsurance.const_get(:"#{@formatter.to_s.capitalize}Formatter").new(gem_infos).format
63
+
64
+ File.open(@output_file, "w+") do |file|
65
+ file.puts output_data
66
+ end
67
+ puts "Generated report #{@output_file}."
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,803 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf8">
5
+ <title>Gemsurance Report</title>
6
+ <style>
7
+ /*!
8
+ * Bootstrap v3.0.0
9
+ *
10
+ * Copyright 2013 Twitter, Inc
11
+ * Licensed under the Apache License v2.0
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+ *
14
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
15
+ */
16
+
17
+ /*! normalize.css v2.1.0 | MIT License | git.io/normalize */
18
+ article,
19
+ aside,
20
+ details,
21
+ figcaption,
22
+ figure,
23
+ footer,
24
+ header,
25
+ hgroup,
26
+ main,
27
+ nav,
28
+ section,
29
+ summary {
30
+ display: block;
31
+ }
32
+ audio,
33
+ canvas,
34
+ video {
35
+ display: inline-block;
36
+ }
37
+ audio:not([controls]) {
38
+ display: none;
39
+ height: 0;
40
+ }
41
+ [hidden] {
42
+ display: none;
43
+ }
44
+ html {
45
+ font-family: sans-serif;
46
+ -webkit-text-size-adjust: 100%;
47
+ -ms-text-size-adjust: 100%;
48
+ }
49
+ body {
50
+ margin: 0;
51
+ }
52
+ a:focus {
53
+ outline: thin dotted;
54
+ }
55
+ a:active,
56
+ a:hover {
57
+ outline: 0;
58
+ }
59
+ h1 {
60
+ font-size: 2em;
61
+ margin: 0.67em 0;
62
+ }
63
+ abbr[title] {
64
+ border-bottom: 1px dotted;
65
+ }
66
+ b,
67
+ strong {
68
+ font-weight: bold;
69
+ }
70
+ dfn {
71
+ font-style: italic;
72
+ }
73
+ hr {
74
+ -moz-box-sizing: content-box;
75
+ box-sizing: content-box;
76
+ height: 0;
77
+ }
78
+ mark {
79
+ background: #ff0;
80
+ color: #000;
81
+ }
82
+ code,
83
+ kbd,
84
+ pre,
85
+ samp {
86
+ font-family: monospace, serif;
87
+ font-size: 1em;
88
+ }
89
+ pre {
90
+ white-space: pre-wrap;
91
+ }
92
+ q {
93
+ quotes: "\201C" "\201D" "\2018" "\2019";
94
+ }
95
+ small {
96
+ font-size: 80%;
97
+ }
98
+ sub,
99
+ sup {
100
+ font-size: 75%;
101
+ line-height: 0;
102
+ position: relative;
103
+ vertical-align: baseline;
104
+ }
105
+ sup {
106
+ top: -0.5em;
107
+ }
108
+ sub {
109
+ bottom: -0.25em;
110
+ }
111
+ img {
112
+ border: 0;
113
+ }
114
+ svg:not(:root) {
115
+ overflow: hidden;
116
+ }
117
+ figure {
118
+ margin: 0;
119
+ }
120
+ fieldset {
121
+ border: 1px solid #c0c0c0;
122
+ margin: 0 2px;
123
+ padding: 0.35em 0.625em 0.75em;
124
+ }
125
+ legend {
126
+ border: 0;
127
+ padding: 0;
128
+ }
129
+ button,
130
+ input,
131
+ select,
132
+ textarea {
133
+ font-family: inherit;
134
+ font-size: 100%;
135
+ margin: 0;
136
+ }
137
+ button,
138
+ input {
139
+ line-height: normal;
140
+ }
141
+ button,
142
+ select {
143
+ text-transform: none;
144
+ }
145
+ button,
146
+ html input[type="button"],
147
+ input[type="reset"],
148
+ input[type="submit"] {
149
+ -webkit-appearance: button;
150
+ cursor: pointer;
151
+ }
152
+ button[disabled],
153
+ html input[disabled] {
154
+ cursor: default;
155
+ }
156
+ input[type="checkbox"],
157
+ input[type="radio"] {
158
+ box-sizing: border-box;
159
+ padding: 0;
160
+ }
161
+ input[type="search"] {
162
+ -webkit-appearance: textfield;
163
+ -moz-box-sizing: content-box;
164
+ -webkit-box-sizing: content-box;
165
+ box-sizing: content-box;
166
+ }
167
+ input[type="search"]::-webkit-search-cancel-button,
168
+ input[type="search"]::-webkit-search-decoration {
169
+ -webkit-appearance: none;
170
+ }
171
+ button::-moz-focus-inner,
172
+ input::-moz-focus-inner {
173
+ border: 0;
174
+ padding: 0;
175
+ }
176
+ textarea {
177
+ overflow: auto;
178
+ vertical-align: top;
179
+ }
180
+ table {
181
+ border-collapse: collapse;
182
+ border-spacing: 0;
183
+ }
184
+ *,
185
+ *:before,
186
+ *:after {
187
+ -webkit-box-sizing: border-box;
188
+ -moz-box-sizing: border-box;
189
+ box-sizing: border-box;
190
+ }
191
+ html {
192
+ font-size: 62.5%;
193
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
194
+ }
195
+ body {
196
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
197
+ font-size: 14px;
198
+ line-height: 1.428571429;
199
+ color: #333333;
200
+ background-color: #ffffff;
201
+ }
202
+ input,
203
+ button,
204
+ select,
205
+ textarea {
206
+ font-family: inherit;
207
+ font-size: inherit;
208
+ line-height: inherit;
209
+ }
210
+ button,
211
+ input,
212
+ select[multiple],
213
+ textarea {
214
+ background-image: none;
215
+ }
216
+ a {
217
+ color: #428bca;
218
+ text-decoration: none;
219
+ }
220
+ a:hover,
221
+ a:focus {
222
+ color: #2a6496;
223
+ text-decoration: underline;
224
+ }
225
+ a:focus {
226
+ outline: thin dotted #333;
227
+ outline: 5px auto -webkit-focus-ring-color;
228
+ outline-offset: -2px;
229
+ }
230
+ img {
231
+ vertical-align: middle;
232
+ }
233
+ .img-responsive {
234
+ display: block;
235
+ max-width: 100%;
236
+ height: auto;
237
+ }
238
+ .img-rounded {
239
+ border-radius: 6px;
240
+ }
241
+ .img-thumbnail {
242
+ padding: 4px;
243
+ line-height: 1.428571429;
244
+ background-color: #ffffff;
245
+ border: 1px solid #dddddd;
246
+ border-radius: 4px;
247
+ -webkit-transition: all 0.2s ease-in-out;
248
+ transition: all 0.2s ease-in-out;
249
+ display: inline-block;
250
+ max-width: 100%;
251
+ height: auto;
252
+ }
253
+ .img-circle {
254
+ border-radius: 50%;
255
+ }
256
+ hr {
257
+ margin-top: 20px;
258
+ margin-bottom: 20px;
259
+ border: 0;
260
+ border-top: 1px solid #eeeeee;
261
+ }
262
+ .sr-only {
263
+ position: absolute;
264
+ width: 1px;
265
+ height: 1px;
266
+ margin: -1px;
267
+ padding: 0;
268
+ overflow: hidden;
269
+ clip: rect(0 0 0 0);
270
+ border: 0;
271
+ }
272
+ p {
273
+ margin: 0 0 10px;
274
+ }
275
+ .lead {
276
+ margin-bottom: 20px;
277
+ font-size: 16.099999999999998px;
278
+ font-weight: 200;
279
+ line-height: 1.4;
280
+ }
281
+ @media (min-width: 768px) {
282
+ .lead {
283
+ font-size: 21px;
284
+ }
285
+ }
286
+ small {
287
+ font-size: 85%;
288
+ }
289
+ cite {
290
+ font-style: normal;
291
+ }
292
+ .text-muted {
293
+ color: #999999;
294
+ }
295
+ .text-primary {
296
+ color: #428bca;
297
+ }
298
+ .text-warning {
299
+ color: #c09853;
300
+ }
301
+ .text-danger {
302
+ color: #b94a48;
303
+ }
304
+ .text-success {
305
+ color: #468847;
306
+ }
307
+ .text-info {
308
+ color: #3a87ad;
309
+ }
310
+ .text-left {
311
+ text-align: left;
312
+ }
313
+ .text-right {
314
+ text-align: right;
315
+ }
316
+ .text-center {
317
+ text-align: center;
318
+ }
319
+ h1,
320
+ h2,
321
+ h3,
322
+ h4,
323
+ h5,
324
+ h6,
325
+ .h1,
326
+ .h2,
327
+ .h3,
328
+ .h4,
329
+ .h5,
330
+ .h6 {
331
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
332
+ font-weight: 500;
333
+ line-height: 1.1;
334
+ }
335
+ h1 small,
336
+ h2 small,
337
+ h3 small,
338
+ h4 small,
339
+ h5 small,
340
+ h6 small,
341
+ .h1 small,
342
+ .h2 small,
343
+ .h3 small,
344
+ .h4 small,
345
+ .h5 small,
346
+ .h6 small {
347
+ font-weight: normal;
348
+ line-height: 1;
349
+ color: #999999;
350
+ }
351
+ h1,
352
+ h2,
353
+ h3 {
354
+ margin-top: 20px;
355
+ margin-bottom: 10px;
356
+ }
357
+ h4,
358
+ h5,
359
+ h6 {
360
+ margin-top: 10px;
361
+ margin-bottom: 10px;
362
+ }
363
+ h1,
364
+ .h1 {
365
+ font-size: 36px;
366
+ }
367
+ h2,
368
+ .h2 {
369
+ font-size: 30px;
370
+ }
371
+ h3,
372
+ .h3 {
373
+ font-size: 24px;
374
+ }
375
+ h4,
376
+ .h4 {
377
+ font-size: 18px;
378
+ }
379
+ h5,
380
+ .h5 {
381
+ font-size: 14px;
382
+ }
383
+ h6,
384
+ .h6 {
385
+ font-size: 12px;
386
+ }
387
+ h1 small,
388
+ .h1 small {
389
+ font-size: 24px;
390
+ }
391
+ h2 small,
392
+ .h2 small {
393
+ font-size: 18px;
394
+ }
395
+ h3 small,
396
+ .h3 small,
397
+ h4 small,
398
+ .h4 small {
399
+ font-size: 14px;
400
+ }
401
+ .page-header {
402
+ padding-bottom: 9px;
403
+ margin: 40px 0 20px;
404
+ border-bottom: 1px solid #eeeeee;
405
+ }
406
+ ul,
407
+ ol {
408
+ margin-top: 0;
409
+ margin-bottom: 10px;
410
+ }
411
+ ul ul,
412
+ ol ul,
413
+ ul ol,
414
+ ol ol {
415
+ margin-bottom: 0;
416
+ }
417
+ .list-unstyled {
418
+ padding-left: 0;
419
+ list-style: none;
420
+ }
421
+ .list-inline {
422
+ padding-left: 0;
423
+ list-style: none;
424
+ }
425
+ .list-inline > li {
426
+ display: inline-block;
427
+ padding-left: 5px;
428
+ padding-right: 5px;
429
+ }
430
+ dl {
431
+ margin-bottom: 20px;
432
+ }
433
+ dt,
434
+ dd {
435
+ line-height: 1.428571429;
436
+ }
437
+ dt {
438
+ font-weight: bold;
439
+ }
440
+ dd {
441
+ margin-left: 0;
442
+ }
443
+ @media (min-width: 768px) {
444
+ .dl-horizontal dt {
445
+ float: left;
446
+ width: 160px;
447
+ clear: left;
448
+ text-align: right;
449
+ overflow: hidden;
450
+ text-overflow: ellipsis;
451
+ white-space: nowrap;
452
+ }
453
+ .dl-horizontal dd {
454
+ margin-left: 180px;
455
+ }
456
+ .dl-horizontal dd:before,
457
+ .dl-horizontal dd:after {
458
+ content: " ";
459
+ /* 1 */
460
+
461
+ display: table;
462
+ /* 2 */
463
+
464
+ }
465
+ .dl-horizontal dd:after {
466
+ clear: both;
467
+ }
468
+ }
469
+ abbr[title],
470
+ abbr[data-original-title] {
471
+ cursor: help;
472
+ border-bottom: 1px dotted #999999;
473
+ }
474
+ abbr.initialism {
475
+ font-size: 90%;
476
+ text-transform: uppercase;
477
+ }
478
+ blockquote {
479
+ padding: 10px 20px;
480
+ margin: 0 0 20px;
481
+ border-left: 5px solid #eeeeee;
482
+ }
483
+ blockquote p {
484
+ font-size: 17.5px;
485
+ font-weight: 300;
486
+ line-height: 1.25;
487
+ }
488
+ blockquote p:last-child {
489
+ margin-bottom: 0;
490
+ }
491
+ blockquote small {
492
+ display: block;
493
+ line-height: 1.428571429;
494
+ color: #999999;
495
+ }
496
+ blockquote small:before {
497
+ content: '\2014 \00A0';
498
+ }
499
+ blockquote.pull-right {
500
+ padding-right: 15px;
501
+ padding-left: 0;
502
+ border-right: 5px solid #eeeeee;
503
+ border-left: 0;
504
+ }
505
+ blockquote.pull-right p,
506
+ blockquote.pull-right small {
507
+ text-align: right;
508
+ }
509
+ blockquote.pull-right small:before {
510
+ content: '';
511
+ }
512
+ blockquote.pull-right small:after {
513
+ content: '\00A0 \2014';
514
+ }
515
+ q:before,
516
+ q:after,
517
+ blockquote:before,
518
+ blockquote:after {
519
+ content: "";
520
+ }
521
+ address {
522
+ display: block;
523
+ margin-bottom: 20px;
524
+ font-style: normal;
525
+ line-height: 1.428571429;
526
+ }
527
+ table {
528
+ max-width: 100%;
529
+ background-color: transparent;
530
+ }
531
+ th {
532
+ text-align: left;
533
+ }
534
+ .table {
535
+ width: 100%;
536
+ margin-bottom: 20px;
537
+ }
538
+ .table thead > tr > th,
539
+ .table tbody > tr > th,
540
+ .table tfoot > tr > th,
541
+ .table thead > tr > td,
542
+ .table tbody > tr > td,
543
+ .table tfoot > tr > td {
544
+ padding: 8px;
545
+ line-height: 1.428571429;
546
+ vertical-align: top;
547
+ border-top: 1px solid #dddddd;
548
+ }
549
+ .table thead > tr > th {
550
+ vertical-align: bottom;
551
+ border-bottom: 2px solid #dddddd;
552
+ }
553
+ .table caption + thead tr:first-child th,
554
+ .table colgroup + thead tr:first-child th,
555
+ .table thead:first-child tr:first-child th,
556
+ .table caption + thead tr:first-child td,
557
+ .table colgroup + thead tr:first-child td,
558
+ .table thead:first-child tr:first-child td {
559
+ border-top: 0;
560
+ }
561
+ .table tbody + tbody {
562
+ border-top: 2px solid #dddddd;
563
+ }
564
+ .table .table {
565
+ background-color: #ffffff;
566
+ }
567
+ .table-condensed thead > tr > th,
568
+ .table-condensed tbody > tr > th,
569
+ .table-condensed tfoot > tr > th,
570
+ .table-condensed thead > tr > td,
571
+ .table-condensed tbody > tr > td,
572
+ .table-condensed tfoot > tr > td {
573
+ padding: 5px;
574
+ }
575
+ .table-bordered {
576
+ border: 1px solid #dddddd;
577
+ }
578
+ .table-bordered > thead > tr > th,
579
+ .table-bordered > tbody > tr > th,
580
+ .table-bordered > tfoot > tr > th,
581
+ .table-bordered > thead > tr > td,
582
+ .table-bordered > tbody > tr > td,
583
+ .table-bordered > tfoot > tr > td {
584
+ border: 1px solid #dddddd;
585
+ }
586
+ .table-bordered > thead > tr > th,
587
+ .table-bordered > thead > tr > td {
588
+ border-bottom-width: 2px;
589
+ }
590
+ .table-striped > tbody > tr:nth-child(odd) > td,
591
+ .table-striped > tbody > tr:nth-child(odd) > th {
592
+ background-color: #f9f9f9;
593
+ }
594
+ .table-hover > tbody > tr:hover > td,
595
+ .table-hover > tbody > tr:hover > th {
596
+ background-color: #f5f5f5;
597
+ }
598
+ table col[class*="col-"] {
599
+ float: none;
600
+ display: table-column;
601
+ }
602
+ table td[class*="col-"],
603
+ table th[class*="col-"] {
604
+ float: none;
605
+ display: table-cell;
606
+ }
607
+ .table > thead > tr > td.active,
608
+ .table > tbody > tr > td.active,
609
+ .table > tfoot > tr > td.active,
610
+ .table > thead > tr > th.active,
611
+ .table > tbody > tr > th.active,
612
+ .table > tfoot > tr > th.active,
613
+ .table > thead > tr.active > td,
614
+ .table > tbody > tr.active > td,
615
+ .table > tfoot > tr.active > td,
616
+ .table > thead > tr.active > th,
617
+ .table > tbody > tr.active > th,
618
+ .table > tfoot > tr.active > th {
619
+ background-color: #f5f5f5;
620
+ }
621
+ .table > thead > tr > td.success,
622
+ .table > tbody > tr > td.success,
623
+ .table > tfoot > tr > td.success,
624
+ .table > thead > tr > th.success,
625
+ .table > tbody > tr > th.success,
626
+ .table > tfoot > tr > th.success,
627
+ .table > thead > tr.success > td,
628
+ .table > tbody > tr.success > td,
629
+ .table > tfoot > tr.success > td,
630
+ .table > thead > tr.success > th,
631
+ .table > tbody > tr.success > th,
632
+ .table > tfoot > tr.success > th {
633
+ background-color: #dff0d8;
634
+ border-color: #d6e9c6;
635
+ }
636
+ .table-hover > tbody > tr > td.success:hover,
637
+ .table-hover > tbody > tr > th.success:hover,
638
+ .table-hover > tbody > tr.success:hover > td {
639
+ background-color: #d0e9c6;
640
+ border-color: #c9e2b3;
641
+ }
642
+ .table > thead > tr > td.danger,
643
+ .table > tbody > tr > td.danger,
644
+ .table > tfoot > tr > td.danger,
645
+ .table > thead > tr > th.danger,
646
+ .table > tbody > tr > th.danger,
647
+ .table > tfoot > tr > th.danger,
648
+ .table > thead > tr.danger > td,
649
+ .table > tbody > tr.danger > td,
650
+ .table > tfoot > tr.danger > td,
651
+ .table > thead > tr.danger > th,
652
+ .table > tbody > tr.danger > th,
653
+ .table > tfoot > tr.danger > th {
654
+ background-color: #f2dede;
655
+ border-color: #eed3d7;
656
+ }
657
+ .table-hover > tbody > tr > td.danger:hover,
658
+ .table-hover > tbody > tr > th.danger:hover,
659
+ .table-hover > tbody > tr.danger:hover > td {
660
+ background-color: #ebcccc;
661
+ border-color: #e6c1c7;
662
+ }
663
+ .table > thead > tr > td.warning,
664
+ .table > tbody > tr > td.warning,
665
+ .table > tfoot > tr > td.warning,
666
+ .table > thead > tr > th.warning,
667
+ .table > tbody > tr > th.warning,
668
+ .table > tfoot > tr > th.warning,
669
+ .table > thead > tr.warning > td,
670
+ .table > tbody > tr.warning > td,
671
+ .table > tfoot > tr.warning > td,
672
+ .table > thead > tr.warning > th,
673
+ .table > tbody > tr.warning > th,
674
+ .table > tfoot > tr.warning > th {
675
+ background-color: #fcf8e3;
676
+ border-color: #fbeed5;
677
+ }
678
+ .table-hover > tbody > tr > td.warning:hover,
679
+ .table-hover > tbody > tr > th.warning:hover,
680
+ .table-hover > tbody > tr.warning:hover > td {
681
+ background-color: #faf2cc;
682
+ border-color: #f8e5be;
683
+ }
684
+ @media (max-width: 768px) {
685
+ .table-responsive {
686
+ width: 100%;
687
+ margin-bottom: 15px;
688
+ overflow-y: hidden;
689
+ overflow-x: scroll;
690
+ border: 1px solid #dddddd;
691
+ }
692
+ .table-responsive > .table {
693
+ margin-bottom: 0;
694
+ background-color: #fff;
695
+ }
696
+ .table-responsive > .table > thead > tr > th,
697
+ .table-responsive > .table > tbody > tr > th,
698
+ .table-responsive > .table > tfoot > tr > th,
699
+ .table-responsive > .table > thead > tr > td,
700
+ .table-responsive > .table > tbody > tr > td,
701
+ .table-responsive > .table > tfoot > tr > td {
702
+ white-space: nowrap;
703
+ }
704
+ .table-responsive > .table-bordered {
705
+ border: 0;
706
+ }
707
+ .table-responsive > .table-bordered > thead > tr > th:first-child,
708
+ .table-responsive > .table-bordered > tbody > tr > th:first-child,
709
+ .table-responsive > .table-bordered > tfoot > tr > th:first-child,
710
+ .table-responsive > .table-bordered > thead > tr > td:first-child,
711
+ .table-responsive > .table-bordered > tbody > tr > td:first-child,
712
+ .table-responsive > .table-bordered > tfoot > tr > td:first-child {
713
+ border-left: 0;
714
+ }
715
+ .table-responsive > .table-bordered > thead > tr > th:last-child,
716
+ .table-responsive > .table-bordered > tbody > tr > th:last-child,
717
+ .table-responsive > .table-bordered > tfoot > tr > th:last-child,
718
+ .table-responsive > .table-bordered > thead > tr > td:last-child,
719
+ .table-responsive > .table-bordered > tbody > tr > td:last-child,
720
+ .table-responsive > .table-bordered > tfoot > tr > td:last-child {
721
+ border-right: 0;
722
+ }
723
+ .table-responsive > .table-bordered > thead > tr:last-child > th,
724
+ .table-responsive > .table-bordered > tbody > tr:last-child > th,
725
+ .table-responsive > .table-bordered > tfoot > tr:last-child > th,
726
+ .table-responsive > .table-bordered > thead > tr:last-child > td,
727
+ .table-responsive > .table-bordered > tbody > tr:last-child > td,
728
+ .table-responsive > .table-bordered > tfoot > tr:last-child > td {
729
+ border-bottom: 0;
730
+ }
731
+ }
732
+
733
+ .wrapper {
734
+ margin: 0 auto;
735
+ width: 980px;
736
+ }
737
+ </style>
738
+ </head>
739
+ <body>
740
+ <div class="wrapper">
741
+ <h1>Gemsurance Report</h1>
742
+ <table class="table">
743
+ <thead>
744
+ <tr>
745
+ <th>Gem Name</th>
746
+ <th>Bundle Version</th>
747
+ <th>Newest Version</th>
748
+ <th>Status</th>
749
+ <th>Detailed Status</th>
750
+ </tr>
751
+ </thead>
752
+ <tbody>
753
+ <% @gem_infos.sort { |a, b| a.name.downcase <=> b.name.downcase }.each do |gem_info| %>
754
+ <%
755
+ row_class = if gem_info.current?
756
+ 'success'
757
+ elsif gem_info.outdated?
758
+ 'warning'
759
+ elsif gem_info.vulnerable?
760
+ 'danger'
761
+ else
762
+ nil
763
+ end
764
+ %>
765
+ <tr class="<%= row_class %>">
766
+ <td><%= gem_info.name %></td>
767
+ <td><%= gem_info.current_version %></td>
768
+ <td><%= gem_info.newest_version %></td>
769
+ <td>
770
+ <% if gem_info.vulnerable? %>
771
+ <strong>Vulnerable</strong>
772
+ <% elsif gem_info.outdated? %>
773
+ <strong>Out of Date</strong>
774
+ <% elsif gem_info.current? %>
775
+ Up-to-Date
776
+ <% else %>
777
+ Unknown Status
778
+ <% end %>
779
+ </td>
780
+ <td>
781
+ <div style="width:200px">
782
+ <% if gem_info.vulnerable? %>
783
+ <% gem_info.vulnerabilities.each do |vulnerability| %>
784
+ <strong><%= vulnerability.title %></strong>
785
+ <dl>
786
+ <dt>CVE</dt>
787
+ <dd><%= vulnerability.cve %></dd>
788
+ <dt>URL</dt>
789
+ <dd><a href="<%= vulnerability.url %>">More Info</a></dd>
790
+ <dt>Patched Versions</dt>
791
+ <dd><%= vulnerability.patched_versions.join(', ') %></dd>
792
+ </dl>
793
+ <% end %>
794
+ <% end %>
795
+ </div>
796
+ </td>
797
+ </tr>
798
+ <% end %>
799
+ </tbody>
800
+ </table>
801
+ </div>
802
+ </body>
803
+ </html>
@@ -0,0 +1,3 @@
1
+ module Gemsurance
2
+ VERSION = '0.1.2'
3
+ end
@@ -0,0 +1,23 @@
1
+ require 'yaml'
2
+
3
+ module Gemsurance
4
+ class Vulnerability
5
+ def initialize(yaml)
6
+ @attributes = YAML.load(yaml)
7
+ end
8
+
9
+ attr_reader :attributes
10
+
11
+ ATTRIBUTES = [:gem, :framework, :cve, :osvdb, :url, :title, :description, :date, :cvss_v2, :patched_versions].freeze
12
+
13
+ ATTRIBUTES.each do |attr|
14
+ define_method(attr) do
15
+ @attributes[attr.to_s]
16
+ end
17
+ end
18
+
19
+ def ==(other)
20
+ @attributes == other.attributes
21
+ end
22
+ end
23
+ end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 0
9
- - 0
10
- version: 0.0.0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jon Kessler
@@ -80,16 +80,39 @@ dependencies:
80
80
  version: 0.9.2.2
81
81
  type: :development
82
82
  version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: nokogiri
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - "="
90
+ - !ruby/object:Gem::Version
91
+ hash: 17
92
+ segments:
93
+ - 1
94
+ - 5
95
+ - 9
96
+ version: 1.5.9
97
+ type: :development
98
+ version_requirements: *id005
83
99
  description: Gem vulnerability and version checker
84
100
  email: jon.kessler@appfolio.com
85
- executables: []
86
-
101
+ executables:
102
+ - gemsurance
87
103
  extensions: []
88
104
 
89
105
  extra_rdoc_files: []
90
106
 
91
- files: []
92
-
107
+ files:
108
+ - bin/gemsurance
109
+ - lib/gemsurance.rb
110
+ - lib/gemsurance/gem_info_retriever.rb
111
+ - lib/gemsurance/html_formatter.rb
112
+ - lib/gemsurance/runner.rb
113
+ - lib/gemsurance/templates/output.html.erb
114
+ - lib/gemsurance/version.rb
115
+ - lib/gemsurance/vulnerability.rb
93
116
  homepage: http://github.com/appfolio/gemsurance
94
117
  licenses:
95
118
  - MIT
@@ -125,4 +148,3 @@ specification_version: 3
125
148
  summary: Your Gem Insurance Policy
126
149
  test_files: []
127
150
 
128
- has_rdoc: