gem-guardian 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/{main.yml → ci.yml} +3 -21
- data/.rubocop.yml +12 -0
- data/CHANGELOG.md +25 -1
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile +0 -1
- data/README.md +397 -49
- data/Rakefile +27 -27
- data/bin/console +2 -2
- data/gem-guardian.gemspec +11 -9
- data/lib/gem/guardian/artifact_store.rb +13 -2
- data/lib/gem/guardian/checksum_provider.rb +181 -0
- data/lib/gem/guardian/cli.rb +99 -7
- data/lib/gem/guardian/configuration.rb +88 -0
- data/lib/gem/guardian/dependency.rb +5 -1
- data/lib/gem/guardian/github_release_verifier.rb +2 -2
- data/lib/gem/guardian/lockfile_parser.rb +32 -6
- data/lib/gem/guardian/progress.rb +66 -0
- data/lib/gem/guardian/provenance_verifier.rb +1 -3
- data/lib/gem/guardian/registry.rb +83 -0
- data/lib/gem/guardian/registry_audit.rb +81 -0
- data/lib/gem/guardian/report_builder.rb +3 -4
- data/lib/gem/guardian/result_printer.rb +35 -5
- data/lib/gem/guardian/rubygems_client.rb +366 -21
- data/lib/gem/guardian/verifier.rb +119 -12
- data/lib/gem/guardian/version.rb +1 -1
- data/lib/gem/guardian.rb +4 -0
- data/script/registry_provenance_audit.rb +41 -0
- metadata +16 -19
- data/sig/gem/guardian/artifact_store.rbs +0 -22
- data/sig/gem/guardian/checksum.rbs +0 -14
- data/sig/gem/guardian/cli.rbs +0 -60
- data/sig/gem/guardian/dependency.rbs +0 -18
- data/sig/gem/guardian/error.rbs +0 -26
- data/sig/gem/guardian/lockfile_parser.rbs +0 -55
- data/sig/gem/guardian/rubygems_client.rbs +0 -46
- data/sig/gem/guardian/verifier.rbs +0 -40
- data/sig/gem/guardian/version.rbs +0 -10
- data/sig/gem/guardian.rbs +0 -4
data/lib/gem/guardian.rb
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
require_relative "guardian/version"
|
|
8
8
|
require_relative "guardian/error"
|
|
9
9
|
require_relative "guardian/checksum"
|
|
10
|
+
require_relative "guardian/checksum_provider"
|
|
11
|
+
require_relative "guardian/configuration"
|
|
10
12
|
require_relative "guardian/dependency"
|
|
11
13
|
require_relative "guardian/lockfile_parser"
|
|
12
14
|
require_relative "guardian/rubygems_client"
|
|
@@ -16,5 +18,7 @@ require_relative "guardian/artifact_store"
|
|
|
16
18
|
require_relative "guardian/verifier"
|
|
17
19
|
require_relative "guardian/provenance_verifier"
|
|
18
20
|
require_relative "guardian/report_builder"
|
|
21
|
+
require_relative "guardian/registry"
|
|
22
|
+
require_relative "guardian/registry_audit"
|
|
19
23
|
require_relative "guardian/result_printer"
|
|
20
24
|
require_relative "guardian/cli"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
|
|
5
|
+
|
|
6
|
+
require "gem/guardian"
|
|
7
|
+
|
|
8
|
+
limit = ENV.fetch("MAX_GEMS", nil)&.to_i
|
|
9
|
+
source = ENV.fetch("REGISTRY_SOURCE", nil)
|
|
10
|
+
sources = source ? [source] : Gem.sources
|
|
11
|
+
|
|
12
|
+
registry = Gem::Guardian::Registry.new(sources:)
|
|
13
|
+
audit = Gem::Guardian::RegistryAudit.new(registry:)
|
|
14
|
+
result = audit.run(limit:)
|
|
15
|
+
counts = result.counts
|
|
16
|
+
|
|
17
|
+
puts "Registry provenance audit"
|
|
18
|
+
puts
|
|
19
|
+
puts "Sources:"
|
|
20
|
+
Array(sources.respond_to?(:to_a) ? sources.to_a : sources).each do |configured_source|
|
|
21
|
+
uri = configured_source.respond_to?(:uri) ? configured_source.uri : configured_source
|
|
22
|
+
puts "- #{uri}"
|
|
23
|
+
end
|
|
24
|
+
puts
|
|
25
|
+
puts "Latest gems scanned: #{result.total}"
|
|
26
|
+
puts
|
|
27
|
+
puts "Provenance:"
|
|
28
|
+
puts " verified: #{counts.fetch(:verified, 0)}"
|
|
29
|
+
puts " unsupported: #{counts.fetch(:unsupported, 0)}"
|
|
30
|
+
puts " mismatch: #{counts.fetch(:mismatch, 0)}"
|
|
31
|
+
puts " error: #{counts.fetch(:error, 0)}"
|
|
32
|
+
|
|
33
|
+
unless result.unsupported.empty?
|
|
34
|
+
puts
|
|
35
|
+
puts "Unsupported:"
|
|
36
|
+
result.unsupported.first(50).each do |entry_result|
|
|
37
|
+
dependency = entry_result.dependency
|
|
38
|
+
puts "- #{dependency.name} #{dependency.version} #{dependency.platform}"
|
|
39
|
+
end
|
|
40
|
+
puts "..." if result.unsupported.size > 50
|
|
41
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gem-guardian
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kenneth Demanawa
|
|
@@ -9,10 +9,10 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
|
-
description:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
description: |
|
|
13
|
+
Verifies gem integrity using lockfile, registry, and artifact checksums,
|
|
14
|
+
audits Bundler checksum coverage, and reports supply-chain provenance
|
|
15
|
+
when available.
|
|
16
16
|
email:
|
|
17
17
|
- kenneth.c.demanawa@gmail.com
|
|
18
18
|
executables:
|
|
@@ -20,7 +20,7 @@ executables:
|
|
|
20
20
|
extensions: []
|
|
21
21
|
extra_rdoc_files: []
|
|
22
22
|
files:
|
|
23
|
-
- ".github/workflows/
|
|
23
|
+
- ".github/workflows/ci.yml"
|
|
24
24
|
- ".github/workflows/pages.yml"
|
|
25
25
|
- ".github/workflows/release.yml"
|
|
26
26
|
- ".gitignore"
|
|
@@ -40,34 +40,31 @@ files:
|
|
|
40
40
|
- lib/gem/guardian.rb
|
|
41
41
|
- lib/gem/guardian/artifact_store.rb
|
|
42
42
|
- lib/gem/guardian/checksum.rb
|
|
43
|
+
- lib/gem/guardian/checksum_provider.rb
|
|
43
44
|
- lib/gem/guardian/cli.rb
|
|
45
|
+
- lib/gem/guardian/configuration.rb
|
|
44
46
|
- lib/gem/guardian/dependency.rb
|
|
45
47
|
- lib/gem/guardian/error.rb
|
|
46
48
|
- lib/gem/guardian/github_client.rb
|
|
47
49
|
- lib/gem/guardian/github_release_verifier.rb
|
|
48
50
|
- lib/gem/guardian/lockfile_parser.rb
|
|
51
|
+
- lib/gem/guardian/progress.rb
|
|
49
52
|
- lib/gem/guardian/provenance_verifier.rb
|
|
53
|
+
- lib/gem/guardian/registry.rb
|
|
54
|
+
- lib/gem/guardian/registry_audit.rb
|
|
50
55
|
- lib/gem/guardian/report_builder.rb
|
|
51
56
|
- lib/gem/guardian/result_printer.rb
|
|
52
57
|
- lib/gem/guardian/rubygems_client.rb
|
|
53
58
|
- lib/gem/guardian/verifier.rb
|
|
54
59
|
- lib/gem/guardian/version.rb
|
|
55
60
|
- mise.toml
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
- sig/gem/guardian/checksum.rbs
|
|
59
|
-
- sig/gem/guardian/cli.rbs
|
|
60
|
-
- sig/gem/guardian/dependency.rbs
|
|
61
|
-
- sig/gem/guardian/error.rbs
|
|
62
|
-
- sig/gem/guardian/lockfile_parser.rbs
|
|
63
|
-
- sig/gem/guardian/rubygems_client.rbs
|
|
64
|
-
- sig/gem/guardian/verifier.rbs
|
|
65
|
-
- sig/gem/guardian/version.rbs
|
|
66
|
-
homepage: https://github.com/kanutocd/gem-guardian
|
|
61
|
+
- script/registry_provenance_audit.rb
|
|
62
|
+
homepage: https://kanutocd.github.io/gem-guardian
|
|
67
63
|
licenses:
|
|
68
64
|
- MIT
|
|
69
65
|
metadata:
|
|
70
|
-
homepage_uri: https://github.
|
|
66
|
+
homepage_uri: https://kanutocd.github.io/gem-guardian
|
|
67
|
+
documentation_uri: https://kanutocd.github.io/gem-guardian
|
|
71
68
|
source_code_uri: https://github.com/kanutocd/gem-guardian
|
|
72
69
|
changelog_uri: https://github.com/kanutocd/gem-guardian/blob/main/CHANGELOG.md
|
|
73
70
|
rubygems_mfa_required: 'true'
|
|
@@ -87,5 +84,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
87
84
|
requirements: []
|
|
88
85
|
rubygems_version: 3.6.9
|
|
89
86
|
specification_version: 4
|
|
90
|
-
summary:
|
|
87
|
+
summary: Gem integrity and supply-chain verification for Ruby.
|
|
91
88
|
test_files: []
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
class ArtifactStore
|
|
4
|
-
@client: untyped
|
|
5
|
-
|
|
6
|
-
@cache_dir: untyped
|
|
7
|
-
|
|
8
|
-
def initialize: (client: untyped, ?cache_dir: untyped) -> void
|
|
9
|
-
|
|
10
|
-
def path_for: (untyped dependency) -> untyped
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
module Gem
|
|
15
|
-
module Guardian
|
|
16
|
-
class ArtifactStore
|
|
17
|
-
def initialize: (client: RubygemsClient, ?cache_dir: String) -> void
|
|
18
|
-
|
|
19
|
-
def path_for: (Dependency dependency) -> String
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
data/sig/gem/guardian/cli.rbs
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
class CLI
|
|
4
|
-
@argv: untyped
|
|
5
|
-
|
|
6
|
-
@stdout: untyped
|
|
7
|
-
|
|
8
|
-
@stderr: untyped
|
|
9
|
-
|
|
10
|
-
def self.start: (untyped argv) -> untyped
|
|
11
|
-
|
|
12
|
-
def initialize: (untyped argv, ?stdout: untyped, ?stderr: untyped) -> void
|
|
13
|
-
|
|
14
|
-
def run: () -> untyped
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def verify: () -> untyped
|
|
19
|
-
|
|
20
|
-
def parse_gem_spec: (untyped spec) -> untyped
|
|
21
|
-
|
|
22
|
-
def option_value: (untyped name) -> (nil | untyped)
|
|
23
|
-
|
|
24
|
-
def print_results: (untyped results) -> untyped
|
|
25
|
-
|
|
26
|
-
def usage: (?untyped io) -> untyped
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
module Gem
|
|
31
|
-
module Guardian
|
|
32
|
-
class CLI
|
|
33
|
-
@argv: untyped
|
|
34
|
-
|
|
35
|
-
@stdout: untyped
|
|
36
|
-
|
|
37
|
-
@stderr: untyped
|
|
38
|
-
|
|
39
|
-
def self.start: (untyped argv) -> untyped
|
|
40
|
-
|
|
41
|
-
def initialize: (untyped argv, ?stdout: untyped, ?stderr: untyped, ?verifier_class: untyped, ?lockfile_parser_class: untyped) -> void
|
|
42
|
-
|
|
43
|
-
def run: () -> untyped
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def verify: () -> untyped
|
|
48
|
-
|
|
49
|
-
def parse_gem_spec: (String spec) -> Dependency
|
|
50
|
-
|
|
51
|
-
def option_value: (String name) -> (String?)
|
|
52
|
-
|
|
53
|
-
def print_results: (untyped results, lockfile_mode: bool) -> void
|
|
54
|
-
|
|
55
|
-
def print_lockfile_coverage: (untyped lockfile_data) -> void
|
|
56
|
-
|
|
57
|
-
def usage: (?untyped io) -> void
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
Dependency: untyped
|
|
4
|
-
end
|
|
5
|
-
end
|
|
6
|
-
module Gem
|
|
7
|
-
module Guardian
|
|
8
|
-
class Dependency
|
|
9
|
-
attr_reader name: String
|
|
10
|
-
|
|
11
|
-
attr_reader version: String
|
|
12
|
-
|
|
13
|
-
attr_reader platform: String?
|
|
14
|
-
|
|
15
|
-
def gem_filename: () -> String
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
data/sig/gem/guardian/error.rbs
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
Error: untyped
|
|
4
|
-
|
|
5
|
-
ChecksumNotFound: untyped
|
|
6
|
-
|
|
7
|
-
ArtifactFetchError: untyped
|
|
8
|
-
|
|
9
|
-
LockfileError: untyped
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
module Gem
|
|
13
|
-
module Guardian
|
|
14
|
-
class Error < StandardError
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
class ChecksumNotFound < Error
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
class ArtifactFetchError < Error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class LockfileError < Error
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
class LockfileParser
|
|
4
|
-
@path: untyped
|
|
5
|
-
|
|
6
|
-
GEM_LINE: ::Regexp
|
|
7
|
-
|
|
8
|
-
def initialize: (?::String path) -> void
|
|
9
|
-
|
|
10
|
-
def dependencies: () -> untyped
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
# Bundler renders native platforms as `1.2.3-x86_64-linux` in the spec line.
|
|
15
|
-
# Ruby versions remain plain, for example `1.2.3`.
|
|
16
|
-
def split_version_and_platform: (untyped value) -> ::Array[untyped]
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
module Gem
|
|
21
|
-
module Guardian
|
|
22
|
-
class LockfileParser
|
|
23
|
-
GEM_LINE: ::Regexp
|
|
24
|
-
CHECKSUM_LINE: ::Regexp
|
|
25
|
-
|
|
26
|
-
class LockfileData
|
|
27
|
-
attr_reader dependencies: ::Array[Dependency]
|
|
28
|
-
|
|
29
|
-
attr_reader checksums: ::Hash[Dependency, ::Hash[String, String]]
|
|
30
|
-
|
|
31
|
-
attr_reader checksums_section_present: bool
|
|
32
|
-
|
|
33
|
-
def checksum_for: (Dependency dependency, ?String algorithm) -> String?
|
|
34
|
-
|
|
35
|
-
def sha256_checksums: () -> ::Hash[Dependency, String]
|
|
36
|
-
|
|
37
|
-
def missing_checksum_dependencies: () -> ::Array[Dependency]
|
|
38
|
-
|
|
39
|
-
def checksums_present?: () -> bool
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def initialize: (?String path) -> void
|
|
43
|
-
|
|
44
|
-
def parse: () -> LockfileData
|
|
45
|
-
|
|
46
|
-
def dependencies: () -> ::Array[Dependency]
|
|
47
|
-
|
|
48
|
-
def checksums: () -> ::Hash[Dependency, ::Hash[String, String]]
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
|
|
52
|
-
def split_version_and_platform: (String value) -> ::Array[String]
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
class RubygemsClient
|
|
4
|
-
@host: untyped
|
|
5
|
-
|
|
6
|
-
@http: untyped
|
|
7
|
-
|
|
8
|
-
DEFAULT_HOST: "https://rubygems.org"
|
|
9
|
-
|
|
10
|
-
def initialize: (?host: untyped, ?http: untyped) -> void
|
|
11
|
-
|
|
12
|
-
def expected_sha256: (untyped dependency) -> untyped
|
|
13
|
-
|
|
14
|
-
def download_gem: (untyped dependency, untyped destination) -> untyped
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def get: (untyped path) -> untyped
|
|
19
|
-
|
|
20
|
-
def platform_matches?: (untyped remote_platform, untyped wanted_platform) -> untyped
|
|
21
|
-
|
|
22
|
-
def blank?: (untyped value) -> untyped
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
module Gem
|
|
27
|
-
module Guardian
|
|
28
|
-
class RubygemsClient
|
|
29
|
-
DEFAULT_HOST: String
|
|
30
|
-
|
|
31
|
-
def initialize: (?host: String, ?http: untyped) -> void
|
|
32
|
-
|
|
33
|
-
def expected_sha256: (Dependency dependency) -> String
|
|
34
|
-
|
|
35
|
-
def download_gem: (Dependency dependency, String destination) -> String
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def get: (String path) -> String
|
|
40
|
-
|
|
41
|
-
def platform_matches?: (untyped remote_platform, untyped wanted_platform) -> bool
|
|
42
|
-
|
|
43
|
-
def blank?: (untyped value) -> bool
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
module Gem
|
|
2
|
-
module Guardian
|
|
3
|
-
VerificationResult: untyped
|
|
4
|
-
|
|
5
|
-
class Verifier
|
|
6
|
-
@client: untyped
|
|
7
|
-
|
|
8
|
-
@artifact_store: untyped
|
|
9
|
-
|
|
10
|
-
def initialize: (?client: untyped, ?artifact_store: untyped?) -> void
|
|
11
|
-
|
|
12
|
-
def verify: (untyped dependency) -> untyped
|
|
13
|
-
|
|
14
|
-
def verify_all: (untyped dependencies) -> untyped
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def secure_compare: (untyped left, untyped right) -> (false | untyped)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
module Gem
|
|
23
|
-
module Guardian
|
|
24
|
-
VerificationResult: untyped
|
|
25
|
-
|
|
26
|
-
class Verifier
|
|
27
|
-
def initialize: (?client: RubygemsClient, ?artifact_store: ArtifactStore?, ?expected_checksums: ::Hash[Dependency, String]) -> void
|
|
28
|
-
|
|
29
|
-
def verify: (Dependency dependency) -> untyped
|
|
30
|
-
|
|
31
|
-
def verify_all: (::Array[Dependency] dependencies) -> ::Array[untyped]
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
def secure_compare: (String left, String right) -> bool
|
|
36
|
-
|
|
37
|
-
def expected_sha256_for: (Dependency dependency) -> ::Array[untyped]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
data/sig/gem/guardian.rbs
DELETED