CVE-2023-40165: Insufficient Information (NVD-CWE-noinfo)
value: rubygems.org is the Ruby community's primary gem (library) hosting service. Insufficient input validation allowed malicious actors to replace any uploaded gem version that had a platform, version number, or gem name matching `/-\d/`, permanently replacing the legitimate upload in the canonical gem storage bucket, and triggering an immediate CDN purge so that the malicious gem would be served immediately. The maintainers have checked all gems matching the `/-\d/` pattern and can confirm that no unexpected `.gem`s were found. As a result, we believe this vulnerability was _not_ exploited. The easiest way to ensure that a user's applications were not exploited by this vulnerability is to check that all of your downloaded .gems have a checksum that matches the checksum recorded in the RubyGems.org database. RubyGems contributor Maciej Mensfeld wrote a tool to automatically check that all downloaded .gem files match the checksums recorded in the RubyGems.org database. You can use it by running: `bundle add bundler-integrity` followed by `bundle exec bundler-integrity`. Neither this tool nor anything else can prove you were not exploited, but the can assist your investigation by quickly comparing RubyGems API-provided checksums with the checksums of files on your disk. The issue has been patched with improved input validation and the changes are live. No action is required on the part of the user. Users are advised to validate their local gems.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 7.5
Published: 2023-08-17T18:15Z
Repository: https://github.com/rubygems/rubygems.org
Language: Ruby
Files changed: Gemfile, Gemfile.lock, app/models/pusher.rb, app/models/specification_policy.rb, app/models/version.rb, lib/patterns.rb, test/helpers/gem_helpers.rb, test/integration/push_test.rb, test/models/pusher_test.rb
Commit date: 2023-08-16 18:37:38 -0700
Commit message: Merge pull request from GHSA-rxcq-2m4f-94wm

* Add improved input validation of pushed gemspecs

See https://github.com/rubygems/rubygems.org/security/advisories/GHSA-rxcq-2m4f-94wm

* Remove debugging code
Diff: diff --git a/Gemfile b/Gemfile
index d0c68d740..36e18e61e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -101,6 +101,7 @@ group :test do
   gem "shoulda", "~> 4.0"
   gem "selenium-webdriver", "~> 4.8"
   gem "webmock", "~> 3.18"
   gem "simplecov", "~> 0.22", require: false
   gem "simplecov-cobertura", "~> 2.1", require: false
+  gem "aggregate_assertions", "~> 0.2.0"
 end
diff --git a/Gemfile.lock b/Gemfile.lock
index c52783e1a..f26cf2c95 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -69,10 +69,12 @@ GEM
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
       tzinfo (~> 2.0)
     addressable (2.8.4)
       public_suffix (>= 2.0.2, < 6.0)
+    aggregate_assertions (0.2.0)
+      minitest (~> 5.0)
     amazing_print (1.5.0)
     android_key_attestation (0.3.0)
     argon2 (2.1.1)
       ffi (~> 1.14)
       ffi-compiler (~> 1.0)
@@ -619,10 +621,11 @@ GEM
 
 PLATFORMS
   ruby
 
 DEPENDENCIES
+  aggregate_assertions (~> 0.2.0)
   amazing_print (~> 1.4)
   autoprefixer-rails (~> 10.4)
   avo (~> 2.28, < 2.36)
   aws-sdk-s3 (~> 1.119)
   aws-sdk-sqs (~> 1.53)
diff --git a/app/models/pusher.rb b/app/models/pusher.rb
index ef41f624c..804b47740 100644
--- a/app/models/pusher.rb
+++ b/app/models/pusher.rb
@@ -34,14 +34,21 @@ class Pusher
     user.mfa_enabled? || !(version_mfa_required? || rubygem.metadata_mfa_required?) ||
       notify("Rubygem requires owners to enable MFA. You must enable MFA before pushing new version.", 403)
   end
 
   def validate
-    signature_missing = "There was a problem saving your gem: \nYou have added cert_chain in gemspec but signature was empty"
+    unless validate_signature_exists?
+      return notify("There was a problem saving your gem: \nYou have added cert_chain in gemspec but signature was empty", 403)
+    end
+
+    return notify("There was a problem saving your gem: #{rubygem.all_errors(version)}", 403) unless rubygem.valid? && version.valid?
+
+    unless version.full_name == spec.original_name && Patterns::NAME_PATTERN.match?(spec.platform.to_s)
+      return notify("There was a problem saving your gem: the uploaded spec has malformed platform attributes", 409)
+    end
 
