Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit of gem builder

  • Loading branch information...
commit 9608eed92b3839b06ebf72d5043da547de10ce85 0 parents
@pjhyett pjhyett authored
Showing with 128 additions and 0 deletions.
  1. +31 −0 README
  2. +64 −0 gem_builder.rb
  3. +33 −0 gem_eval.rb
31 README
@@ -0,0 +1,31 @@
+GitHub's Gem Evaler
+-------------------
+
+Help make GitHub's gem build process more secure and robust!
+
+There are two components associated with this:
+
+* gem_builder.rb - Script that builds the gem
+* gem_eval.rb - Sandboxed Sinatra app that evals ruby gemspecs
+
+
+gem_builder.rb works as follows:
+
+1) process() is called with a repository object and the path to the gemspec
+2) If the spec is not in YAML, a request is made to the gem evaler (see below how it works)
+3) A Gem::Specification object is created from the YAML gemspec and renamed with the user's login
+4) The gem is built from the Gem::Specification using a monkey-patched version of RubyGems,
+ so instead of grabbing the files from the filesystem, they're grabbed from the git repo
+
+gem_eval.rb works as follows:
+
+1) Receives a request with the repo location and the ruby gemspec
+2) Makes a shallow clone of the repo and chdir's to that repo
+3) Evals the spec in a separate thread with a higher $SAFE level
+4) Converts spec to YAML
+
+
+Goals
+-----
+* Lower the $SAFE level to allow methods like Dir.glob, but without compromising security.
+* Never get another email from someone wondering why their gem didn't build
64 gem_builder.rb
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby
+require 'rubygems/specification'
+require 'rubygems/builder'
+require 'rubygems/package'
+require 'fileutils'
+
+class GemBuildError < StandardError
+end
+
+Gem::Builder.class_eval do
+ def build(repository)
+ @spec.mark_version
+ @signer = sign
+ write_package_with_git(repository)
+ end
+
+ def write_package_with_git(repository)
+ tmp_path = File.join('/data/github/gems', 'tmp', @spec.file_name)
+ gem_path = File.join('/data/github/gems', 'gems', @spec.file_name)
+
+ open(tmp_path, 'wb') do |gem_io|
+ Gem::Package.open(gem_io, 'w', @signer) do |pkg|
+ pkg.metadata = @spec.to_yaml
+ @spec.files.each do |file|
+ obj = repository.git.tree('master', file).contents.first rescue nil
+ next if obj.nil? || obj.is_a?(Grit::Tree)
+ mode = 0
+ obj.mode.each_byte { |i| mode = (mode << 3) | i-'0'[0] }
+ pkg.add_file_simple file, mode & 0777, obj.size do |tar_io|
+ tar_io.write obj.data
+ end
+ end
+ end
+ end
+
+ FileUtils.mv(tmp_path, gem_path, :force => true)
+ end
+end
+
+def process(repository, path)
+ return unless content = repository.git.tree('master', path).contents.first
+ data = content.data
+
+ if data !~ %r{!ruby/object:Gem::Specification}
+ url = URI.parse("http://gem_evaler:4567/")
+ res = Net::HTTP.post_form(url, { 'repo' => repository.name_with_owner, 'data' => data })
+ data = res.body
+ if data.include?('ERROR: ')
+ data = data.sub('ERROR: ', '')
+ Message.create \
+ :to => repository.owner,
+ :from => (User / :github),
+ :subject => "[#{repository}] Gem Build Failure",
+ :body => "The gem build failed with the following error:\n\n#{data}"
+ raise GemBuildError.new(data)
+ end
+ end
+
+ spec = Gem::Specification.from_yaml(data)
+ name = spec.name.gsub(' ', '-')
+ spec.name = "#{repository.owner}-#{name}"
+ Gem::Builder.new(spec).build(repository)
+end
+
33 gem_eval.rb
@@ -0,0 +1,33 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'rubygems/specification'
+require 'sinatra'
+require 'timeout'
+require 'yaml'
+
+post '/' do
+ begin
+ repo = params[:repo]
+ data = params[:data]
+
+ tmpdir = "tmp/#{repo}"
+ spec = nil
+
+ `git clone --depth 1 git://github.com/#{repo} #{tmpdir}`
+ Dir.chdir(tmpdir) do
+ Timeout::timeout(3) do
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
+ end
+ spec.rubygems_version = Gem::RubyGemsVersion # make sure validation passes
+ spec.validate
+ end
+ `rm -rf #{tmpdir}`
+ YAML.dump spec
+ rescue Object => e
+ `rm -rf #{tmpdir}`
+ puts e
+ puts e.backtrace
+ "ERROR: #{e}"
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.