# sinatra-0001: CWE-407 — `content_type` iterates `add_charset` Array on every response | Field | Value | |-------------|-------| | ID | sinatra-0001 | | Project | sinatra/sinatra | | Severity | MEDIUM | | Status | OPEN | | CWE | CWE-407 (Algorithmic Complexity) | | Found | 2026-03-27 | ## Location `lib/sinatra/base.rb:392` ```ruby def content_type(type = nil, params = {}) ... unless params.include?(:charset) || settings.add_charset.all? { |p| !(p === mime_type) } params[:charset] = params.delete('charset') || settings.default_encoding end ``` ## Root Cause `settings.add_charset` is an Array set at line 1950-1951: ```ruby set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } settings.add_charset << %r{^text/} ``` This Array contains both strings and Regexps. For each call to `content_type`, the code calls `Array#all?` iterating every element and evaluating `p === mime_type` (which for Regexp is a match). This is O(k) per response where k = length of `add_charset`. `content_type` is called on virtually every response (Sinatra sets it in helpers, in `send_file`, in template rendering, etc.). With the default configuration k=4, but users can extend the array to arbitrary length. ## Hot Path `dispatch!` → (template render / json / etc.) → `content_type` → O(k) scan. Called once per request at minimum, potentially multiple times per request. ## Fix Since `add_charset` supports both String equality and Regexp match via `===`, a pure `Set` won't help here (Regexp `===` can't be O(1)-indexed). The practical fix is to split the list into a `Set` for exact matches and a separate `Array` for pattern matches, checking the Set first (O(1)) and only falling through to Regexp scan on miss: ```ruby # In configure block or as a helper: add_charset_strings = Set.new(settings.add_charset.select { |p| p.is_a?(String) }) add_charset_patterns = settings.add_charset.select { |p| p.is_a?(Regexp) } unless params.include?(:charset) || (!add_charset_strings.include?(mime_type) && add_charset_patterns.none? { |p| p === mime_type }) params[:charset] = ... end ``` For the common case (all strings, short list) this is micro-opt. For the Regexp case this is unchanged. The bigger win is freezing the set at app startup rather than re-scanning per request. ## Patch `defects/sinatra/patch/sinatra-0001-content-type-add-charset-set.patch` ## Benchmark `defects/sinatra/unit/SinatraTest.java`