-
-
Notifications
You must be signed in to change notification settings - Fork 981
Add Attestation Repair action In Avo #6186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…station repairs Co-authored-by: Ben Fischer <[email protected]>
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "logIndex" => 123 }], | ||
| "certificate" => { "rawBytes" => Base64.strict_encode64("DER data") } | ||
| } | ||
| } |
Check failure
Code scanning / CodeQL
Clear-text storage of sensitive information High test
a write to certificate
Copilot Autofix
AI 7 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "kindVersion" => { "kind" => "dsse", "version" => "0.0.1" } }], | ||
| "certificate" => { "rawBytes" => double_encoded } | ||
| } | ||
| } |
Check failure
Code scanning / CodeQL
Clear-text storage of sensitive information High test
a write to certificate
Copilot Autofix
AI 7 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "kindVersion" => { "kind" => "dsse", "version" => "0.0.1" } }], | ||
| "certificate" => { "rawBytes" => Base64.strict_encode64("DER data") } | ||
| } | ||
| } |
Check failure
Code scanning / CodeQL
Clear-text storage of sensitive information High test
a write to certificate
Copilot Autofix
AI 7 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "logIndex" => 123 }], | ||
| "certificate" => { "rawBytes" => double_encoded } | ||
| } | ||
| } |
Check failure
Code scanning / CodeQL
Clear-text storage of sensitive information High test
a write to certificate
Copilot Autofix
AI 7 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds attestation repair functionality to the Avo admin interface, allowing rubygems.org operators to view the status of gem attestations and initiate repairs for known issues. The functionality repairs two specific issues in Sigstore bundles: missing kindVersion fields in transaction log entries and double-encoded PEM certificates.
Key changes:
- Introduces an
AttestationBundleRepairconcern with detection and repair logic for invalid attestations - Adds a
valid_bundle?method to check if an attestation can be successfully parsed - Creates an Avo action (
RepairAttestation) for operators to manually trigger repairs - Adds comprehensive test coverage for the repair functionality
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| app/models/concerns/attestation_bundle_repair.rb | New concern implementing repair logic for two known attestation bundle issues |
| app/models/attestation.rb | Includes the repair concern and adds a valid_bundle? helper method |
| app/avo/resources/attestation.rb | Adds repair action and validity indicator field to the Avo resource |
| app/avo/actions/repair_attestation.rb | Avo action for triggering attestation repairs with appropriate visibility controls |
| test/models/attestation_test.rb | Tests for the repairable? method covering various scenarios |
| test/unit/avo/actions/repair_attestation_test.rb | Comprehensive tests for the repair action functionality |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def valid_bundle? | ||
| sigstore_bundle.present? | ||
| rescue Sigstore::Error | ||
| false | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new valid_bundle? method in the Attestation model lacks test coverage. Consider adding tests to verify that it correctly returns true for valid bundles and false for invalid bundles (both for Sigstore::Error and other potential exceptions).
| field :id, as: :id | ||
|
|
||
| field :valid, as: :boolean, only_on: %i[index show] do | ||
| record.valid_bundle? |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The valid field calls record.valid_bundle? on every attestation displayed on the index and show pages. This method parses the entire Sigstore bundle for each record, which could cause performance issues when displaying many attestations on the index page. Consider adding caching or computing this value asynchronously if performance becomes a concern.
| record.valid_bundle? | |
| Rails.cache.fetch(["attestation-valid-bundle", record.id, record.updated_at]) do | |
| record.valid_bundle? | |
| end |
|
|
||
| refute_predicate attestation, :repairable? | ||
| end | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The repair! method from the AttestationBundleRepair concern is only tested indirectly through the Avo action tests. Consider adding direct unit tests for the repair! method in test/models/attestation_test.rb to ensure it properly modifies the attestation body and returns the expected changes array.
| should "be repairable when verificationMaterial is missing" do | ||
| attestation = Attestation.new( | ||
| version: @version, | ||
| media_type: "application/vnd.dev.sigstore.bundle.v0.3+json", | ||
| body: {} | ||
| ) | ||
|
|
||
| refute_predicate attestation, :repairable? | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description "be repairable when verificationMaterial is missing" doesn't match the assertion. The test expects refute_predicate attestation, :repairable? (i.e., NOT repairable), but the description says it should "be repairable". Either the description should be changed to "not be repairable when verificationMaterial is missing" or the assertion should be changed to assert_predicate.
| test "repairs attestation with missing kindVersion" do | ||
| attestation = Attestation.create!( | ||
| version: @version, | ||
| media_type: "application/vnd.dev.sigstore.bundle.v0.3+json", | ||
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "logIndex" => 123 }], | ||
| "certificate" => { "rawBytes" => Base64.strict_encode64("DER data") } | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| assert_predicate attestation, :repairable? | ||
|
|
||
| action = Avo::Actions::RepairAttestation.new | ||
| action.handle( | ||
| fields: { comment: "Repairing invalid attestation" }, | ||
| current_user: @admin, | ||
| resource: nil, | ||
| records: [attestation], | ||
| query: nil | ||
| ) | ||
|
|
||
| attestation.reload | ||
|
|
||
| refute_predicate attestation, :repairable? | ||
| assert_equal( | ||
| { "kind" => "dsse", "version" => "0.0.1" }, | ||
| attestation.body.dig("verificationMaterial", "tlogEntries", 0, "kindVersion") | ||
| ) | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests do not verify that audit records are created when the repair action is executed. Based on the ApplicationAction base class implementation, all Avo actions should create audit records. Consider adding assertions to check that audit records are created and contain the expected information, similar to other action tests in the codebase.
| module AttestationBundleRepair | ||
| extend ActiveSupport::Concern | ||
|
|
||
| def repairable? | ||
| verification = body["verificationMaterial"] | ||
| return false if verification.blank? | ||
|
|
||
| missing_kind_version?(verification) || double_encoded_certificate?(verification) | ||
| end | ||
|
|
||
| def repair! | ||
| verification = body["verificationMaterial"] | ||
| return false if verification.blank? | ||
|
|
||
| new_body = body.deep_dup | ||
| new_verification = new_body["verificationMaterial"] | ||
| changes = [] | ||
|
|
||
| repair_kind_version!(new_verification, changes) if missing_kind_version?(verification) | ||
| repair_certificate!(new_verification, changes) if double_encoded_certificate?(verification) | ||
|
|
||
| return false if changes.empty? | ||
|
|
||
| update!(body: new_body) | ||
| changes | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AttestationBundleRepair concern and its public methods (repairable? and repair!) lack documentation. Consider adding comments or docstrings that explain what conditions make an attestation repairable and what changes the repair method makes.
| def repair_kind_version!(verification, changes) | ||
| verification["tlogEntries"]&.each_with_index do |entry, idx| | ||
| next if entry.key?("kindVersion") | ||
| entry["kindVersion"] = { "kind" => "dsse", "version" => "0.0.1" } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hardcoded values for kindVersion ("kind" => "dsse", "version" => "0.0.1") lack explanation or documentation about why these specific values are used. Consider adding a comment explaining that these are the correct default values for Sigstore bundles or extracting them as constants with descriptive names.
| assert_predicate attestation, :repairable? | ||
| end | ||
|
|
||
| should "be not repairable when bundle has no known issues" do |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description "be not repairable" contains awkward grammar. It should be "not be repairable" without the extra "be".
| should "be not repairable when bundle has no known issues" do | |
| should "not be repairable when bundle has no known issues" do |
| test "repairs attestation with double-encoded PEM certificate" do | ||
| key = OpenSSL::PKey::RSA.new(2048) | ||
| cert = OpenSSL::X509::Certificate.new | ||
| cert.version = 2 | ||
| cert.serial = 1 | ||
| cert.subject = OpenSSL::X509::Name.parse("/CN=Test") | ||
| cert.issuer = cert.subject | ||
| cert.public_key = key.public_key | ||
| cert.not_before = Time.current | ||
| cert.not_after = Time.current + 3600 | ||
| cert.sign(key, OpenSSL::Digest.new("SHA256")) | ||
|
|
||
| pem_cert = cert.to_pem | ||
| double_encoded = Base64.strict_encode64(pem_cert) | ||
|
|
||
| attestation = Attestation.create!( | ||
| version: @version, | ||
| media_type: "application/vnd.dev.sigstore.bundle.v0.3+json", | ||
| body: { | ||
| "verificationMaterial" => { | ||
| "tlogEntries" => [{ "kindVersion" => { "kind" => "dsse", "version" => "0.0.1" } }], | ||
| "certificate" => { "rawBytes" => double_encoded } | ||
| } | ||
| } | ||
| ) | ||
|
|
||
| assert_predicate attestation, :repairable? | ||
|
|
||
| action = Avo::Actions::RepairAttestation.new | ||
| action.handle( | ||
| fields: { comment: "Repairing invalid attestation" }, | ||
| current_user: @admin, | ||
| resource: nil, | ||
| records: [attestation], | ||
| query: nil | ||
| ) | ||
|
|
||
| attestation.reload | ||
|
|
||
| refute_predicate attestation, :repairable? | ||
|
|
||
| raw_bytes = attestation.body.dig("verificationMaterial", "certificate", "rawBytes") | ||
| decoded = Base64.strict_decode64(raw_bytes) | ||
|
|
||
| refute decoded.start_with?("-----BEGIN CERTIFICATE-----") | ||
| end |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no test coverage for the error handling path in the repair_certificate! method (lines 61-63). Consider adding a test case that verifies behavior when an invalid PEM certificate is provided that causes OpenSSL::X509::Certificate.new to raise an exception.
|
|
||
| def valid_bundle? | ||
| sigstore_bundle.present? | ||
| rescue Sigstore::Error |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The valid_bundle? method only rescues Sigstore::Error, but other exceptions (like ArgumentError from Base64 decoding issues, or other unexpected errors) would propagate and cause the field display to fail. Consider rescuing StandardError or at least additional specific exception types that could occur during bundle creation to make the validity check more robust.
| rescue Sigstore::Error | |
| rescue StandardError => e | |
| Rails.logger.warn("Failed to validate sigstore bundle for Attestation #{id}: #{e.class}: #{e.message}") if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #6186 +/- ##
==========================================
- Coverage 97.24% 97.19% -0.05%
==========================================
Files 476 478 +2
Lines 9788 9850 +62
==========================================
+ Hits 9518 9574 +56
- Misses 270 276 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| false | ||
| end | ||
|
|
||
| def repair_kind_version!(verification, changes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at our attestations, I believe the kind should be hashedrekord instead of dsse
"kindVersion": {
"kind": "hashedrekord",
"version": "0.0.1"
},
| if resource.record.repairable? | ||
| "This attestation has invalid data that needs repair. Proceed with repair?" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's potential to say what is exactly wrong aka kind version, double encode or both, but this is fine for now.
| field :id, as: :id | ||
|
|
||
| field :valid, as: :boolean, only_on: %i[index show] do | ||
| record.valid_bundle? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the purpose of this to see if the attestation needs repairing? It still shows as valid when it does need to be repaired.
This is intended to succeed #6141 by moving the new repair functionality (kept the same from #6141) into Avo so that rubygems.org operators will be able to view the status of Gem Attestations and initiate a repair when applicable.