diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a6250ea --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release Gem + +on: + workflow_dispatch: + +jobs: + release: + name: Publish to RubyGems + runs-on: ubuntu-latest + environment: release-approval + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.4.2' + bundler-cache: true + + - name: Build gem + run: gem build orion.gemspec + + - name: Publish to RubyGems + run: gem push orion-*.gem + env: + RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }} diff --git a/lib/orion/gems/parser.rb b/lib/orion/gems/parser.rb index 70b9d35..3125007 100644 --- a/lib/orion/gems/parser.rb +++ b/lib/orion/gems/parser.rb @@ -17,12 +17,20 @@ def initialize(lockfile:, include_dev: false) def run content = File.read(@lockfile) parser = Bundler::LockfileParser.new(content) - security = Orion::Gems::Security.new(lockfile: @lockfile) + dev_dependencies = extract_dev_dependencies + security = Orion::Gems::Security.new( + lockfile: @lockfile, + include_dev: @include_dev, + dev_dependencies: dev_dependencies + ) - vuln_map = security.vulnerable_gems_map - vulnerabilities = security.detailed_vulnerabilities + vuln_map = security.vulnerable_gems_map || [] + vulnerabilities = security.detailed_vulnerabilities || [] + + analyzed_gems = parser.specs.filter_map do |spec| + is_dev = dev_dependencies.include?(spec.name) + next if is_dev && !@include_dev - analyzed_gems = parser.specs.map do |spec| { name: spec.name, version: spec.version.to_s, @@ -96,6 +104,27 @@ def export_gem_report(gems, vulns, include_vulns: false, filename: "orion_gem_re write_out(dir, "#{filename}.csv", data) end end + + def extract_dev_dependencies + # assumption here is that Gemfile is next to lockfile + lockfile_path = Pathname.new(@lockfile).dirname.to_s + gemfile_path = File.join(lockfile_path, "Gemfile") + + unless File.exist?(gemfile_path) + warn "[orion]: Gemfile not found at #{gemfile_path}. Skipping dev dependency analysis." + return [] + end + + begin + definition = Bundler::Definition.build(gemfile_path, @lockfile, nil) + definition.dependencies + .select { |dep| dep.groups.include?(:development) } + .map(&:name) + rescue => e + warn "[orion]: Failed to parse Gemfile for dev dependencies: #{e.class} - #{e.message}" + [] + end + end end end end diff --git a/lib/orion/gems/security.rb b/lib/orion/gems/security.rb index da4ead8..846b111 100644 --- a/lib/orion/gems/security.rb +++ b/lib/orion/gems/security.rb @@ -5,9 +5,11 @@ module Orion module Gems class Security - def initialize(lockfile:) + def initialize(lockfile:, include_dev: false, dev_dependencies: nil) @lockfile = lockfile @lockfile_dir = Pathname.new(@lockfile).dirname.to_s + @include_dev = include_dev + @dev_dependencies = dev_dependencies || [] @scan_results = nil begin @@ -27,12 +29,16 @@ def scan! def vulnerable_gems_map scan!.each_with_object({}) do |result, hash| + next if skip_dev_gem?(result.gem.name) + hash[result.gem.name] = true end end def detailed_vulnerabilities - scan!.map do |result| + scan!.filter_map do |result| + next if skip_dev_gem?(result.gem.name) + { name: result.gem.name, advisory: result.advisory.title, @@ -42,6 +48,10 @@ def detailed_vulnerabilities } end end + + def skip_dev_gem?(name) + !@include_dev && @dev_dependencies.include?(name) + end end end end diff --git a/lib/orion/version.rb b/lib/orion/version.rb index c8ae28d..d4f9344 100644 --- a/lib/orion/version.rb +++ b/lib/orion/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Orion - VERSION = "0.5.1" + VERSION = "0.6.1" end