diffense 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1421ed8ba3bcde63fa7a0e15afc0448aa26e399eff063583859eddda19b99bda
4
+ data.tar.gz: 5c60bdf597124c0246f0d96a503858fa8c5b43b7fa2624f9c2dad5a8cf86fae8
5
+ SHA512:
6
+ metadata.gz: '049401c348f364422039d6979e0cae7d6eb8d67164868cffb859287839147c920c74e37cc45ed4f9995a37c22d466fc740bd876979a60f58b3d876530e470743'
7
+ data.tar.gz: 5d2e5fad1bc5a9ed30e6b64cf55f7755b5835a98d56f0063ae1bb875efaeddf87c61e8b081f2901fc9fdffc987a963ec0df4a797da7b58abe746bd6a2b81a52a
@@ -0,0 +1,25 @@
1
+ version: 2
2
+
3
+ jobs:
4
+ coditsu:
5
+ machine: true
6
+ steps:
7
+ - checkout
8
+ - run: \curl -sSL https://api.coditsu.io/run/ci | bash
9
+
10
+ workflows:
11
+ version: 2
12
+ build:
13
+ jobs:
14
+ - coditsu
15
+
16
+ nightly:
17
+ triggers:
18
+ - schedule:
19
+ cron: '0 0 * * *'
20
+ filters:
21
+ branches:
22
+ only:
23
+ - master
24
+ jobs:
25
+ - coditsu
@@ -0,0 +1,3 @@
1
+ repository_id: 'aa9fa8a4-3b65-4b4b-b066-bc66046068b4'
2
+ api_key: <%= ENV['CODITSU_API_KEY'] %>
3
+ api_secret: <%= ENV['CODITSU_API_SECRET'] %>
@@ -0,0 +1,51 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+ .coditsu/local.yml
@@ -0,0 +1 @@
1
+ 2.7.1
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bundler-security (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (13.0.1)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ bundler
16
+ bundler-security!
17
+ rake
18
+
19
+ BUNDLED WITH
20
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,165 @@
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
+ Library.
@@ -0,0 +1,19 @@
1
+ # Coditsu::BundlerSecurity
2
+
3
+ A bundler plugin that checks if it's secure to install your dependencies.
4
+
5
+ ## Installation
6
+
7
+ $ bundle plugin install bundler-security
8
+
9
+ ## Usage
10
+
11
+ It automatically hooks up to your `bundle` commands.
12
+
13
+ ## Contributing
14
+
15
+ Bug reports and pull requests are welcome on GitHub at https://github.com/coditsu/bundler-security.
16
+
17
+ ## License
18
+
19
+ The gem is available as open source under the terms of the [LGPL-3.0](https://github.com/coditsu/bundler-security/blob/master/LICENSE).
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEODCCAqCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhtYWNp
3
+ ZWovREM9bWVuc2ZlbGQvREM9cGwwHhcNMTkwNzMwMTQ1NDU0WhcNMjAwNzI5MTQ1
4
+ NDU0WjAjMSEwHwYDVQQDDBhtYWNpZWovREM9bWVuc2ZlbGQvREM9cGwwggGiMA0G
5
+ CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC9fCwtaHZG2SyyNXiH8r0QbJQx/xxl
6
+ dkvwWz9QGJO+O8rEx20FB1Ab+MVkfOscwIv5jWpmk1U9whzDPl1uFtIbgu+sk+Zb
7
+ uQlZyK/DPN6c+/BbBL+RryTBRyvkPLoCVwm7uxc/JZ1n4AI6eF4cCZ2ieZ9QgQbU
8
+ MQs2QPqs9hT50Ez/40GnOdadVfiDDGz+NME2C4ms0BriXwZ1tcRTfJIHe2xjIbbb
9
+ y5qRGfsLKcgMzvLQR24olixyX1MR0s4+Wveq3QL/gBhL4veUcv+UABJA8IJR0kyB
10
+ seHHutusiwZ1v3SjjjW1xLLrc2ARV0mgCb0WaK2T4iA3oFTGLh6Ydz8LNl31KQFv
11
+ 94nRd8IhmJxrhQ6dQ/WT9IXoa5S9lfT5lPJeINemH4/6QPABzf9W2IZlCdI9wCdB
12
+ TBaw57MKneGAYZiKjw6OALSy2ltQUCl3RqFl3VP7n8uFy1U987Q5VIIQ3O1UUsQD
13
+ Oe/h+r7GUU4RSPKgPlrwvW9bD/UQ+zF51v8CAwEAAaN3MHUwCQYDVR0TBAIwADAL
14
+ BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFJNIBHdfEUD7TqHqIer2YhWaWhwcMB0GA1Ud
15
+ EQQWMBSBEm1hY2llakBtZW5zZmVsZC5wbDAdBgNVHRIEFjAUgRJtYWNpZWpAbWVu
16
+ c2ZlbGQucGwwDQYJKoZIhvcNAQELBQADggGBAKA4eqko6BTNhlysip6rfBkVTGri
17
+ ZXsL+kRb2hLvsQJS/kLyM21oMlu+LN0aPj3qEFR8mE/YeDD8rLAfruBRTltPNbR7
18
+ xA5eE1gkxY5LfExUtK3b2wPqfmo7mZgfcsMwfYg/tUXw1WpBCnrhAJodpGH6SXmp
19
+ A40qFUZst0vjiOoO+aTblIHPmMJXoZ3K42dTlNKlEiDKUWMRKSgpjjYGEYalFNWI
20
+ hHfCz2r8L2t+dYdMZg1JGbEkq4ADGsAA8ioZIpJd7V4hI17u5TCdi7X5wh/0gN0E
21
+ CgP+nLox3D+l2q0QuQEkayr+auFYkzTCkF+BmEk1D0Ru4mcf3F4CJvEmW4Pzbjqt
22
+ i1tsCWPtJ4E/UUKnKaWKqGbjrjHJ0MuShYzHkodox5IOiCXIQg+1+YSzfXUV6WEK
23
+ KJG/fhg1JV5vVDdVy6x+tv5SQ5ctU0feCsVfESi3rE3zRd+nvzE9HcZ5aXeL1UtJ
24
+ nT5Xrioegu2w1jPyVEgyZgTZC5rvD0nNS5sFNQ==
25
+ -----END CERTIFICATE-----
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'bundler/security/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'diffense'
9
+ spec.version = Bundler::Security::VERSION
10
+ spec.authors = ['Tomasz Pajor']
11
+ spec.email = ['tomek@coditsu.io']
12
+
13
+ spec.summary = 'Bundler Security'
14
+ spec.description = 'Bundler Security'
15
+ spec.homepage = Bundler::Security::HOMEPAGE
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ if spec.respond_to?(:metadata)
21
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
+ else
23
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
24
+ 'public gem pushes.'
25
+ end
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
28
+ spec.require_paths = %w[lib]
29
+
30
+ spec.add_development_dependency 'bundler'
31
+ spec.add_development_dependency 'rake'
32
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ %w[
4
+ bundler
5
+ ].each(&method(:require))
6
+
7
+ %w[
8
+ config/fetcher
9
+ config/file_finder
10
+ commands
11
+ version
12
+ errors
13
+ voting
14
+ ].each { |file| require "bundler/security/#{file}" }
15
+
16
+ %w[
17
+ build_unsafe_gem
18
+ build_success
19
+ build_failure
20
+ gem_policy
21
+ remote_policy
22
+ request
23
+ versions/local
24
+ versions/remote
25
+ ].each { |file| require "bundler/security/voting/#{file}" }
26
+
27
+ # Bundler main namespace
28
+ module Bundler
29
+ # Plugin responsible for safe gem installation
30
+ module Security
31
+ class << self
32
+ # Registers the plugin and add before install all hook
33
+ def register
34
+ return if defined?(@registered) && @registered
35
+
36
+ @registered = true
37
+
38
+ Bundler::Plugin.add_hook('before-install-all') do |_|
39
+ Bundler::Security::Voting.call(
40
+ command,
41
+ build_definition
42
+ )
43
+ end
44
+ end
45
+
46
+ # Build clean instance of bundler definition, as we don't want to pollute the main one
47
+ #
48
+ # @return [Bundler::Definition]
49
+ def build_definition
50
+ Bundler.configure
51
+
52
+ Bundler::Definition.build(
53
+ Bundler.default_gemfile,
54
+ Bundler.default_lockfile,
55
+ true
56
+ )
57
+ end
58
+
59
+ # Command that was run with bundle
60
+ #
61
+ # @return [String]
62
+ def command
63
+ ARGV
64
+ .first
65
+ .then { |value| value || Bundler::Security::Commands::INSTALL }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ # Modules grouping supported bundler commands
6
+ module Commands
7
+ # Install bundler command
8
+ INSTALL = 'install'
9
+ # Update bundler command
10
+ UPDATE = 'update'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ # Module for all the components related to setting up the config
6
+ module Config
7
+ # Class responsible for fetching the config from .coditsu.yml
8
+ module Fetcher
9
+ class << self
10
+ # @param build_path [String] path of the current build
11
+ # @return [OpenStruct] open struct with config details
12
+ # @example
13
+ # details = Fetcher.new.call('./')
14
+ # details.build_path #=> './'
15
+ def call(build_path)
16
+ FileFinder
17
+ .call(build_path)
18
+ .then(&File.method(:read))
19
+ .then(&ERB.method(:new))
20
+ .then(&:result)
21
+ .then(&YAML.method(:load))
22
+ .then { |data| data.merge(build_path: build_path) }
23
+ .then(&OpenStruct.method(:new))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Config
6
+ # Class used to figure out the file from which we should load the settings
7
+ module FileFinder
8
+ # Names of the files or paths where we will look for the settings
9
+ #
10
+ # @note We do the double dot trick, to look outside of the current dir because when
11
+ # executed from a docker container, we copy the local uncommitted settings into the
12
+ # dir above the app location not to pollute the reset state of the git repo
13
+ #
14
+ # @note Order is important, as for local env we should load from
15
+ # local file (if present first)
16
+ FILE_NAMES = %w[
17
+ .coditsu/local.yml
18
+ .coditsu/ci.yml
19
+ .coditsu/*.yml
20
+ .coditsu.yml
21
+ ].map { |name| ["../#{name}", name] }.tap(&:flatten!).freeze
22
+
23
+ private_constant :FILE_NAMES
24
+
25
+ class << self
26
+ # Looks for coditsu settings file for a given env
27
+ #
28
+ # @param build_path [String] path of the current build
29
+ #
30
+ # @return [String] path to the file from which we should load all the settings
31
+ def call(build_path)
32
+ FILE_NAMES
33
+ .map { |name| File.join(build_path, name) }
34
+ .map { |name| Dir[name] }
35
+ .find { |selection| !selection.empty? }
36
+ .tap { |path| path || raise(Errors::MissingConfigurationFile) }
37
+ .first
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ # Build runner app errors
6
+ module Errors
7
+ # Base error class from which all the errors should inherit
8
+ BaseError = Class.new(StandardError)
9
+ # Raised when we couldn't find a valid configuration file
10
+ MissingConfigurationFile = Class.new(BaseError)
11
+ # When we receive invalid remote versions type
12
+ InvalidRemoteVersionsType = Class.new(BaseError)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ # Current BundlerSecurity version
6
+ VERSION = '0.1.1'
7
+ # Coditsu differ homepage
8
+ HOMEPAGE = 'https://diff.coditsu.io'
9
+ end
10
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ # Verifies voting verdicts for gems
6
+ module Voting
7
+ class << self
8
+ # Build verdict
9
+ #
10
+ # @param command [String] either install or update
11
+ # @param definition [Bundler::Definition] definition for your source
12
+ def call(command, definition)
13
+ remote_data(command, definition)
14
+ .then { |policy, gems| [policy, build_gems(policy, gems)] }
15
+ .then { |policy, errors| build_status(policy.type, command, errors) }
16
+ end
17
+
18
+ # Build gems that don't have enough approvals
19
+ #
20
+ # @param policy [Voting::RemotePolicy] remote policy settings
21
+ # @param gems [Hash] remote gem statistics
22
+ #
23
+ # @return [Array] gems that don't have enough approvals based on remote policy
24
+ def build_gems(policy, gems)
25
+ gems.each_with_object([]) do |(name, data), errors|
26
+ gem_policy = GemPolicy.new(name, data, policy)
27
+
28
+ next if gem_policy.approved?
29
+ next unless gem_policy.rejected?
30
+
31
+ errors << BuildUnsafeGem.call(gem_policy)
32
+ end
33
+ end
34
+
35
+ # Fetch data from the differ
36
+ #
37
+ # @param command [String] either install or update
38
+ # @param definition [Bundler::Definition]
39
+ def remote_data(command, definition)
40
+ Versions::Remote
41
+ .call(command, definition)
42
+ .yield_self { |response| [build_remote_policy(response['policy']), response['gems']] }
43
+ end
44
+
45
+ # Build remote policy based on Coditsu differ settings
46
+ #
47
+ # @param policy [Hash] remote policy settings
48
+ #
49
+ # @return [Voting::RemotePolicy]
50
+ def build_remote_policy(policy)
51
+ RemotePolicy.new(
52
+ policy['type'], policy['threshold']
53
+ )
54
+ end
55
+
56
+ # Build security verdict
57
+ #
58
+ # @param remote_policy_type [String]
59
+ # @param command [String] either install or update
60
+ # @param errors [Array] detected security errors
61
+ def build_status(remote_policy_type, command, errors)
62
+ if errors.empty?
63
+ BuildSuccess.call(remote_policy_type, command)
64
+ else
65
+ BuildFailure.call(remote_policy_type, command, errors)
66
+ exit 1
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Build failure security verdict
7
+ module BuildFailure
8
+ class << self
9
+ # Prints failure security verdict
10
+ #
11
+ # @param policy_type [String]
12
+ # @param command [String] either install or update
13
+ # @param errors [Array] detected security errors
14
+ def call(policy_type, command, errors)
15
+ Bundler.ui.error(
16
+ build(policy_type, command, errors)
17
+ )
18
+ end
19
+
20
+ # Builds failure security verdict message
21
+ #
22
+ # @param policy_type [String]
23
+ # @param command [String] either install or update
24
+ # @param errors [Array] detected security errors
25
+ #
26
+ # @return [String]
27
+ def build(policy_type, command, errors)
28
+ [
29
+ "\n",
30
+ message_type(policy_type),
31
+ ", blocking #{command}",
32
+ "\n\n",
33
+ errors.join("\n"),
34
+ "\n\n"
35
+ ].join
36
+ end
37
+
38
+ # Builds a message based on policy type
39
+ #
40
+ # @param policy_type [String]
41
+ #
42
+ # @return [String]
43
+ #
44
+ # @raise InvalidPolicyType if policy type was not recognized
45
+ def message_type(policy_type)
46
+ case policy_type
47
+ when 'organization'
48
+ 'Not enough reviews on your organization'
49
+ when 'community'
50
+ 'Not enough reviews in the community'
51
+ else
52
+ raise InvalidPolicyType, policy_type
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Build successful security verdict
7
+ module BuildSuccess
8
+ class << self
9
+ # Prints successful security verdict
10
+ #
11
+ # @param policy_type [String]
12
+ # @param command [String] either install or update
13
+ def call(policy_type, command)
14
+ Bundler.ui.confirm(
15
+ build(policy_type, command)
16
+ )
17
+ end
18
+
19
+ # Builds successful security verdict message
20
+ #
21
+ # @param policy_type [String]
22
+ # @param command [String] either install or update
23
+ #
24
+ # @return [String]
25
+ def build(policy_type, command)
26
+ [
27
+ "\n",
28
+ message_type(policy_type),
29
+ ", commencing #{command}",
30
+ "\n\n"
31
+ ].join
32
+ end
33
+
34
+ # Builds a message based on policy type
35
+ #
36
+ # @param policy_type [String]
37
+ #
38
+ # @return [String]
39
+ #
40
+ # @raise InvalidPolicyType if policy type was not recognized
41
+ def message_type(policy_type)
42
+ case policy_type
43
+ when 'organization'
44
+ 'All gems approved by your organization'
45
+ when 'community'
46
+ 'All gems approved by community'
47
+ else
48
+ raise InvalidPolicyType, policy_type
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Module responsible for building unsafe gem details
7
+ module BuildUnsafeGem
8
+ # Differ url
9
+ DIFF_URL = 'https://diff.coditsu.io/gems'
10
+
11
+ class << self
12
+ # Builds details of an unsafe gem
13
+ #
14
+ # @param gem_policy [Voting::GemPolicy]
15
+ #
16
+ # @return [String]
17
+ def call(gem_policy)
18
+ [
19
+ gem_policy.name,
20
+ gem_policy.new_version? ? gem_policy.new_version : gem_policy.current_version,
21
+ '-',
22
+ [
23
+ approved(gem_policy.remote_policy, gem_policy),
24
+ rejected(gem_policy.remote_policy, gem_policy)
25
+ ].compact.join(', ')
26
+ ].join(' ')
27
+ end
28
+
29
+ # Builds safe details
30
+ #
31
+ # @param remote_policy [Voting::RemotePolicy]
32
+ # @param gem_policy [Voting::GemPolicy]
33
+ #
34
+ # @return [String]
35
+ def approved(remote_policy, gem_policy)
36
+ return if gem_policy.approved?
37
+
38
+ array = []
39
+ array << "approved (#{gem_policy.approved} expected #{remote_policy.approved})"
40
+ array << differ_url(gem_policy)
41
+ array.join(' ')
42
+ end
43
+
44
+ # Builds malicious details
45
+ #
46
+ # @param remote_policy [Voting::RemotePolicy]
47
+ # @param gem_policy [Voting::GemPolicy]
48
+ #
49
+ # @return [String]
50
+ def rejected(remote_policy, gem_policy)
51
+ return if gem_policy.rejected?
52
+
53
+ array = []
54
+ array << "rejected (#{gem_policy.rejected} expected #{remote_policy.rejected})"
55
+ array << differ_url(gem_policy)
56
+ array.join(' ')
57
+ end
58
+
59
+ # Builds differ url for gem with version details
60
+ #
61
+ # @param gem_policy [Voting::GemPolicy]
62
+ #
63
+ # @return [String]
64
+ def differ_url(gem_policy)
65
+ array = [DIFF_URL, gem_policy.name, gem_policy.current_version]
66
+ array << gem_policy.new_version if gem_policy.new_version?
67
+ array.join('/')
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Gem policy with statistics from Coditsu differ
7
+ class GemPolicy
8
+ attr_reader :name, :current_version, :new_version, :remote_policy
9
+
10
+ # Build gem policy
11
+ #
12
+ # @param name [String] gem name
13
+ # @param gem_data [Array] gem version and statistics from Coditsu
14
+ # @param remote_policy [Voting::RemotePolicy]
15
+ def initialize(name, gem_data, remote_policy)
16
+ @name = name
17
+ @new_version = nil
18
+
19
+ versions = gem_data.first
20
+
21
+ raise Errors::InvalidRemoteVersionsType, versions.class unless versions.is_a?(Array)
22
+
23
+ @current_version = versions.first.empty? ? versions.last : versions.first
24
+ @new_version = versions.last if @current_version != versions.last
25
+
26
+ @remote_policy = remote_policy
27
+ @threshold = gem_data.last[remote_policy.type]
28
+ end
29
+
30
+ # How many time gem was marked as safe
31
+ #
32
+ # @return [Integer]
33
+ def approved
34
+ @threshold['up'].to_i
35
+ end
36
+
37
+ # How many time gem was marked as malicious
38
+ #
39
+ # @return [Integer]
40
+ def rejected
41
+ @threshold['down'].to_i
42
+ end
43
+
44
+ # Checks if a gem is safe based on a remote policy
45
+ #
46
+ # @return [Boolean] true if it's safe, false otherwise
47
+ def approved?
48
+ approved >= @remote_policy.approved
49
+ end
50
+
51
+ # Checks if a gem is malicious based on a remote policy
52
+ #
53
+ # @return [Boolean] true if it's malicious, false otherwise
54
+ def rejected?
55
+ @remote_policy.rejected >= rejected
56
+ end
57
+
58
+ # Check if a new version was requested
59
+ #
60
+ # @return [Boolean] true if new version was requested, false otherwise
61
+ def new_version?
62
+ !@new_version.nil?
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Remote policy settings from Coditsu
7
+ RemotePolicy = Struct.new(:type, :threshold) do
8
+ # How many time gem was marked as safe
9
+ #
10
+ # @return [Integer]
11
+ def approved
12
+ threshold['up'].to_i
13
+ end
14
+
15
+ # How many time gem was marked as malicious
16
+ #
17
+ # @return [Integer]
18
+ def rejected
19
+ threshold['down'].to_i
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'json'
6
+
7
+ module Bundler
8
+ module Security
9
+ module Voting
10
+ # Module responsible for doing request to Coditsu differ
11
+ module Request
12
+ # Differ endpoint url
13
+ ENDPOINT_URL = 'https://diff.coditsu.io/api/bundler.json'
14
+ # Request headers
15
+ HEADERS = { 'Content-Type': 'application/json' }.freeze
16
+
17
+ private_constant :ENDPOINT_URL, :HEADERS
18
+
19
+ class << self
20
+ # Execute request to the differ
21
+ #
22
+ # @param config [OpenStruct] Coditsu config
23
+ # @param payload [Hash] with versions to check
24
+ #
25
+ # @return [Net::HTTPResponse] response from Coditsu differ
26
+ def call(config, payload)
27
+ build_http do |http, uri|
28
+ http.request(build_request(uri, config, payload))
29
+ end
30
+ end
31
+
32
+ # Builds http connection object
33
+ def build_http
34
+ uri = URI(differ_url)
35
+
36
+ Net::HTTP.start(
37
+ uri.host,
38
+ uri.port,
39
+ use_ssl: uri.scheme == 'https',
40
+ verify_mode: OpenSSL::SSL::VERIFY_NONE
41
+ ) { |http| yield(http, uri) }
42
+ end
43
+
44
+ # Build http post request and assigns headers and payload
45
+ #
46
+ # @param uri [URI::HTTPS]
47
+ # @param config [OpenStruct] Coditsu config
48
+ # @param payload [Hash] with versions to check
49
+ #
50
+ # @return [Net::HTTP::Post]
51
+ def build_request(uri, config, payload)
52
+ Net::HTTP::Post
53
+ .new(uri.request_uri, HEADERS)
54
+ .tap { |request| assign_auth(request, config) }
55
+ .tap { |request| assign_payload(request, payload) }
56
+ end
57
+
58
+ # Assigns basic authorization if provided in the config
59
+ #
60
+ # @param request [Net::HTTP::Post] prepared http post
61
+ # @param config [OpenStruct] Coditsu config
62
+ def assign_auth(request, config)
63
+ return unless config
64
+ return unless config.api_key
65
+ return unless config.api_secret
66
+
67
+ request.basic_auth(config.api_key, config.api_secret)
68
+ end
69
+
70
+ # Assigns payload as json
71
+ #
72
+ # @param request [Net::HTTP::Post] prepared http post
73
+ # @param payload [Hash] with versions to check
74
+ def assign_payload(request, payload)
75
+ request.body = JSON.dump(payload)
76
+ end
77
+
78
+ # Provides differ endpoint url
79
+ #
80
+ # @return [String]
81
+ def differ_url
82
+ ENV['CODITSU_DIFFER_URL'] || ENDPOINT_URL
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Security
5
+ module Voting
6
+ # Module responsible for handling both local and remote gem versions
7
+ module Versions
8
+ # Module responsible for preparing current or current/new versions of gems
9
+ module Local
10
+ class << self
11
+ # Definition of a local path, if it matches it means that we are the source
12
+ ME_PATH = '.'
13
+ # Sources that we expect to match ourselves too
14
+ ME_SOURCES = [
15
+ Bundler::Source::Gemspec,
16
+ Bundler::Source::Path
17
+ ].freeze
18
+
19
+ # @param command [String] either install or update
20
+ # @param definition [Bundler::Definition] definition for your source
21
+ def call(command, definition)
22
+ Bundler.ui.silence { definition.resolve_remotely! }
23
+
24
+ case command
25
+ when Commands::INSTALL then build_install(definition)
26
+ when Commands::UPDATE then build_update(definition)
27
+ else
28
+ raise ArgumentError, "invalid command: #{command}"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ # @param definition [Bundler::Definition] definition for your source
35
+ def build_install(definition)
36
+ requested_specs = definition.requested_specs
37
+ # Support case without Gemfile.lock
38
+ if definition.locked_gems
39
+ locked_specs = definition.locked_gems.specs
40
+ introduced = requested_specs.map(&:name) - locked_specs.map(&:name)
41
+ introduced_specs = requested_specs.select { |spec| introduced.include?(spec.name) }
42
+ introduced_specs.concat(locked_specs)
43
+ else
44
+ introduced_specs = requested_specs
45
+ end
46
+
47
+ introduced_specs.each_with_object({}) do |spec, hash|
48
+ next if skip?(spec.source)
49
+
50
+ hash[spec.name] = ['', spec.version.to_s]
51
+ end
52
+ end
53
+
54
+ # @param definition [Bundler::Definition] definition for your source
55
+ def build_update(definition)
56
+ locked_specs = definition.locked_gems.specs
57
+
58
+ definition.requested_specs.each_with_object({}) do |spec, hash|
59
+ next if skip?(spec.source)
60
+
61
+ locked_spec = locked_specs.find { |s| s.name == spec.name }
62
+
63
+ hash[spec.name] = if locked_spec
64
+ [locked_spec.version.to_s, spec.version.to_s]
65
+ else
66
+ ['', spec.version.to_s]
67
+ end
68
+ end
69
+ end
70
+
71
+ # Checks if we should skip a source
72
+ #
73
+ # @param source [Bundler::Source::Git, Bundler::Source::Rubygems]
74
+ #
75
+ # @return [Boolean] true if we should skip this source, false otherwise
76
+ def skip?(source)
77
+ return true if git?(source)
78
+ return true if me?(source)
79
+
80
+ false
81
+ end
82
+
83
+ # Checks if it's a git source
84
+ #
85
+ # @param source [Bundler::Source::Git, Bundler::Source::Rubygems]
86
+ #
87
+ # @return [Boolean] true if it's a git source, false otherwise
88
+ def git?(source)
89
+ source.instance_of?(Bundler::Source::Git)
90
+ end
91
+
92
+ # Checks if it's a self source, this happens for repositories that are a gem
93
+ #
94
+ # @param source [Bundler::Source::Path,Bundler::Source::Git,Bundler::Source::Rubygems]
95
+ #
96
+ # @return [Boolean] true if it's a self source, false otherwise
97
+ def me?(source)
98
+ return false unless ME_SOURCES.include?(source.class)
99
+
100
+ source.path.to_s == ME_PATH
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Bundler
6
+ module Security
7
+ module Voting
8
+ # Module responsible for handling both local and remote gem versions
9
+ module Versions
10
+ # Module responsible for fetching safe/malicious votes
11
+ # for current or current/new versions of gems
12
+ module Remote
13
+ # Differ bundler url
14
+ ENDPOINT_URL = 'https://diff.coditsu.io/api/bundler.json'
15
+
16
+ private_constant :ENDPOINT_URL
17
+
18
+ class << self
19
+ # @param command [String] either install or update
20
+ # @param definition [Bundler::Definition] definition for your source
21
+ def call(command, definition)
22
+ config = fetch_config
23
+
24
+ Request
25
+ .call(config, payload(command, config&.repository_id, definition))
26
+ .then { |response| JSON.parse(response.body) }
27
+ end
28
+
29
+ # @param command [String] either install or update
30
+ # @param repository_id [String] coditsu repository_id
31
+ # @param definition [Bundler::Definition] definition for your source
32
+ #
33
+ # @return [Hash] payload for differ bundler endpoint
34
+ def payload(command, repository_id, definition)
35
+ Local.call(command, definition).each_with_object({}) do |(name, versions), hash|
36
+ hash[:data] ||= {}
37
+ hash[:data][:repository_id] = repository_id if repository_id
38
+ hash[:data][:gems] ||= {}
39
+ hash[:data][:gems][name] = versions
40
+ end
41
+ end
42
+
43
+ # Fetch coditsu config file
44
+ #
45
+ # @return [OpenStruct, nil] configuration object
46
+ #
47
+ # @raise [Errors::MissingConfigurationFile] when no config file
48
+ def fetch_config
49
+ Config::Fetcher.call(
50
+ File.expand_path('..', Bundler.bin_path)
51
+ )
52
+ rescue Errors::MissingConfigurationFile
53
+ nil
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/security'
4
+
5
+ Bundler::Security.register
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: diffense
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Tomasz Pajor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Bundler Security
42
+ email:
43
+ - tomek@coditsu.io
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".circleci/config.yml"
49
+ - ".coditsu/ci.yml"
50
+ - ".gitignore"
51
+ - ".ruby-version"
52
+ - Gemfile
53
+ - Gemfile.lock
54
+ - LICENSE
55
+ - README.md
56
+ - certs/mensfeld.pem
57
+ - diffense.gemspec
58
+ - lib/bundler/security.rb
59
+ - lib/bundler/security/commands.rb
60
+ - lib/bundler/security/config/fetcher.rb
61
+ - lib/bundler/security/config/file_finder.rb
62
+ - lib/bundler/security/errors.rb
63
+ - lib/bundler/security/version.rb
64
+ - lib/bundler/security/voting.rb
65
+ - lib/bundler/security/voting/build_failure.rb
66
+ - lib/bundler/security/voting/build_success.rb
67
+ - lib/bundler/security/voting/build_unsafe_gem.rb
68
+ - lib/bundler/security/voting/gem_policy.rb
69
+ - lib/bundler/security/voting/remote_policy.rb
70
+ - lib/bundler/security/voting/request.rb
71
+ - lib/bundler/security/voting/versions/local.rb
72
+ - lib/bundler/security/voting/versions/remote.rb
73
+ - plugins.rb
74
+ homepage: https://diff.coditsu.io
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.1.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Bundler Security
98
+ test_files: []