feat(logging): FIZZY-3212: filter sensitive arguments from service-call logging (v0.7.0) - #53
Merged
sebscholl merged 4 commits intoAug 15, 2026
Conversation
…ll logging Logger.log_call logged service arguments verbatim, leaking credentials passed as service args (session tokens, OmniAuth hashes, passwords) into application logs — observed live in Machina Console's CloudWatch. Filter args through ActiveSupport::ParameterFilter before logging. New Servus.config.log_filter_parameters defaults to a credential-shaped deny-list; in Rails apps the railtie merges the app's filter_parameters into that default after boot, so gem filtering matches request-log filtering without configuration. The filter is memoized against a copied snapshot so in-place mutations of the list rebuild it. Version 0.7.0.
…g option The railtie's after_initialize hook forced the gem's deny-list: it ran after all app initializers and unconditionally reassigned log_filter_parameters, silently clobbering anything set in config/initializers/servus.rb and making the default keys impossible to opt out of (e.g. args named author or token_count always [FILTERED]). Now the gem imposes nothing: - log_filter_parameters defaults to [] (no filtering); unconfigured behavior is identical to 0.6.0 via a short-circuit in log_call - railtie merge hook and DEFAULT_LOG_FILTER_PARAMETERS removed - recommended defaults (Rails' stock credential list) documented in the initializer example in site/rails/configuration.md; Rails users can assign Rails.application.config.filter_parameters directly - specs reworked for opt-in semantics, plus coverage that arbitrary configured keys are masked as [FILTERED] with key names kept visible Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Config The logger polled the config on every log call — holding a duplicated snapshot of the filter list and comparing it each time — solely to support in-place mutation of log_filter_parameters at runtime, a contract nobody needs. Its two unsynchronized class ivars also allowed a narrow stale-filter race during reconfiguration. Invalidation now lives with the data it derives from: - Config#log_filter_parameters= dups and freezes the assigned list and clears the memoized filter; in-place mutation raises FrozenError instead of silently depending on the logger noticing - Config#parameter_filter memoizes ActiveSupport::ParameterFilter until reassignment (the constant autoloads via the active_support umbrella require in servus.rb, so no explicit require is needed) - Logger.parameter_filter and its snapshot ivars are removed; log_call reads config.parameter_filter directly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rameters Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Servus::Support::Logger.log_calllogs every service call's arguments verbatim (args.inspect). Any app passing credentials as service args leaks them into logs. Observed live in Machina Console's production CloudWatch: rawps_session bearer tokens on every request (Sessions::Resolve) and full Google OAuth auth hashes (access + id tokens) fromSessions::CreateFromOmniauth.Fizzy: https://fizzy.internal.zarhq.dev/0000001/cards/3212
Change
Logger.log_callmasks argument values matchingServus.config.log_filter_parametersviaActiveSupport::ParameterFilterbefore logging →{token: "[FILTERED]"}.log_filter_parametersis a pure, opt-in config option: it defaults to[](no filtering), and Servus never touches it after boot — whatever the app sets inconfig/initializers/servus.rbis authoritative. Accepts allParameterFilternotations (partial-match strings/symbols, regexps, procs). With the default empty list,log_callshort-circuits, so unconfigured behavior is byte-identical to 0.6.0.site/rails/configuration.md) sets the option to Rails' stock credential list (passw email secret token _key crypt salt certificate otp ssn cvv cvc), andsite/features/logging.mdexplains the option. Rails users can simply reuse their app's request-log filtering:config.log_filter_parameters = Rails.application.config.filter_parameters.ActiveSupport::ParameterFilteris memoized onConfigand invalidated by the writer. The assigned list is dup'd and frozen, so in-place mutation (<<) raisesFrozenErrorinstead of silently drifting from the memoized filter — reconfiguring means assigning a new list. Runtime mutation of filter params is explicitly not a supported contract.Design revision
An earlier revision of this PR shipped a gem-level deny-list default and a railtie
config.after_initializehook that merged the app'sfilter_parametersinto it. That hook ran after all app initializers, so it silently clobbered anything set inconfig/initializers/servus.rb, and the deny-list was impossible to opt out of (args named e.g.authorortoken_countwould always log as[FILTERED]). Both are removed — the gem imposes nothing; the defaults are configuration, documented in the initializer example.Adversarial review
Codex challenge review ran against the original diff; both findings addressed:
Configand the writer invalidates it, with the frozen list making in-place mutation an error.Testing
spec/servus/support/logger_spec.rb(7 examples): verbatim logging by default; with a configured list — value filtering, partial-match keys (raw_token,password), wholesale nested filtering, non-matching keys stay visible, reassignment applied on the next call; arbitrary custom keys (wand,sigil) masked as[FILTERED]with key names kept visible.spec/servus/config_spec.rb(#log_filter_parameters, 4 examples): empty default, customization, frozen-list contract (<<raisesFrozenError), memoized filter rebuilt on reassignment.Release
After merge: publish GitHub Release
v0.7.0(triggers the RubyGems publish workflow). Machina Console then bumps0.2.1 → 0.7.0(FIZZY-3211), addsconfig.log_filter_parametersto itsconfig/initializers/servus.rb(filtering is opt-in — without it, args log verbatim), and deletes its app-level override initializer.🤖 Generated with Claude Code