# RuboCop — CWE-407 Disclosure Brief
**2026-03-27 · Patch available — awaiting upstream merge**

## Finding

Two O(n²) defects in RuboCop's `IgnoredNode` mixin and `RedundantSelf` cop. Both patched. Patches ready for upstream review. RuboCop is the dominant Ruby static analysis tool — used in virtually every Ruby/Rails CI pipeline.

## The Defects

**rubocop-0001 (PATCHED — MEDIUM):** `lib/rubocop/cop/mixin/ignored_node.rb:32`

```ruby
# IgnoredNode mixin — included in every cop via Cop::Base:
def initialize(*)
    super
    @ignored_nodes = []   # plain Array
end

def ignore_node(node)
    @ignored_nodes << node
end

def part_of_ignored_node?(node)
    @ignored_nodes.any? { |ignored| ignored.equal?(node) ||
                                    node.each_ancestor.include?(ignored) }
    # Array.any? — O(R) scan for R ignored nodes
end
```

`@ignored_nodes` is a plain Ruby Array. `@ignored_nodes.any?` scans linearly for every `on_str` and `on_sym` node encountered. In a Ruby file with many string literals and active cops that use `IgnoredNode`, this fires O(R × S) times where R = ignored nodes, S = string/symbol nodes.

**rubocop-0002 (PATCHED — LOW):** `lib/rubocop/cop/style/redundant_self.rb:62`

```ruby
# In RedundantSelf cop:
def initialize(*)
    super
    @allowed_send_nodes = []   # plain Array
end

def allow_send_node?(node)
    @allowed_send_nodes.include?(node.method_name)  # O(A) per on_send call
end
```

`@allowed_send_nodes` is a plain Array. `include?` is O(A) per `on_send` callback. Called for every method call in the file. **O(A × M)** where A = allowed nodes, M = method calls.

## Complexity Proof

**rubocop-0001:** For R ignored nodes and S string/symbol nodes per file:
- Per node: O(R) `Array.any?` scan
- Total per file: **O(R × S)**

In large Ruby files with many string literals (template files, configuration DSLs, i18n files): R grows as cops accumulate ignored regions; S grows with file size. **100× op reduction** with `Set.new.compare_by_identity`.

**rubocop-0002:** For A allowed nodes and M method calls:
- Per call: O(A) `Array.include?` scan
- Total per file: **O(A × M)**

Fix: `Set.new` for both.

## Impact

RuboCop is used in virtually every Ruby and Rails CI pipeline. It runs on every file on every push for code style enforcement. Large Rails applications with thousands of files, run through RuboCop on every CI commit, pay this overhead on every file analysis.

rubocop-0001 is particularly significant: `IgnoredNode` is included in `Cop::Base`, which is the base class for every RuboCop cop. Every cop that uses `ignore_node` (multiple built-in cops do) creates an `@ignored_nodes` Array. For files with many string literals (common in Rails view helpers, i18n YAML loaders, RSpec test files), this fires repeatedly per node per active cop.

The `IgnoredNode` mixin is performance-sensitive — it fires for every node of certain types in every analyzed file.

## The Fix

**rubocop-0001:** Replace Array with `Set.new.compare_by_identity`:

```ruby
# Before
@ignored_nodes = []

# After
# CWE-407 fix: Set with identity comparison for O(1) include? instead of O(R) Array scan.
require 'set'
@ignored_nodes = Set.new.compare_by_identity
```

`compare_by_identity` uses object identity (`equal?`) for set membership — matching the existing `ignored.equal?(node)` semantics exactly.

**rubocop-0002:** Replace Array with `Set.new`:

```ruby
# Before
@allowed_send_nodes = []

# After
# CWE-407 fix: Set for O(1) include? instead of O(A) Array scan.
@allowed_send_nodes = Set.new
```

## Patch

Fix available: `defects/rubocop/patch/rubocop-0001-0002-ignored-nodes-set.patch`

Two-location patch across `cop/mixin/ignored_node.rb` and `cop/style/redundant_self.rb`.

Unit test: rubocop-0001 O(R×S) → O(S) growth confirmed. **Significant speedup on large files with many string literals.** rubocop-0002: O(A×M) → O(M).

## What We Ask

A patch is ready for review.

1. Confirm receipt and assign a GitHub issue reference (rubocop/rubocop).
2. Assess severity — rubocop-0001 fires for every string/symbol node in every file; the `IgnoredNode` mixin affects all cops that track ignored regions.
3. Coordinate a disclosure date — we are targeting 90 days from first contact.
4. We will credit the RuboCop team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.
