java-topology/docs/tickets/rails-0009-filter-attribute-handler-filter-params-array.md
russell@unturf.com 547a9f5738 ORM wave 2: 10 new defects — Active Record +3, Exposed +3, SeaORM +4 (167 sites, 64 ecosystems)
rails-0009: FilterAttributeHandler filter_parameters Array O(A×F) → Set (450×)
rails-0010: Encryption::AutoFilteredParameters two Array scans → Set (250×)
rails-0011: TimeZoneConversion skip_list Array O(M×C×S) → Set (20×)

exposed-0001: SchemaUtilityApi mapMissingColumnStatements O(N×M) → map (118×)
exposed-0002: IdentifierManagerApi isAKeyword O(K) linear → HashSet (144×)
exposed-0003: Table.clone consParams.map fresh List → hoisted HashSet (6×)

seaorm-0001: active_model establish_links leftover.any O(N²) → HashSet (501×)
seaorm-0002: rbac engine group_permissions .values().find() → HashMap by ID (502×)
seaorm-0003: schema builder sorted_tables Vec::contains → HashSet (500×)
seaorm-0004: TopologicalSort from_iter seen Vec O(N²) → BTreeSet (28×)

Unit tests: RailsTest 11/11, ExposedTest 3/3, SeaORMTest 4/4 PASS
Whitepaper: 157→167 sites, 62→64 ecosystems; §13.12 ORM Wave 2 added
2026-03-27 13:49:46 -04:00

1.9 KiB
Raw Blame History

rails-0009: FilterAttributeHandler — O(A×F) filter_parameters Array#include? per sensitive attribute

Severity: MEDIUM File: activerecord/lib/active_record/filter_attribute_handler.rb Line: 69 Status: PATCHED

Description

FilterAttributeHandler#apply_filter is called once per sensitive attribute per model class during app initialization. Inside the loop it calls:

app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)

app.config.filter_parameters is an Array (initialized as [] in railties). Each .include?(filter) call is O(F) where F is the current size of filter_parameters. With A total sensitive attributes across all models and F growing as attributes are added, total cost is O(A×F) — quadratic in the number of sensitive attributes declared across the application.

This is the same structural pattern as rails-0010 (encryption variant) and runs at every app boot for all models using has_secure_password, attr_encrypted, or manual filter_attributes declarations.

Root Cause

filter_parameters is a plain Array. The deduplication guard include? is O(F) per insertion rather than using a Set or Hash for O(1) membership.

Fix

Pre-build a Set mirror of filter_parameters for O(1) membership checks, or change the underlying storage to a Set:

# BEFORE (filter_attribute_handler.rb:69)
app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)

# AFTER
existing = app.config.filter_parameters.to_set
list.each do |attribute|
  next if klass.abstract_class? || klass == Base
  klass_name = klass.name ? klass.model_name.element : nil
  filter = [klass_name, attribute.to_s].compact.join(".")
  unless existing.include?(filter)
    app.config.filter_parameters << filter
    existing << filter
  end
end

Speedup

~10x at A=500 total attributes, F=200 existing filter_parameters