-    return notify(signature_missing, 403) unless validate_signature_exists?
-    (rubygem.valid? && version.valid?) || notify("There was a problem saving your gem: #{rubygem.all_errors(version)}", 403)
+    true
   end
 
   def save
     # Restructured so that if we fail to write the gem (ie, s3 is down)
     # can clean things up well.
@@ -65,10 +72,11 @@ class Pusher
 
   def pull_spec
     package = Gem::Package.new(body, gem_security_policy)
     @spec = package.spec
     @files = package.files
+    validate_spec
   rescue StandardError => e
     notify <<~MSG, 422
       RubyGems.org cannot process this gem.
       Please try rebuilding it and installing it locally to make sure it's valid.
       Error:
@@ -210,11 +218,12 @@ class Pusher
   def version_mfa_required?
     ActiveRecord::Type::Boolean.new.cast(spec.metadata["rubygems_mfa_required"])
   end
 
   def write_gem(body, spec)
-    original_name = spec.original_name
+    # we validate that the version full_name == spec.original_name
+    original_name = @version.full_name
 
     gem_path = "gems/#{original_name}.gem"
     gem_contents = body.string
 
     spec.abbreviate
@@ -246,6 +255,26 @@ class Pusher
         end
 
       { message: "Pushing gem", version:, rubygem: @version.rubygem.name, pusher: user.as_json }
     end
   end
+
+  def validate_spec
+    @spec.send(:invalidate_memoized_attributes)
+
+    spec = @spec.dup
+
+    cert_chain = spec.cert_chain
+
+    spec.abbreviate
+    spec.sanitize
+
+    # make sure we validate the cert chain, which gets snipped in abbreviate
+    spec.cert_chain = cert_chain
+
+    # Silence warnings from the verification
+    stream = StringIO.new
+    policy = SpecificationPolicy.new(spec)
+    policy.ui = Gem::StreamUI.new(stream, stream, stream, false)
+    policy.validate(false)
+  end
 end
