# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Agent module Assess module Policy module Propagator # Propagation that results in all the tags of the source being # applied to the totality of the target. The target's preexisting # tags are unaffected beyond any merging of overlapping tags. class Splat < Contrast::Agent::Assess::Policy::Propagator::Base class << self def propagate propagation_node, preshift, target tracked_inputs = [] propagation_node.sources.each do |source| case source when Contrast::Utils::ObjectShare::OBJECT_KEY tracked_inputs << preshift.object if Contrast::Agent::Assess::Tracker.tracked?(preshift.object) else check_for_buffer(tracked_inputs, preshift.args[source]) find_argument_inputs(tracked_inputs, preshift.args[source]) end end splat_tags(tracked_inputs, target) Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags end def splat_tags tracked_inputs, target return if tracked_inputs.empty? return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target)) tracked_inputs.each do |input| input_properties = Contrast::Agent::Assess::Tracker.properties(input) next unless input_properties properties.splat_from(input, target) end end private # The arguments to the splat method are complex and of multiple types. As such, we need to handle # Strings and iterables to determine the tracked inputs on which to act. # # @param tracked_inputs [Array] storage for the inputs to act on later # @param arg [Object] an input to the method which act as sources for this propagation. def find_argument_inputs tracked_inputs, arg if arg.is_a?(String) || arg.is_a?(File) tracked_inputs << arg if tracked_value?(arg) else iterable_arg(tracked_inputs, arg) end end def iterable_arg tracked_inputs, arg if Contrast::Utils::DuckUtils.iterable_hash?(arg) arg.each_pair do |key, value| tracked_inputs << key if tracked_value?(key) tracked_inputs << value if tracked_value?(value) end elsif Contrast::Utils::DuckUtils.iterable_enumerable?(arg) arg.each do |value| tracked_inputs << value if tracked_value?(value) end end end def check_for_buffer tracked_inputs, arg return if RUBY_VERSION < '3.1.0' return unless arg.is_a?(IO::Buffer) && tracked_value?(arg) tracked_inputs << arg end end end end end end end end