Information Disclosure

MEDIUM
rails/rails
Commit: f9243afd51d2
Affected: 8.1.0 - 8.1.3 (inclusive)
2026-04-11 22:40 UTC

Description

The commit adds an optimization in ActiveSupport::ParameterFilter to detect anchored exact-match regular expressions and treat those keys as exact matches to be masked. Specifically, it introduces extract_exact_key to derive a literal key from anchored patterns like /^token$/ or \Atoken\z and stores these in an @exact_keys hash. During filtering, if a key exactly matches one of these literals, or if a general regex matches the key, the value is masked. This reduces the risk of information disclosure by ensuring that sensitive parameter names that are intended to be masked (even when specified with anchored exact-match regex) are reliably hidden across both top-level and nested parameter structures. The change also adds a test for anchored exact-match regexp filters to verify the behavior.

Commit Details

Author: Jean Boussier

Date: 2026-04-10 12:13 UTC

Message:

Merge pull request #57166 from alexcwatt/optimize-parameter-filter-exact-key-lookup Use hash lookup for exact-match regexp filters in ParameterFilter

Triage Assessment

Vulnerability Type: Information Disclosure

Confidence: MEDIUM

Reasoning:

The commit introduces an optimization to ParameterFilter that detects anchored exact-match regular expressions and treats those keys as exact matches to be masked. This ensures sensitive parameter names (e.g., tokens, passwords) are reliably hidden, addressing potential information disclosure via parameter leakage when using regex-based filters. It enhances security by reducing the risk of leaking sensitive data through parameter filtering, not just performance improvements.

Verification Assessment

Vulnerability Type: Information Disclosure

Confidence: MEDIUM

Affected Versions: 8.1.0 - 8.1.3 (inclusive)

Code Diff

diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb index 7558c87519d0..b4e97c6c45a3 100644 --- a/activesupport/lib/active_support/parameter_filter.rb +++ b/activesupport/lib/active_support/parameter_filter.rb @@ -96,6 +96,18 @@ def filter_param(key, value) end private + # If the regexp is an anchored exact match like /^token$/ or /\Atoken\z/, + # returns the literal string. + def extract_exact_key(regexp) # :nodoc: + return if regexp.casefold? + source = regexp.source + return unless source.start_with?("^", "\\A") && source.end_with?("$", "\\z") + + literal = source.delete_prefix("^").delete_prefix("\\A") + .delete_suffix("$").delete_suffix("\\z") + literal if literal.match?(/\A[a-zA-Z0-9_]+\z/) + end + def compile_filters!(filters) @no_filters = filters.empty? return if @no_filters @@ -103,6 +115,7 @@ def compile_filters!(filters) @regexps, strings = [], [] @deep_regexps, deep_strings = nil, nil @blocks = nil + @exact_keys = nil filters.each do |item| case item @@ -111,6 +124,8 @@ def compile_filters!(filters) when Regexp if item.to_s.include?("\\.") (@deep_regexps ||= []) << item + elsif (literal = extract_exact_key(item)) + (@exact_keys ||= {})[literal] = true else @regexps << item end @@ -139,11 +154,15 @@ def call(params, full_parent_key = nil, original_params = params) end def value_for_key(key, value, full_parent_key = nil, original_params = nil) + key_s = key.to_s + if @deep_regexps - full_key = full_parent_key ? "#{full_parent_key}.#{key}" : key.to_s + full_key = full_parent_key ? "#{full_parent_key}.#{key_s}" : key_s end - if @regexps.any? { |r| r.match?(key.to_s) } + if @exact_keys && @exact_keys[key_s] + value = @mask + elsif @regexps.any? { |r| r.match?(key_s) } value = @mask elsif @deep_regexps&.any? { |r| r.match?(full_key) } value = @mask diff --git a/activesupport/test/parameter_filter_test.rb b/activesupport/test/parameter_filter_test.rb index a797165714a0..0a320e8c2bcc 100644 --- a/activesupport/test/parameter_filter_test.rb +++ b/activesupport/test/parameter_filter_test.rb @@ -122,6 +122,12 @@ class ParameterFilterTest < ActiveSupport::TestCase end end + test "anchored exact-match regexp filters" do + parameter_filter = ActiveSupport::ParameterFilter.new([/^token$/, /\Astate\z/, /password/]) + result = parameter_filter.filter("token" => "t0k3n", "state" => "abc", "password" => "pass", "user_password" => "pass", "not_secret" => "visible") + assert_equal({ "token" => "[FILTERED]", "state" => "[FILTERED]", "password" => "[FILTERED]", "user_password" => "[FILTERED]", "not_secret" => "visible" }, result) + end + test "precompile_filters" do patterns = [/A.a/, /b.B/i, "ccC", :ddD] keys = ["Aaa", "Bbb", "Ccc", "Ddd"]
← Back to Alerts View on GitHub →