class Dependabot::SecurityAdvisory
def affects_version?(version)
def affects_version?(version) return false unless version_class.correct?(version) return false unless [*safe_versions, *vulnerable_versions].any? version = version_class.new(version) # If version is known safe for this advisory, it's not vulnerable return false if safe_versions.any? { |r| r.satisfied_by?(version) } # If in the vulnerable range and not known safe, it's vulnerable return true if vulnerable_versions.any? { |r| r.satisfied_by?(version) } # If a vulnerable range present but not met, it's not vulnerable return false if vulnerable_versions.any? # Finally, if no vulnerable range provided, but a safe range provided, # and this versions isn't included (checked earlier), it's vulnerable safe_versions.any? end
def check_version_requirements
def check_version_requirements unless vulnerable_versions.is_a?(Array) && vulnerable_versions.all? { |i| requirement_class <= i.class } raise ArgumentError, "vulnerable_versions must be an array " \ "of #{requirement_class} instances" end unless safe_versions.is_a?(Array) && safe_versions.all? { |i| requirement_class <= i.class } raise ArgumentError, "safe_versions must be an array " \ "of #{requirement_class} instances" end end
def convert_string_version_requirements(vulnerable_version_strings, safe_versions)
def convert_string_version_requirements(vulnerable_version_strings, safe_versions) @vulnerable_versions = vulnerable_version_strings.flat_map do |vuln_str| next vuln_str unless vuln_str.is_a?(String) requirement_class.requirements_array(vuln_str) end @safe_versions = safe_versions.flat_map do |safe_str| next safe_str unless safe_str.is_a?(String) requirement_class.requirements_array(safe_str) end end
def fixed_by?(dependency)
def fixed_by?(dependency) # Handle case mismatch between the security advisory and parsed name return false unless dependency_name.casecmp(dependency.name)&.zero? return false unless package_manager == dependency.package_manager # TODO: Support no previous version to the same level as dependency graph # and security alerts. We currently ignore dependency updates without a # previous version because we don't know if the dependency was vulnerable. return false unless dependency.previous_version return false unless version_class.correct?(dependency.previous_version) # Ignore deps that weren't previously vulnerable return false unless affects_version?(T.must(dependency.previous_version)) # Removing a dependency is a way to fix the vulnerability return true if dependency.removed? # Select deps that are now fixed !affects_version?(T.must(dependency.version)) end
def initialize(dependency_name:, package_manager:,
def initialize(dependency_name:, package_manager:, vulnerable_versions: [], safe_versions: []) @dependency_name = dependency_name @package_manager = package_manager @vulnerable_version_strings = T.let(vulnerable_versions || [], T::Array[T.any(String, Dependabot::Requirement)]) @vulnerable_versions = T.let([], T::Array[Dependabot::Requirement]) @safe_versions = T.let([], T::Array[Dependabot::Requirement]) convert_string_version_requirements(vulnerable_version_strings, safe_versions || []) check_version_requirements end
def requirement_class
def requirement_class Utils.requirement_class_for_package_manager(package_manager) end
def version_class
def version_class Utils.version_class_for_package_manager(package_manager) end
def vulnerable?(version)
def vulnerable?(version) in_safe_range = safe_versions .any? { |r| r.satisfied_by?(version) } # If version is known safe for this advisory, it's not vulnerable return false if in_safe_range in_vulnerable_range = vulnerable_versions .any? { |r| r.satisfied_by?(version) } # If in the vulnerable range and not known safe, it's vulnerable return true if in_vulnerable_range # If a vulnerable range present but not met, it's not vulnerable return false if vulnerable_versions.any? # Finally, if no vulnerable range provided, but a safe range provided, # and this versions isn't included (checked earlier), it's vulnerable safe_versions.any? end