Skip to content

Keep Rails' parameter filtering working, and filter every value LogStruct logs - #16

Merged
ndbroadbent merged 6 commits into
mainfrom
nathan/filtering-security-fixes
Oct 17, 2026
Merged

ndbroadbent merged 6 commits into
mainfrom
nathan/filtering-security-fixes

Conversation

@ndbroadbent

@ndbroadbent ndbroadbent commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

The first of twelve stacked PRs for 0.2.0. Merge them in order.

What this fixes

  • Rails' filter_parameters stayed empty. LogStruct copied Rails' list into its own filters and then cleared it, so request.filtered_parameters, error reporters' parameter filters and Rails' request inspection stopped filtering. Setting component loggers at boot also loaded ActiveRecord::Base before the app's initializers, so filter_attributes was empty and inspected records showed passwords and tokens. Rails' list is now left alone, and the loggers are set from load hooks.
  • Values deeper than the depth limit were logged raw (Values nested more than 20 levels deep are logged raw, skipping filtering and scrubbing #15), skipping filtering and scrubbing. They're now summarized (class and size), as is any value that raises while being processed.
  • Rails' filters matched by exact name only. :passw didn't filter user_password. Symbols, Strings and Regexps now go through ActiveSupport::ParameterFilter, so they match what Rails matches. Partial matches don't apply to LogStruct's own top-level field names (Rails' default :_key would filter GoodJob's cron_key). Rails' filter blocks get copies of the key and value.
  • Structs, ActionController::Parameters and other objects were serialized without filtering. The formatter now filters what as_json returns.
  • Circular references (a Hash containing itself from two keys) took exponential time and produced huge lines. They're logged once.
  • Verbose query logs are turned off on the ActiveRecord module, without loading ActiveRecord::Base.

Also pins json below 3 in the Rails test app (json 3 rejects an option ActiveSupport passes to it).

Addresses #15.

Testing

Every regression test fails without its fix. Sorbet and the unit suite (284 tests) pass at this branch's tip.


Stack for 0.2.0, merged in order: #16 → #17 → #18 → #19 → #20 → #21 → #22 → #23 → #24 → #25 → #26 → #27

Summary by CodeRabbit

  • New Features

    • Rails parameter filters now also protect LogStruct logs. Filtered values remain masked as [FILTERED], and custom filters can still be configured.
    • Sensitive-data filtering now handles nested and circular values safely, with summaries when values cannot be processed.
  • Documentation

    • Clarified how Rails filters match keys, how filter blocks are handled, and which LogStruct fields are affected.

merge_rails_filter_parameters! copied Rails' filter_parameters into
LogStruct's filters and then cleared Rails' list. Everything else that
reads that list stopped filtering: request.filtered_parameters, Sentry's
and other error reporters' parameter filters, ActionDispatch's request
inspection and ActiveRecord's filter_attributes. Rails' list is now left
in place, and values Rails has already replaced are logged as
"[FILTERED]" instead of a second _filtered summary.

ActiveRecord::Base.filter_attributes was also empty for a second reason:
replace_rails_logger referenced ActiveRecord::Base, ActionController::Base,
ActionMailer::Base, ActiveJob::Base and ActionView::Base during boot to
set their loggers. Loading those classes that early runs their on_load
hooks before the app's initializers, so ActiveRecord copied an empty
filter_parameters into filter_attributes and inspected records showed
passwords and tokens. The loggers are now set from on_load hooks, and the
ActionMailer integration registers its prepends the same way.
process_values stops recursing after 20 levels so circular references
can't loop forever, but it returned the remaining value untouched. Keys
below that depth skipped filtering and string scrubbing, so a password
or email nested deep enough was written to the log as is. The rescue at
the end of process_values had the same problem: any error while
processing a value logged the original value.

Both paths now return a summary of the value (its class and size)
instead. Hash and T::Struct log values were also symbolized with
deep_symbolize_keys, which recursed forever on a circular hash before
process_values ever saw it; symbolize_keys stops at the same depth.

Fixes #15
json 3 rejects the quirks_mode option that ActiveSupport's JSON encoder passes
to JSON.generate in every released Rails version up to 8.0.4, so a freshly
generated test app raised ArgumentError on every to_json call. Rails main
no longer passes it.
LogStruct turned Rails' filter_parameters entries into exact key names,
so keys Rails filters by partial match (:passw filters user_password,
:_key filters stripe_key) were logged in job arguments and logged
Hashes. Symbols, Strings and Regexps now go through
ActiveSupport::ParameterFilter, so they match the keys Rails matches.
Merging again replaces the earlier list.

Partial matches don't apply to the names of LogStruct's own fields at
the top of a line, so Rails' default :_key doesn't filter GoodJob's
cron_key. Exact names and LogStruct's own filters still do, and the
fields' values are filtered as before.

Rails' filter blocks may change the value they're given, as the Rails
guides' example does, so they now get copies instead of the logged
value.

Tests restore the filters after each test, since several change them in
place.
Structs, ActionController::Parameters and other objects were written by
to_json without filtering or scrubbing, so a password in logged params
was logged raw. The formatter now filters what as_json returns.

A Hash that referred back to itself from two keys took exponential time
and wrote a huge line, because only the depth limit stopped it. The
formatter now tracks the Hashes and Arrays it's inside and summarizes a
value that refers back to one of them.
The SQL logging integration checked ActiveRecord::Base for
verbose_query_logs=, which loaded ActiveRecord::Base during setup and
never matched, because the setting is on the ActiveRecord module.
@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: cf5487fe-9b04-4fd2-b312-43e768acdb32
📥 Commits

Reviewing files that changed from the base of the PR and between a356c64 and e2a5bbd.

📒 Files selected for processing: 16
  • .cspell/project-words.txt
  • README.md
  • docs/app/docs/filtering-sensitive-data/page.tsx
  • lib/log_struct/concerns/configuration.rb
  • lib/log_struct/config_struct/filters.rb
  • lib/log_struct/formatter.rb
  • lib/log_struct/integrations/action_mailer.rb
  • lib/log_struct/integrations/active_record.rb
  • lib/log_struct/param_filters.rb
  • lib/log_struct/semantic_logger/setup.rb
  • rails_test_app/create_app.rb
  • rails_test_app/templates/test/integration/filter_parameters_test.rb
  • test/log_struct/configuration_test.rb
  • test/log_struct/formatter_test.rb
  • test/log_struct/integrations/active_record_test.rb
  • test/test_helper.rb
+----------------------------------------------------------------------+
| Your indentation suggests confidence. Your logic suggests otherwise. |
+----------------------------------------------------------------------+
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@ndbroadbent
ndbroadbent merged commit 641cc46 into main Oct 17, 2026
3 of 5 checks passed
@ndbroadbent
ndbroadbent deleted the nathan/filtering-security-fixes branch October 17, 2026 23:10
@ndbroadbent
ndbroadbent restored the nathan/filtering-security-fixes branch October 17, 2026 23:12
@ndbroadbent
ndbroadbent deleted the nathan/filtering-security-fixes branch October 17, 2026 23:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant