From 7f45e9350721d0454184ce371f70e3f44c86b83e Mon Sep 17 00:00:00 2001 From: Stephen Margheim Date: Mon, 19 Oct 2026 11:51:18 +0200 Subject: [PATCH 1/2] Record request params and job arguments only when the app opts in filter_parameters matches keys, so positional job arguments, mail delivery jobs and webhook payloads were stored and emailed as they were. The controller action and the job's class, id and queue are still recorded; record_params and record_arguments turn the rest back on. Fixes #117. Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 13 ++++++++++--- lib/solid_errors.rb | 3 +++ lib/solid_errors/subscriber.rb | 4 ++-- test/solid_errors/context_test.rb | 29 +++++++++++++++++++++++++++-- 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 937b804..cffb167 100644 --- a/README.md +++ b/README.md @@ -157,10 +157,16 @@ end ### What each occurrence records -Every occurrence stores the backtrace and the error's context. Rails adds the current request or job to that context, and Solid Errors records it as: +Every occurrence stores the backtrace and the error's context. Rails adds the current request or job to that context, and Solid Errors records which one it was: the controller action (`UsersController#show`), or the job's class, id and queue. -* **Requests:** the controller action (`UsersController#show`) and its params, with your `config.filter_parameters` applied. -* **Background jobs:** the job class, its id and queue, and its arguments, also filtered. +Request params and job arguments are not recorded unless you opt in, because they often carry user content that `config.filter_parameters` can't catch: it matches keys, so positional job arguments, mail delivery jobs (the mail's recipients and body) and webhook payloads would be stored and emailed as they are. + +```ruby +config.solid_errors.record_params = true # the request's params, with filter_parameters applied +config.solid_errors.record_arguments = true # the job's arguments, with filter_parameters applied +``` + +Context you set yourself takes precedence. For example, `Rails.error.set_context(params: ...)` in a `before_action` replaces the recorded params. Any Active Record object in the context is stored as its [GlobalID](https://github.com/rails/globalid) (`gid://my-app/User/1`), so you can tell which record was involved. Add your own context with `Rails.error.set_context(...)` or the `context:` option shown above. @@ -181,6 +187,7 @@ You can configure Solid Errors via the Rails configuration object, under the `so * `email_subject` - A proc that builds the subject line for email notifications. See [Email notifications](#email-notifications) for more information. * `base_controller_class` - Specify a different controller as the base class for the Solid Errors controller. See [Authentication](#authentication) for more information. * `destroy_after` - If set, Solid Errors will periodically destroy resolved records that are older than the value specified. See [Automatically destroying old records](#automatically-destroying-old-records) for more information. +* `record_params` / `record_arguments` - Whether to record request params and job arguments with each occurrence. Both default to `false`. See [What each occurrence records](#what-each-occurrence-records). * `ignored_errors` - Error classes, or their names, to never record, in addition to the built-in list in `SolidErrors::Subscriber::IGNORED_ERRORS`. For example, `config.solid_errors.ignored_errors = ["ActiveStorage::FileNotFoundError"]`. ### Database Configuration diff --git a/lib/solid_errors.rb b/lib/solid_errors.rb index c4faa29..81355fb 100644 --- a/lib/solid_errors.rb +++ b/lib/solid_errors.rb @@ -21,6 +21,9 @@ module SolidErrors } mattr_accessor :destroy_after mattr_accessor :ignored_errors, default: [] + # Request params and job arguments carry user content that filter_parameters' key matching misses + mattr_accessor :record_params, default: false + mattr_accessor :record_arguments, default: false mattr_writer :url_helper_name, :url_options class << self diff --git a/lib/solid_errors/subscriber.rb b/lib/solid_errors/subscriber.rb index 21b2b6b..72e7248 100644 --- a/lib/solid_errors/subscriber.rb +++ b/lib/solid_errors/subscriber.rb @@ -52,13 +52,13 @@ def readable_context(context) context = context.dup if defined?(ActionController::Metal) && (controller = context[:controller]).is_a?(ActionController::Metal) context[:controller] = "#{controller.class.name}##{controller.action_name}" - context[:params] ||= controller.request.filtered_parameters.except("controller", "action") + context[:params] ||= controller.request.filtered_parameters.except("controller", "action") if SolidErrors.record_params end if defined?(ActiveJob::Base) && (job = context[:job]).is_a?(ActiveJob::Base) context[:job] = job.class.name context[:job_id] ||= job.job_id context[:queue] ||= job.queue_name - context[:arguments] ||= parameter_filter.filter(arguments: s(job.arguments))[:arguments] + context[:arguments] ||= parameter_filter.filter(arguments: s(job.arguments))[:arguments] if SolidErrors.record_arguments end context end diff --git a/test/solid_errors/context_test.rb b/test/solid_errors/context_test.rb index be589b6..3af49e3 100644 --- a/test/solid_errors/context_test.rb +++ b/test/solid_errors/context_test.rb @@ -1,7 +1,31 @@ require "test_helper" class SolidErrors::ContextTest < ActionDispatch::IntegrationTest - test "an error in a request records the controller action and its filtered params" do + teardown do + SolidErrors.record_params = false + SolidErrors.record_arguments = false + end + + test "an error in a request records the controller action but not its params by default" do + post "/reports", params: {message: {content: "something private"}} + + context = SolidErrors::Occurrence.last.context + assert_equal "ReportsController#create", context["controller"] + assert_not context.key?("params") + end + + test "an error in a job records the job but not its arguments by default" do + job = ReportJob.new("someone@example.com", {"body" => "something private"}) + job.perform_now + + context = SolidErrors::Occurrence.last.context + assert_equal ["ReportJob", job.job_id, "default"], context.values_at("job", "job_id", "queue") + assert_not context.key?("arguments") + end + + test "an error in a request records its filtered params when the app opts in" do + SolidErrors.record_params = true + post "/reports", params: {name: "Ada", password: "secret"} context = SolidErrors::Occurrence.last.context @@ -9,7 +33,8 @@ class SolidErrors::ContextTest < ActionDispatch::IntegrationTest assert_equal({"name" => "Ada", "password" => "[FILTERED]"}, context["params"]) end - test "an error in a job records the job and its filtered arguments, with records as global ids" do + test "an error in a job records its filtered arguments, with records as global ids, when the app opts in" do + SolidErrors.record_arguments = true record = record_an_error job = ReportJob.new(record, {"password" => "secret", "retries" => 3}) From 15651403a7eeab2ee51d4f78d281c9e8d8567959 Mon Sep 17 00:00:00 2001 From: Stephen Margheim Date: Mon, 19 Oct 2026 11:51:32 +0200 Subject: [PATCH 2/2] Separate the occurrence section from Configuration in the README Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cffb167..5a71eda 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Context you set yourself takes precedence. For example, `Rails.error.set_context Any Active Record object in the context is stored as its [GlobalID](https://github.com/rails/globalid) (`gid://my-app/User/1`), so you can tell which record was involved. Add your own context with `Rails.error.set_context(...)` or the `context:` option shown above. Errors raised in background jobs reach Solid Errors when your job backend runs jobs inside the Rails executor, as Solid Queue, Sidekiq and GoodJob do. You don't need any extra setup for this. + ### Configuration You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, the following configuration options are available: