# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'contrast/utils/duck_utils' require 'contrast/utils/object_share' module Contrast module Agent module Telemetry module Exception # Obfuscate sensitive user data before building exception. module Obfuscate CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.cs__freeze # This will generate different chars for each agent startup but will be constant # for current run and will make identical obfuscation to be compared if given type # is the same. CYPHER = CHARS.chars.shuffle.join.cs__freeze VERSION_MATCH = '[^0-9].-' # List of known places after witch a user name might appear: KNOWN_DIRS = %w[app application project projects git github users home user].cs__freeze class << self # Returns paths for known gems. # # @return [Array] known paths def known_paths @_known_paths ||= KNOWN_DIRS.map do |name| to_regexp(name) end end # Obfuscate a type and replace it with random characters. # # @param path [String] the StackFrame type to obfuscate # @return [String] obfuscated type def obfuscate_path path return path if Contrast::Utils::DuckUtils.empty_duck?(path) cypher(path) end private # Transforms string to regexp # # @param string [String] def to_regexp string /#{ string }/ end # Add cypher to path to make it obscure, but unique enough for # comparisons. Mutates original or duplicate if frozen string. # # @param string [String] string to be transformed. # @return [String] def cypher string cypher = string.cs__frozen? ? string.dup : string dirs = cypher.split(Contrast::Utils::ObjectShare::SLASH) return string unless dirs.cs__is_a?(Array) dirs.each_with_index do |name, idx| next if Contrast::Utils::DuckUtils.empty_duck?(name) next unless match_known(known_paths, name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase) obscure(name) # obscure username (next dir in line) obscure(dirs[idx + 1]) if dirs[idx + 1] end cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH) return cypher if cypher Contrast::Utils::ObjectShare::EMPTY_STRING rescue StandardError Contrast::Utils::ObjectShare::EMPTY_STRING end # @param known [Array] Array of regexp to match against # @param type [String] type to check def match_known known, type known.any? { |regexp| type =~ regexp } end # Replaces chars in name. # # @param [string] name # @return [String, nil] def obscure name name.to_s.tr!(CHARS, CYPHER) end end end end end end end