Search code examples
rubybundlergemfile

How do I parse the Gemfile to find internal gems not inside a source block?


For the sake of security internal Ruby gems in the Gemfile should always be referenced inside a source block so it never tries to fetch them from rubygems.org. I'd like to automate finding where people fail to do this, so would like to parse the Gemfile, find any gems that match our internal names, and check that rubygems.org isn't in their possible sources list.

source 'https://rubygems.org'

gem 'rails'

gem 'my-private-gem1' # this should be in the source block below

source PRIVATE_GEM_REPO do
  gem 'my-private-gem2'
end

I've seen you can parse the Gemfile

Bundler::Definition.build('Gemfile', '', {})

But can't find anything in the returned data structure that shows me the available / allowed sources per gem

If I include the Gemfile.lock I see more source info, but it doesn't seem right because every gem lists all my sources regardless of if they're in a source block

Bundler::Definition.build('Gemfile', 'Gemfile.lock', {}).
  locked_gems.
  specs.
  map {|g| [g.full_name, g.source.remotes.map(&:hostname).join(', ')]}
=> ["rails-6.0.3.4", "my.private.gemserver, rubygems.org"],
   ["my-private-gem1-1.0.0", "my.private.gemserver, rubygems.org"],
   ["my-private-gem2-1.0.0", "my.private.gemserver, rubygems.org"]]

Any thoughts on how to parse the Gemfile to find that my-private-gem1 is outside a source block?


Solution

  • Figured it out finally, just took awhile digging through the Bundler methods - and a coworker's help.

    Bundler::Definition.
      build('Gemfile', '', nil).
      dependencies.
      map {|dep| [dep.name, dep.source&.remotes&.map(&:hostname)&.join(', ')]}
    =>
    [["rails", nil],
     ["my-private-gem1", nil],
     ["my-private-gem2", "my.private.gemserver"]]
    

    Now I can easily search that resulting data structure for any private gems that aren't locked down to my private gem server.