diff --git a/config/environments/production.rb b/config/environments/production.rb index cd630ae..d3ef8a9 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -157,17 +157,5 @@ Rails.application.configure do # Skip DNS rebinding protection for the default health check endpoint. config.host_authorization = {exclude: ->(request) { request.path == "/up" }} - # Sentry configuration for production - # Only enabled if SENTRY_DSN environment variable is set - if ENV["SENTRY_DSN"].present? - config.sentry.enabled = true - - # Performance monitoring: sample 20% of transactions for traces - # Adjust based on your traffic volume and Sentry plan limits - config.sentry.traces_sample_rate = ENV.fetch("SENTRY_TRACES_SAMPLE_RATE", 0.2).to_f - - # Continuous profiling: disabled by default in production due to cost - # Enable temporarily for performance investigations if needed - config.sentry.profiles_sample_rate = ENV.fetch("SENTRY_PROFILES_SAMPLE_RATE", 0.0).to_f - end + # Sentry is configured in config/initializers/sentry.rb, gated on SENTRY_DSN. end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 9265979..9b99888 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -1,62 +1,44 @@ -# Sentry configuration for error tracking and performance monitoring -# Only initializes if SENTRY_DSN environment variable is set +# Sentry configuration for error tracking and performance monitoring. +# Only initializes if the SENTRY_DSN environment variable is set. return unless ENV["SENTRY_DSN"].present? -Rails.application.configure do - config.sentry.dsn = ENV["SENTRY_DSN"] +Sentry.init do |config| + config.dsn = ENV["SENTRY_DSN"] - # Set environment (defaults to Rails.env) - config.sentry.environment = ENV["SENTRY_ENVIRONMENT"] || Rails.env + # Environment label (defaults to Rails.env) + config.environment = ENV["SENTRY_ENVIRONMENT"] || Rails.env - # Set release version from Git or environment variable - config.sentry.release = ENV["SENTRY_RELEASE"] || `git rev-parse HEAD 2>/dev/null`.strip.presence || nil + # Release version from an env var or the current Git SHA + config.release = ENV["SENTRY_RELEASE"] || `git rev-parse HEAD 2>/dev/null`.strip.presence - # Sample rate for performance monitoring (0.0 to 1.0) - config.sentry.traces_sample_rate = ENV.fetch("SENTRY_TRACES_SAMPLE_RATE", 0.1).to_f - - # Enable profiling in development/staging, disable in production unless explicitly enabled - config.sentry.profiles_sample_rate = if Rails.env.production? - ENV.fetch("SENTRY_PROFILES_SAMPLE_RATE", 0.0).to_f - else - ENV.fetch("SENTRY_PROFILES_SAMPLE_RATE", 0.5).to_f - end - - # Include additional context - config.sentry.before_send = lambda do |event, hint| - # Filter out sensitive information - if event.context[:extra] - event.context[:extra].reject! { |key, value| - key.to_s.match?(/password|secret|token|key/i) || value.to_s.match?(/password|secret/i) - } + # Only report from production unless explicitly enabled elsewhere. + config.enabled_environments = + if ENV["SENTRY_ENABLED_IN_DEVELOPMENT"] == "true" + %w[production development] + else + %w[production] end - # Filter sensitive parameters - if event.context[:request] - event.context[:request].reject! { |key, value| - key.to_s.match?(/password|secret|token|key|authorization/i) - } + # Don't send cookies, request bodies, or user IPs by default. + config.send_default_pii = false + + # Breadcrumbs for debugging + config.breadcrumbs_logger = [:active_support_logger, :http_logger] + + # Performance monitoring sample rate (0.0 to 1.0) + config.traces_sample_rate = ENV.fetch("SENTRY_TRACES_SAMPLE_RATE", 0.1).to_f + + # Profiling: disabled in production by default due to cost. + config.profiles_sample_rate = + if Rails.env.production? + ENV.fetch("SENTRY_PROFILES_SAMPLE_RATE", 0.0).to_f + else + ENV.fetch("SENTRY_PROFILES_SAMPLE_RATE", 0.5).to_f end - event - end - - # Include breadcrumbs for debugging - config.sentry.breadcrumbs_logger = [:active_support_logger, :http_logger] - - # Send session data for user context - config.sentry.user_context = lambda do - if Current.user.present? - { - id: Current.user.id, - email: Current.user.email_address, - admin: Current.user.admin? - } - end - end - # Ignore common non-critical exceptions - config.sentry.excluded_exceptions += [ + config.excluded_exceptions += [ "ActionController::RoutingError", "ActionController::InvalidAuthenticityToken", "ActionController::UnknownFormat", @@ -66,75 +48,38 @@ Rails.application.configure do "ActiveRecord::RecordNotFound" ] - # Add CSP-specific tags for security events - config.sentry.tags = lambda do - { - # Add application context + # Attach application/user context and scrub anything sensitive before sending. + config.before_send = lambda do |event, _hint| + event.tags = (event.tags || {}).merge( app_name: "clinch", - app_environment: Rails.env, - # Add CSP policy status - csp_enabled: defined?(Rails.application.config.content_security_policy) && - Rails.application.config.content_security_policy.present? - } - end + app_environment: Rails.env + ) - # Enhance before_send to handle CSP events properly - config.sentry.before_send = lambda do |event, hint| - # Filter out sensitive information - if event.context[:extra] - event.context[:extra].reject! { |key, value| + if defined?(Current) && Current.respond_to?(:user) && Current.user + event.user = (event.user || {}).merge( + id: Current.user.id, + email: Current.user.email_address, + admin: Current.user.admin? + ) + end + + if event.extra.is_a?(Hash) + event.extra.reject! do |key, value| key.to_s.match?(/password|secret|token|key/i) || value.to_s.match?(/password|secret/i) - } - end - - # Filter sensitive parameters - if event.context[:request] - event.context[:request].reject! { |key, value| - key.to_s.match?(/password|secret|token|key|authorization/i) - } - end - - # Special handling for CSP violations - if event.tags&.dig(:csp_violation) - # Ensure CSP violations have proper security context - event.context[:server] = event.context[:server] || {} - event.context[:server][:name] = "clinch-auth-service" - event.context[:server][:environment] = Rails.env - - # Add additional security context - event.context[:extra] ||= {} - event.context[:extra][:security_context] = { - csp_reporting: true, - user_authenticated: event.context[:user].present?, - request_origin: event.context[:request]&.dig(:headers, "Origin"), - request_referer: event.context[:request]&.dig(:headers, "Referer") - } + end end event end - # Add CSP-specific breadcrumbs for security events - config.sentry.before_breadcrumb = lambda do |breadcrumb, hint| - # Filter out sensitive breadcrumb data - if breadcrumb[:data] - breadcrumb[:data].reject! { |key, value| - key.to_s.match?(/password|secret|token|key|authorization/i) || - value.to_s.match?(/password|secret/i) - } - end - - # Mark CSP-related events - if breadcrumb[:message]&.include?("CSP Violation") || - breadcrumb[:category]&.include?("csp") - breadcrumb[:data] ||= {} - breadcrumb[:data][:security_event] = true - breadcrumb[:data][:csp_violation] = true + # Scrub sensitive data out of breadcrumbs. + config.before_breadcrumb = lambda do |breadcrumb, _hint| + if breadcrumb.data.is_a?(Hash) + breadcrumb.data.reject! do |key, value| + key.to_s.match?(/password|secret|token|key|authorization/i) || value.to_s.match?(/password|secret/i) + end end breadcrumb end - - # Only send errors in production unless explicitly enabled - config.sentry.enabled = Rails.env.production? || ENV["SENTRY_ENABLED_IN_DEVELOPMENT"] == "true" end