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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{main.yml → ci.yml} +3 -21
  3. data/.rubocop.yml +12 -0
  4. data/CHANGELOG.md +25 -1
  5. data/CODE_OF_CONDUCT.md +1 -1
  6. data/Gemfile +0 -1
  7. data/README.md +397 -49
  8. data/Rakefile +27 -27
  9. data/bin/console +2 -2
  10. data/gem-guardian.gemspec +11 -9
  11. data/lib/gem/guardian/artifact_store.rb +13 -2
  12. data/lib/gem/guardian/checksum_provider.rb +181 -0
  13. data/lib/gem/guardian/cli.rb +99 -7
  14. data/lib/gem/guardian/configuration.rb +88 -0
  15. data/lib/gem/guardian/dependency.rb +5 -1
  16. data/lib/gem/guardian/github_release_verifier.rb +2 -2
  17. data/lib/gem/guardian/lockfile_parser.rb +32 -6
  18. data/lib/gem/guardian/progress.rb +66 -0
  19. data/lib/gem/guardian/provenance_verifier.rb +1 -3
  20. data/lib/gem/guardian/registry.rb +83 -0
  21. data/lib/gem/guardian/registry_audit.rb +81 -0
  22. data/lib/gem/guardian/report_builder.rb +3 -4
  23. data/lib/gem/guardian/result_printer.rb +35 -5
  24. data/lib/gem/guardian/rubygems_client.rb +366 -21
  25. data/lib/gem/guardian/verifier.rb +119 -12
  26. data/lib/gem/guardian/version.rb +1 -1
  27. data/lib/gem/guardian.rb +4 -0
  28. data/script/registry_provenance_audit.rb +41 -0
  29. metadata +16 -19
  30. data/sig/gem/guardian/artifact_store.rbs +0 -22
  31. data/sig/gem/guardian/checksum.rbs +0 -14
  32. data/sig/gem/guardian/cli.rbs +0 -60
  33. data/sig/gem/guardian/dependency.rbs +0 -18
  34. data/sig/gem/guardian/error.rbs +0 -26
  35. data/sig/gem/guardian/lockfile_parser.rbs +0 -55
  36. data/sig/gem/guardian/rubygems_client.rbs +0 -46
  37. data/sig/gem/guardian/verifier.rbs +0 -40
  38. data/sig/gem/guardian/version.rbs +0 -10
  39. 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.3.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: 'Audits Bundler checksum coverage and verifies Ruby gem artifacts against
13
- RubyGems SHA256 checksums when needed.
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/main.yml"
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
- - sig/gem/guardian.rbs
57
- - sig/gem/guardian/artifact_store.rbs
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.com/kanutocd/gem-guardian
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: Consumer-side integrity verification for Ruby gems.
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
@@ -1,14 +0,0 @@
1
- module Gem
2
- module Guardian
3
- module Checksum
4
- def self?.sha256_file: (untyped path) -> untyped
5
- end
6
- end
7
- end
8
- module Gem
9
- module Guardian
10
- module Checksum
11
- def self.sha256_file: (String path) -> String
12
- end
13
- end
14
- end
@@ -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
@@ -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
@@ -1,10 +0,0 @@
1
- module Gem
2
- module Guardian
3
- VERSION: "0.1.0"
4
- end
5
- end
6
- module Gem
7
- module Guardian
8
- VERSION: String
9
- end
10
- end
data/sig/gem/guardian.rbs DELETED
@@ -1,4 +0,0 @@
1
- module Gem
2
- module Guardian
3
- end
4
- end