# Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/object_share' require 'contrast/agent/reporting/reporting_events/application_defend_attack_sample_stack' module Contrast module Utils # Utilities for converting ruby stack trace into DTMs class StackTraceUtils # need lib here to separate from specs AGENT_CLASS_MARKER = '/lib/contrast/' class << self # Determine if this method is being invoked by application code or not # based on the immediate caller of the code after Contrast. # # @return [Boolean] true if this code is called with application # (non-gem) code in the caller def custom_code_context? root_dir = Contrast::Agent.framework_manager.app_root stack = Kernel.caller(0, 15) i = 0 while i < stack.length stack_element = stack[i] i += 1 next if stack_element.include?('/contrast/') return stack_element.start_with?(root_dir) end end # Call and translate a caller_locations array to an array of # ApplicationDefendAttackSampleStack for TeamServer to display, excluding any Contrast # code found. # # @return [Array] def build_protect_report_stack_array build_protect_stack(Contrast::Agent::Reporting::ApplicationDefendAttackSampleStack) end private def reject_caller_entries stack stack.reject do |entry| entry = entry.to_s entry.include?(AGENT_CLASS_MARKER) end end # @param clazz [Class] Contrast::Agent::Reporting::ApplicationDefendAttackSampleStack # @return [Array] def build_protect_stack clazz stack = caller(3, 21) return [] unless stack stack = reject_caller_entries(stack) i = 0 stack.map! do |entry| element = clazz.new element = fill_protect_element(element, entry, i) i += 1 element end stack.compact! stack end # In Contrast UI - there are many ways to render the stacktraces. For # Protect, there are two ways we're concerned about, one for the first # element and then all others. # 1 for protect first frame - We need the method specifically for this rendering # 1 for protect Nth frames def fill_protect_element element, caller_location, index return element unless caller_location if index.zero? element.method_name = caller_location.split('`')[-1].delete_suffix!('\'') element.file_name = caller_location.split(':')[0] else element.file_name = caller_location end element rescue StandardError # NOOP nil end end end end end