gemsurance 0.0.0 → 0.1.2

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.
@@ -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: