diff --git a/lib/logtail-rails.rb b/lib/logtail-rails.rb index 3d078d8..cd32212 100755 --- a/lib/logtail-rails.rb +++ b/lib/logtail-rails.rb @@ -50,6 +50,7 @@ def self.integrate! ActionView.integrate! ActiveRecord.integrate! RackLogger.integrate! + filter_parameters_in_urls(::Rails.application.config.filter_parameters) end def self.enabled=(value) @@ -71,6 +72,16 @@ def self.middlewares @middlewares ||= [Logtail::Integrations::Rack::HTTPContext, SessionContext, Logtail::Integrations::Rack::UserContext, Logtail::Integrations::Rack::HTTPEvents, Logtail::Integrations::Rails::ErrorEvent].select(&:enabled?) end + + # Filters the app's filter_parameters from the query strings and the Referer and Location + # URLs that Rack::HTTPEvents logs, unless the app customised its query_string_filters. + # Procs are left out: they rewrite values, while query string filters match names. + def self.filter_parameters_in_urls(filter_parameters) + http_events = Logtail::Integrations::Rack::HTTPEvents + return if http_events.query_string_filters != http_events::DEFAULT_QUERY_STRING_FILTERS + + http_events.query_string_filters = filter_parameters.reject { |filter| filter.is_a?(Proc) } + end end end end diff --git a/spec/logtail-rails/http_events_spec.rb b/spec/logtail-rails/http_events_spec.rb index d185ab9..8bab812 100755 --- a/spec/logtail-rails/http_events_spec.rb +++ b/spec/logtail-rails/http_events_spec.rb @@ -92,6 +92,69 @@ def method_for_action(action_name) end end + describe "query string filters" do + around(:each) do |example| + class RackHttpRedirectController < ActionController::Base + layout nil + + def index + redirect_to "/next?email=jane%40example.com&step=2" + end + + def method_for_action(action_name) + action_name + end + end + + ::RailsApp.routes.draw do + get '/rack_http_redirect' => 'rack_http_redirect#index' + end + + example.run + + Object.send(:remove_const, :RackHttpRedirectController) + end + + it "should filter the app's filter_parameters from the query string, the Referer and the Location" do + dispatch_rails_request("/rack_http_redirect?email=jane%40example.com&page=2&password=hunter2", + "HTTP_REFERER" => "https://example.com/signup?email=jane%40example.com&ref=ad") + + rows = io.string.split("\n").map { |line| JSON.parse(line) } + http_request_received = rows.map { |row| row.dig("event", "http_request_received") }.compact.first + expect(http_request_received["query_string"]).to eq("email=[FILTERED]&page=2&password=[FILTERED]") + expect(JSON.parse(http_request_received["headers_json"])["Referer"]).to eq("https://example.com/signup?email=[FILTERED]&ref=ad") + + http_response_sent = rows.map { |row| row.dig("event", "http_response_sent") }.compact.first + _name, location = JSON.parse(http_response_sent["headers_json"]).find { |name, _value| name.casecmp("location").zero? } + expect(location).to eq("http://example.org/next?email=[FILTERED]&step=2") + end + + it "should use the app's filter_parameters without Procs, when query_string_filters are rack's default" do + with_query_string_filters(described_class::DEFAULT_QUERY_STRING_FILTERS) do + Logtail::Integrations::Rails.filter_parameters_in_urls([:email, /\Apin\z/, ->(_key, value) { value.replace("[FILTERED]") }]) + + expect(described_class.query_string_filters).to eq([:email, /\Apin\z/]) + end + end + + it "should keep query_string_filters that the app customised" do + with_query_string_filters(described_class::DEFAULT_QUERY_STRING_FILTERS + ["code"]) do + Logtail::Integrations::Rails.filter_parameters_in_urls([:email]) + + expect(described_class.query_string_filters).to eq(described_class::DEFAULT_QUERY_STRING_FILTERS + ["code"]) + end + end + + def with_query_string_filters(filters) + previous = described_class.query_string_filters + described_class.query_string_filters = filters + + yield + ensure + described_class.query_string_filters = previous + end + end + # Remove blank lines since Rails does this to space out requests in the logs def clean_lines(lines) lines.select { |line| !line.start_with?(" @metadat") } diff --git a/spec/support/rails.rb b/spec/support/rails.rb index 732257a..1fb5124 100755 --- a/spec/support/rails.rb +++ b/spec/support/rails.rb @@ -20,6 +20,8 @@ class RailsApp < Rails::Application config.active_support.deprecation = :stderr config.eager_load = false config.hosts = nil + # What config/initializers/filter_parameter_logging.rb sets in new Rails 8 apps + config.filter_parameters += [:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn, :cvv, :cvc] end RailsApp.initialize!