CVE-2025-55193 Overview
CVE-2025-55193 affects Active Record, the Object-Relational Mapping (ORM) component of Ruby on Rails. The vulnerability occurs when the ID passed to find or similar lookup methods is logged without escaping. When the resulting ActiveRecord::RecordNotFound error message is written directly to a terminal, attacker-controlled input may include unescaped ANSI escape sequences. This condition maps to [CWE-150] (Improper Neutralization of Escape, Meta, or Control Sequences). The issue is patched in Active Record versions 7.1.5.2, 7.2.2.2, and 8.0.2.1.
Critical Impact
An attacker can inject ANSI control sequences into terminal-rendered Rails log output, potentially manipulating displayed content, hiding activity, or spoofing operator-facing messages.
Affected Products
- Active Record versions prior to 7.1.5.2 in the 7.1.x series
- Active Record versions prior to 7.2.2.2 in the 7.2.x series
- Active Record versions prior to 8.0.2.1 in the 8.0.x series
Discovery Timeline
- 2025-08-13 - CVE-2025-55193 published to NVD
- 2026-06-17 - Last updated in NVD database
Technical Details for CVE-2025-55193
Vulnerability Analysis
Active Record raises RecordNotFound when methods such as find cannot locate a record. The error message is constructed by interpolating the primary key value directly into a string, for example "Couldn't find User with 'id'=#{id}". Because Ruby's default string interpolation calls to_s rather than inspect, non-printable bytes, including ANSI escape sequences beginning with the ESC character (\\x1b), pass through unchanged. When this message is logged and the log stream is a terminal, the terminal interprets the escape sequences as formatting or cursor control commands.
This is a log injection issue, not a code execution flaw. An attacker who can submit a crafted ID value to a Rails controller can cause operator log output to contain arbitrary color changes, cursor moves, screen clears, or forged text lines that appear legitimate to an administrator reviewing logs in a shell session.
Root Cause
The root cause is unsafe string formatting of untrusted input in error construction paths within activerecord/lib/active_record/core.rb and activerecord/lib/active_record/relation/finder_methods.rb. The affected code interpolated id and ids values using implicit to_s conversion, which preserves control bytes. The fix routes those values through inspect, which produces a safely quoted and escaped Ruby literal representation.
Attack Vector
The attack vector is network-accessible: any endpoint that passes user-supplied identifiers into an Active Record find (for example, Model.find(params[:id])) can trigger the vulnerable error path. The attacker submits an ID containing ANSI escape sequences. When the resulting RecordNotFound is logged to a terminal-attached log destination, the escape sequences are rendered by the terminal.
# Security patch in activerecord/lib/active_record/core.rb
# Call inspect on ids in RecordNotFound error
return super if StatementCache.unsupported_value?(id)
cached_find_by([primary_key], [id]) ||
- raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id))
end
def find_by(*args) # :nodoc:
# Source: https://github.com/rails/rails/commit/3beef20013736fd52c5dcfdf061f7999ba318290
# Security patch in activerecord/lib/active_record/relation/finder_methods.rb
# Call inspect on ids in RecordNotFound error
error << " with#{conditions}" if conditions
raise RecordNotFound.new(error, name, key)
elsif Array.wrap(ids).size == 1
- error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
+ id = Array.wrap(ids)[0]
+ error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
else
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
- error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
- error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
+ error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
+ error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
raise RecordNotFound.new(error, name, key, ids)
end
end
# Source: https://github.com/rails/rails/commit/3beef20013736fd52c5dcfdf061f7999ba318290
Routing values through inspect produces representations like "\e[31mattacker\e[0m", neutralizing terminal interpretation.
Detection Methods for CVE-2025-55193
Indicators of Compromise
- Log lines containing raw ESC bytes (\\x1b / 0x1B) or CSI sequences (for example \\x1b[) inside RecordNotFound error messages.
- Repeated ActiveRecord::RecordNotFound entries where the interpolated id value contains non-alphanumeric or non-printable characters.
- Request logs with URL-encoded escape sequences (%1B, %1b) in path or query parameters targeting resource lookup endpoints.
Detection Strategies
- Grep or query centralized logs for the byte pattern \\x1b\[ co-occurring with RecordNotFound or Couldn't find.
- Add a log-ingest parser rule that flags any application log line containing control characters outside tab, carriage return, and line feed.
- Review web access logs for suspicious id parameter values containing encoded ANSI or shell control characters, especially against controllers that call find.
Monitoring Recommendations
- Ship Rails production logs to a structured backend (JSON or OCSF) where control bytes are escaped at write time rather than rendered.
- Alert on unusual spikes in RecordNotFound exceptions that could indicate probing for injection payloads.
- Verify installed gem versions across the fleet by inventorying Gemfile.lock entries for the activerecord gem.
How to Mitigate CVE-2025-55193
Immediate Actions Required
- Upgrade Active Record to 7.1.5.2, 7.2.2.2, or 8.0.2.1 depending on the Rails branch in use.
- Audit application controllers for direct use of Model.find(params[:id]) and similar lookups exposed to untrusted input.
- Route production logs through a non-terminal sink (file, syslog, or SIEM collector) that does not interpret ANSI sequences.
Patch Information
The fix is delivered in Active Record 7.1.5.2, 7.2.2.2, and 8.0.2.1. The patch calls inspect on id and ids values before interpolating them into the RecordNotFound message, ensuring control characters are represented as escaped Ruby string literals. See the GitHub Security Advisory GHSA-76r7-hhxj-r776 and commits 3beef20, 568c0bc, and 6a944ca.
Workarounds
- Coerce or validate identifiers before passing them to Active Record finders, for example by calling Integer(params[:id]) or applying a strict regex.
- Wrap RecordNotFound handling in a custom rescue that sanitizes control characters from messages before logging.
- Avoid attaching production log streams to interactive terminals; use file-based or structured logging with escape-byte stripping.
# Update Active Record via Bundler
bundle update activerecord
# Or pin the patched version in Gemfile, then bundle install
# gem "rails", "~> 8.0.2.1"
# gem "rails", "~> 7.2.2.2"
# gem "rails", "~> 7.1.5.2"
# Verify installed version
bundle info activerecord | grep -i version
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

