# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Inventory # this module is included in classes that need access to the applications dependencies module Dependencies CONTRAST_AGENT = 'contrast-agent' # the #clone is necessary here, as a require in another thread could # potentially result in adding a key to the loaded_specs hash during # iteration. (as in RUBY-330) # this takes care of filtering out contrast-only dependencies def loaded_specs specs = Gem.loaded_specs.clone specs.delete_if { |name, _v| contrast?(name) } end private def contrast_gems @_contrast_gems ||= find_contrast_gems end def contrast? name contrast_gems.include?(name) end # Go through all dependents, given as a pair from the DependencyList: `dependency` # is the dependency itself, filled with all its specs. `dependents` is the array of reverse # dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set, # then contrast depends on it. If its array of dependents is 1, then contrast is the # only dependency in that list. Since only contrast depends on it, we should ignore it. def find_contrast_gems # rubocop:disable Security/Module/Name -- here name is part of Ruby Gems. ignore = Set.new([CONTRAST_AGENT]) contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency| dependency.name == CONTRAST_AGENT end contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents| ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1 end ignore # rubocop:enable Security/Module/Name end end end end end