diff --git a/app/models/specification_policy.rb b/app/models/specification_policy.rb
new file mode 100644
index 000000000..9729cc321
--- /dev/null
+++ b/app/models/specification_policy.rb
@@ -0,0 +1,11 @@
+class SpecificationPolicy < Gem::SpecificationPolicy
+  def error(statement)
+    return if statement.start_with?("#{Gem::SpecificationPolicy::LAZY} is not a")
+
+    super
+  end
+
+  def warning(statement)
+    # do nothing
+  end
+end
diff --git a/app/models/version.rb b/app/models/version.rb
index 63a5118d8..2fe862a7b 100644
--- a/app/models/version.rb
+++ b/app/models/version.rb
@@ -22,11 +22,11 @@ class Version < ApplicationRecord
 
   validates :number, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, format: { with: /\A#{Gem::Version::VERSION_PATTERN}\z/o }
   validates :platform, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, format: { with: Rubygem::NAME_PATTERN }
   validates :full_name, presence: true, uniqueness: { case_sensitive: false }
   validates :rubygem, presence: true
-  validates :required_rubygems_version, :licenses, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, allow_blank: true
+  validates :required_rubygems_version, :licenses, :required_ruby_version, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, allow_blank: true
   validates :description, :summary, :authors, :requirements, :cert_chain,
     length: { minimum: 0, maximum: Gemcutter::MAX_TEXT_FIELD_LENGTH },
     allow_blank: true
 
   validate :unique_canonical_number, on: :create
diff --git a/lib/patterns.rb b/lib/patterns.rb
index b8a7eafd0..6b4aff597 100644
--- a/lib/patterns.rb
+++ b/lib/patterns.rb
@@ -4,8 +4,8 @@ module Patterns
   JAVA_HTTP_USER_AGENT  = /^java/i
   SPECIAL_CHARACTERS    = ".-_".freeze
   ALLOWED_CHARACTERS    = "[A-Za-z0-9#{Regexp.escape(SPECIAL_CHARACTERS)}]+".freeze
   ROUTE_PATTERN         = /#{ALLOWED_CHARACTERS}/
   LAZY_ROUTE_PATTERN    = /#{ALLOWED_CHARACTERS}?/
-  NAME_PATTERN          = /\A#{ALLOWED_CHARACTERS}\Z/
+  NAME_PATTERN          = /\A#{ALLOWED_CHARACTERS}\z/
   URL_VALIDATION_REGEXP = %r{\Ahttps?://([^\s:@]+:[^\s:@]*@)?[A-Za-z\d-]+(\.[A-Za-z\d-]+)+\.?(:\d{1,5})?([/?]\S*)?\z}
 end
diff --git a/test/helpers/gem_helpers.rb b/test/helpers/gem_helpers.rb
index f7f6a4dd3..77c486ae8 100644
--- a/test/helpers/gem_helpers.rb
+++ b/test/helpers/gem_helpers.rb
@@ -15,10 +15,31 @@ module GemHelpers
 
   def build_gem(name, version, summary = "Gemcutter", platform = "ruby", &)
     build_gemspec(new_gemspec(name, version, summary, platform, &))
   end
 
+  def build_gem_raw(file_name:, spec:, contents_writer: nil)
+    package = Gem::Package.new file_name
+
+    File.open(file_name, "wb") do |file|
+      Gem::Package::TarWriter.new(file) do |gem|
+        gem.add_file "metadata.gz", 0o444 do |io|
+          package.gzip_to(io) do |gz_io|
+            gz_io.write spec
+          end
+        end
+        gem.add_file "data.tar.gz", 0o444 do |io|
+          package.gzip_to io do |gz_io|
+            Gem::Package::TarWriter.new gz_io do |data_tar|
+              contents_writer[data_tar] if contents_writer
+            end
+          end
+        end
+      end
+    end
+  end
+
   def new_gemspec(name, version, summary, platform, extra_args = {})
     ruby_version = extra_args[:ruby_version]
     rubygems_version = extra_args[:rubygems_version]
     Gem::Specification.new do |s|
       s.name = name
diff --git a/test/integration/push_test.rb b/test/integration/push_test.rb
index 94de5aeb0..f48d93b35 100644
--- a/test/integration/push_test.rb
+++ b/test/integration/push_test.rb
@@ -66,10 +66,12 @@ class PushTest < ActionDispatch::IntegrationTest
       gemspec.add_runtime_dependency(rubygem.name, "> 0")
     end
 
     push_gem "sandworm-1.0.0.gem"
 
+    assert_response :success
+
     get rubygem_path("sandworm")
 
     assert_response :success
     assert page.has_content?("crysknife")
     assert page.has_content?("> 0")
@@ -80,20 +82,24 @@ class PushTest < ActionDispatch::IntegrationTest
       gemspec.add_runtime_dependency("mauddib", "> 1")
     end
 
     push_gem "sandworm-1.0.0.gem"
 
+    assert_response :success
+
     get rubygem_path("sandworm")
 
     assert_response :success
     assert page.has_content?("mauddib")
     assert page.has_content?("> 1")
   end
 
   test "pushing a signed gem" do
     push_gem gem_file("valid_signature-0.0.0.gem")
 
+    assert_response :success
+
     get rubygem_path("valid_signature")
 
     assert_response :success
 
     assert page.has_content?("Signature validity period")
@@ -115,15 +121,17 @@ class PushTest < ActionDispatch::IntegrationTest
     assert_nil RubygemFs.instance.get("gems/bad-characters-1.0.0.gem")
     assert_nil RubygemFs.instance.get("quick/Marshal.4.8/bad-characters-1.0.0.gemspec.rz")
   end
 
   test "push errors don't save files" do
-    build_gem "sandworm", "1.0.0" do |spec|
-      spec.instance_variable_set :@authors, "string"
-    end
+    build_gem "sandworm", "1.0.0"
 
     assert_nil Rubygem.find_by(name: "sandworm")
+
+    # Error on empty authors now happens in a different place,
+    # but test what would happen if marshal dumping failed
+    Gem::Specification.any_instance.stubs(:_dump).raises(NoMethodError)
     push_gem "sandworm-1.0.0.gem"
 
     assert_response :internal_server_error
     assert_match(/problem saving your gem. Please try again./, response.body)
 
@@ -270,10 +278,134 @@ class PushTest < ActionDispatch::IntegrationTest
     assert_equal 1, hook.failures_since_last_success
     assert_equal "2023-03-01T09:50:31+00:00".to_datetime, hook.last_success
     assert_equal "2023-03-01T09:51:31+00:00".to_datetime, hook.last_failure
   end
 
+  context "with specially crafted gemspecs" do
+    should "not allow overwriting gem with -\\d in name" do
+      create(:version, number: "2.0", rubygem: create(:rubygem, name: "book-2"))
+
+      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+        --- !ruby/hash-with-ivars:Gem::Specification
+        ivars:
+          '@name': book
+          '@version': '2-2.0'
+          '@platform': 'not_ruby'
+          '@original_platform': 'not-ruby'
+          '@new_platform': ruby
+          '@summary': 'malicious'
+          '@authors': [test@example.com]
+      YAML
+
+      push_gem "malicious.gem"
+
+      aggregate_assertions "should fail to push" do
+        assert_response :conflict
+
+        assert_nil Rubygem.find_by(name: "book")
+        assert_nil RubygemFs.instance.get("gems/book-2-2.0.gem")
+        assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-2-2.0.gemspec.rz")
+      end
+    end
+
+    should "not allow overwriting platform gem" do
+      create(:version, number: "2.0", platform: "universal-darwin-19", rubygem: create(:rubygem, name: "book"))
+
+      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+        --- !ruby/hash-with-ivars:Gem::Specification
+        ivars:
+          '@name': book-2.0-universal-darwin
+          '@version': '19'
+          '@platform': 'not_ruby'
+          '@original_platform': 'not-ruby'
+          '@new_platform': ruby
+          '@summary': 'malicious'
+          '@authors': [test@example.com]
+      YAML
+
+      push_gem "malicious.gem"
+
+      aggregate_assertions "should fail to push" do
+        assert_response :conflict
+
+        assert_nil Rubygem.find_by(name: "book-2.0-universal-darwin")
+        assert_nil RubygemFs.instance.get("gems/book-2.0-universal-darwin-19.gem")
+        assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-2.0-universal-darwin-19.gemspec.rz")
+      end
+    end
+
+    context "does not allow pushing a gem where the file name does not match the version full_name" do
+      should "fail when original platform is a ruby Gem::Platform" do
+        build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+          --- !ruby/object:Gem::Specification
+          specification_version: 100
+          name: book
+          version: '1'
+          platform: !ruby/object:Gem::Platform
+            os: ruby
+          summary: 'malicious'
+          authors: [test@example.com]
+        YAML
+        push_gem "malicious.gem"
+
+        aggregate_assertions "should fail to push" do
+          assert_response :conflict
+
+          assert_nil Rubygem.find_by(name: "book")
+          assert_nil RubygemFs.instance.get("gems/book-1-ruby.gem")
+          assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-1-ruby.gemspec.rz")
+        end
+      end
+
+      should "fail when original platform is an array that resolves to a platform of ruby" do
+        build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+          --- !ruby/object:Gem::Specification
+          specification_version: 100
+          name: book
+          version: '1'
+          platform: [ruby]
+          summary: 'malicious'
+          authors: [test@example.com]
+        YAML
+        push_gem "malicious.gem"
+
+        assert_response :forbidden
+      end
+    end
+
+    should "fail fast when spec.name is not a string" do
+      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+        --- !ruby/object:Gem::Specification
+        name: !ruby/object:Gem::Version
+          version: []
+        version: '1'
+        summary: 'malicious'
+        authors: [test@example.com]
+      YAML
+      push_gem "malicious.gem"
+
+      assert_response :unprocessable_entity
+    end
+
+    should "fail when spec.platform is invalid" do
+      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
+        --- !ruby/hash-with-ivars:Gem::Specification
+        ivars:
+          '@name': book
+          '@version': '1'
+          '@new_platform': !ruby/object:Gem::Platform
+            os: "../../../../../etc/passwd"
+          '@original_platform': ruby
+          '@summary': 'malicious'
+          '@authors': [test@example.com]
+      YAML
+      push_gem "malicious.gem"
+
+      assert_response :conflict
+    end
+  end
+
   def push_gem(path)
     post api_v1_rubygems_path,
       env: { "RAW_POST_DATA" => File.read(path) },
       headers: { "CONTENT_TYPE" => "application/octet-stream",
                  "HTTP_AUTHORIZATION" => @key }
@@ -281,6 +413,8 @@ class PushTest < ActionDispatch::IntegrationTest
 
   teardown do
     RubygemFs.mock!
     Dir.chdir(Rails.root)
   end
+
+  make_my_diffs_pretty!
 end
diff --git a/test/models/pusher_test.rb b/test/models/pusher_test.rb
index 65c73476d..3db8ae07d 100644
--- a/test/models/pusher_test.rb
+++ b/test/models/pusher_test.rb
@@ -135,10 +135,12 @@ class PusherTest < ActiveSupport::TestCase
     end
 
     should "not be able to save a gem if it is not valid" do
       legit_gem = create(:rubygem, name: "legit-gem")
       create(:version, rubygem: legit_gem, number: "0.0.1")
+      # this isn't the kind of invalid that we're testing with this gem
+      Gem::Specification.any_instance.stubs(:authors).returns(["user@example.com"])
       @gem = gem_file("legit-gem-0.0.1.gem.fake")
       @cutter = Pusher.new(@user, @gem)
       @cutter.stubs(:save).never
       @cutter.process
 

Vulnerable code:     signature_missing = "There was a problem saving your gem: \nYou have added cert_chain in gemspec but signature was empty"
    return notify(signature_missing, 403) unless validate_signature_exists?
    (rubygem.valid? && version.valid?) || notify("There was a problem saving your gem: #{rubygem.all_errors(version)}", 403)
    original_name = spec.original_name
  validates :required_rubygems_version, :licenses, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, allow_blank: true
  NAME_PATTERN          = /\A#{ALLOWED_CHARACTERS}\Z/
    build_gem "sandworm", "1.0.0" do |spec|
      spec.instance_variable_set :@authors, "string"
    end
Fixed code:   gem "aggregate_assertions", "~> 0.2.0"
    aggregate_assertions (0.2.0)
      minitest (~> 5.0)
  aggregate_assertions (~> 0.2.0)
    unless validate_signature_exists?
      return notify("There was a problem saving your gem: \nYou have added cert_chain in gemspec but signature was empty", 403)
    end

    return notify("There was a problem saving your gem: #{rubygem.all_errors(version)}", 403) unless rubygem.valid? && version.valid?

    unless version.full_name == spec.original_name && Patterns::NAME_PATTERN.match?(spec.platform.to_s)
      return notify("There was a problem saving your gem: the uploaded spec has malformed platform attributes", 409)
    end
    true
    validate_spec
    # we validate that the version full_name == spec.original_name
    original_name = @version.full_name

  def validate_spec
    @spec.send(:invalidate_memoized_attributes)

    spec = @spec.dup

    cert_chain = spec.cert_chain

    spec.abbreviate
    spec.sanitize

    # make sure we validate the cert chain, which gets snipped in abbreviate
    spec.cert_chain = cert_chain

    # Silence warnings from the verification
    stream = StringIO.new
    policy = SpecificationPolicy.new(spec)
    policy.ui = Gem::StreamUI.new(stream, stream, stream, false)
    policy.validate(false)
  end
class SpecificationPolicy < Gem::SpecificationPolicy
  def error(statement)
    return if statement.start_with?("#{Gem::SpecificationPolicy::LAZY} is not a")

    super
  end

  def warning(statement)
    # do nothing
  end
end
  validates :required_rubygems_version, :licenses, :required_ruby_version, length: { maximum: Gemcutter::MAX_FIELD_LENGTH }, allow_blank: true
  NAME_PATTERN          = /\A#{ALLOWED_CHARACTERS}\z/
  def build_gem_raw(file_name:, spec:, contents_writer: nil)
    package = Gem::Package.new file_name

    File.open(file_name, "wb") do |file|
      Gem::Package::TarWriter.new(file) do |gem|
        gem.add_file "metadata.gz", 0o444 do |io|
          package.gzip_to(io) do |gz_io|
            gz_io.write spec
          end
        end
        gem.add_file "data.tar.gz", 0o444 do |io|
          package.gzip_to io do |gz_io|
            Gem::Package::TarWriter.new gz_io do |data_tar|
              contents_writer[data_tar] if contents_writer
            end
          end
        end
      end
    end
  end

    assert_response :success

    assert_response :success

    assert_response :success

    build_gem "sandworm", "1.0.0"

    # Error on empty authors now happens in a different place,
    # but test what would happen if marshal dumping failed
    Gem::Specification.any_instance.stubs(:_dump).raises(NoMethodError)
  context "with specially crafted gemspecs" do
    should "not allow overwriting gem with -\\d in name" do
      create(:version, number: "2.0", rubygem: create(:rubygem, name: "book-2"))

      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
        --- !ruby/hash-with-ivars:Gem::Specification
        ivars:
          '@name': book
          '@version': '2-2.0'
          '@platform': 'not_ruby'
          '@original_platform': 'not-ruby'
          '@new_platform': ruby
          '@summary': 'malicious'
          '@authors': [test@example.com]
      YAML

      push_gem "malicious.gem"

      aggregate_assertions "should fail to push" do
        assert_response :conflict

        assert_nil Rubygem.find_by(name: "book")
        assert_nil RubygemFs.instance.get("gems/book-2-2.0.gem")
        assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-2-2.0.gemspec.rz")
      end
    end

    should "not allow overwriting platform gem" do
      create(:version, number: "2.0", platform: "universal-darwin-19", rubygem: create(:rubygem, name: "book"))

      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
        --- !ruby/hash-with-ivars:Gem::Specification
        ivars:
          '@name': book-2.0-universal-darwin
          '@version': '19'
          '@platform': 'not_ruby'
          '@original_platform': 'not-ruby'
          '@new_platform': ruby
          '@summary': 'malicious'
          '@authors': [test@example.com]
      YAML

      push_gem "malicious.gem"

      aggregate_assertions "should fail to push" do
        assert_response :conflict

        assert_nil Rubygem.find_by(name: "book-2.0-universal-darwin")
        assert_nil RubygemFs.instance.get("gems/book-2.0-universal-darwin-19.gem")
        assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-2.0-universal-darwin-19.gemspec.rz")
      end
    end

    context "does not allow pushing a gem where the file name does not match the version full_name" do
      should "fail when original platform is a ruby Gem::Platform" do
        build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
          --- !ruby/object:Gem::Specification
          specification_version: 100
          name: book
          version: '1'
          platform: !ruby/object:Gem::Platform
            os: ruby
          summary: 'malicious'
          authors: [test@example.com]
        YAML
        push_gem "malicious.gem"

        aggregate_assertions "should fail to push" do
          assert_response :conflict

          assert_nil Rubygem.find_by(name: "book")
          assert_nil RubygemFs.instance.get("gems/book-1-ruby.gem")
          assert_nil RubygemFs.instance.get("quick/Marshal.4.8/book-1-ruby.gemspec.rz")
        end
      end

      should "fail when original platform is an array that resolves to a platform of ruby" do
        build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
          --- !ruby/object:Gem::Specification
          specification_version: 100
          name: book
          version: '1'
          platform: [ruby]
          summary: 'malicious'
          authors: [test@example.com]
        YAML
        push_gem "malicious.gem"

        assert_response :forbidden
      end
    end

    should "fail fast when spec.name is not a string" do
      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
        --- !ruby/object:Gem::Specification
        name: !ruby/object:Gem::Version
          version: []
        version: '1'
        summary: 'malicious'
        authors: [test@example.com]
      YAML
      push_gem "malicious.gem"

      assert_response :unprocessable_entity
    end

    should "fail when spec.platform is invalid" do
      build_gem_raw(file_name: "malicious.gem", spec: <<~YAML)
        --- !ruby/hash-with-ivars:Gem::Specification
        ivars:
          '@name': book
          '@version': '1'
          '@new_platform': !ruby/object:Gem::Platform
            os: "../../../../../etc/passwd"
          '@original_platform': ruby
          '@summary': 'malicious'
          '@authors': [test@example.com]
      YAML
      push_gem "malicious.gem"

      assert_response :conflict
    end
  end


  make_my_diffs_pretty!
      # this isn't the kind of invalid that we're testing with this gem
      Gem::Specification.any_instance.stubs(:authors).returns(["user@example.com"])