Nov 12, 2026·10 min read·1 visit
Active Storage prior to Rails 8.0.5.2 and 8.1.4.1 passes uploaded media files to ffmpeg and ffprobe without the -format_whitelist argument. Attackers can upload an HLS (.m3u8) playlist masquerading as a video or audio file to read local media files via the file:// protocol and leak their contents through generated thumbnail previews or metadata records.
CVE-2026-126188 (GHSA-3f4q-8x5m-76v8) is an information disclosure vulnerability in the Active Storage component of Ruby on Rails prior to versions 8.0.5.2 and 8.1.4.1. When generating previews or extracting metadata from uploaded media files, Active Storage invokes the external command-line tools ffmpeg and ffprobe without specifying a format whitelist. An attacker who can upload a crafted HTTP Live Streaming (HLS) playlist while declaring a standard video or audio MIME type can cause FFmpeg to engage the HLS demuxer, resolve file:// segment URIs, and disclose visual frames or metadata from local media files readable by the Rails application process.
Active Storage is the built-in framework in Ruby on Rails for facilitating file uploads to cloud storage services or local disks and attaching those files to Active Record objects. Beyond raw storage, Active Storage provides automated post-processing capabilities for media files. Specifically, ActiveStorage::Previewer::VideoPreviewer generates thumbnail images from video attachments, while ActiveStorage::Analyzer::VideoAnalyzer and ActiveStorage::Analyzer::AudioAnalyzer inspect uploaded media blobs to extract technical metadata such as duration, dimensions, bitrates, and stream attributes.
To perform these operations, Active Storage shells out to the ffmpeg and ffprobe binaries installed on the host operating system. Prior to Ruby on Rails versions 8.0.5.2 and 8.1.4.1, these external commands were executed directly on the downloaded temporary blob file without constraining which container formats or demuxers FFmpeg was allowed to initialize. Consequently, FFmpeg relied on its default content-probing behavior to inspect the binary signature of the uploaded file rather than enforcing that the file matched a safe container format.
This behavior creates an exposure of sensitive information (CWE-200) when an application accepts untrusted media uploads—particularly via Active Storage's direct upload mechanism, where the client supplies the declared content_type. An attacker can upload a text-based HTTP Live Streaming (HLS) playlist file (#EXTM3U) while declaring its MIME type as video/mp4 or audio/mpeg. When Active Storage passes this blob to ffmpeg or ffprobe, FFmpeg's format probing selects the hls demuxer.
Once engaged, the hls demuxer parses segment URIs listed inside the playlist, which supports the file:// URI scheme. If the attacker knows or can predict the filesystem path of a local media file readable by the Rails application process, FFmpeg opens that local file as a media segment. Depending on which Active Storage operation is triggered, the server either renders a visual frame from the local file into an attacker-accessible preview image or extracts the local file's audio/video stream metadata into the attachment's database record.
The root cause of CVE-2026-126188 is the absence of explicit demuxer restrictions at the boundary where Active Storage constructs command-line arguments for ffmpeg and ffprobe. By design, FFmpeg supports hundreds of container formats, playlist structures, and network/filesystem protocol handlers. When invoked with -i <input_file> (in ffmpeg) or <input_file> (in ffprobe) without the -format_whitelist flag, libavformat reads the leading bytes of the input file and scores registered demuxers based on magic bytes and structural markers.
In Active Storage, determining whether a blob is eligible for video previewing or media analysis depends on the blob's content_type attribute. For instance, ActiveStorage::Previewer::VideoPreviewer.accept? checks if the blob's content type matches a video MIME type supported by the application. When using Active Storage direct uploads (/rails/active_storage/direct_uploads), the client sends a JSON payload specifying the filename, byte_size, checksum, and content_type before uploading the raw file bytes directly to the storage service. If server-side MIME type sniffing is bypassed or disabled (such as setting identify: false or relying on client-provided metadata during direct upload), the blob retains the attacker-controlled content_type.
When the application subsequently requests a preview variant or executes the background analysis job (ActiveStorage::AnalyzeJob), Active Storage downloads the blob to a local temporary file and invokes ffmpeg or ffprobe. Even though the blob is marked as video/mp4 in the database, libavformat inspects the file's actual contents, detects the #EXTM3U header, and initializes the hls demuxer.
Within FFmpeg, the hls demuxer acts as a meta-container that sequentially opens underlying media segments specified in the playlist body. By default, FFmpeg permits the hls demuxer to use the file protocol handler. When the playlist contains an entry such as file:///var/data/internal_recording.mp4, FFmpeg opens that path on the server's local filesystem, decodes its stream headers or video frames, and returns the output to Active Storage.
Prior to the security patch, three distinct classes in the activestorage gem invoked FFmpeg utilities without format restrictions: ActiveStorage::Previewer::VideoPreviewer, ActiveStorage::Analyzer::VideoAnalyzer, and ActiveStorage::Analyzer::AudioAnalyzer. In VideoPreviewer, the draw_relevant_frame_from method constructed the ffmpeg command by interpolating ActiveStorage.video_preview_arguments directly after -i file.path. Similarly, both analyzer classes executed ffprobe with JSON output flags and passed file.path directly as the final positional argument.
To remediate the vulnerability in commits 3b1f2627538dc597cd973530c08514a26c2b0282 (Rails 8.1) and 69aff80d115bbb9d1e82fd6c6f2fd3ddeafb958b (Rails 8.0), the Rails maintainers introduced a module-level configuration attribute ffmpeg_allowed_formats in activestorage/lib/active_storage.rb alongside a helper method ffmpeg_format_whitelist_arguments:
# activestorage/lib/active_storage.rb
mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"
# Define a strict default allowlist of standard container and audio formats,
# explicitly excluding playlist/reference demuxers like 'hls' or 'concat'.
mattr_accessor :ffmpeg_allowed_formats, default: %w[ mov matroska avi mpeg mpegts flv asf ogg mp3 wav flac aac aiff caf ].freeze
def self.ffmpeg_format_whitelist_arguments # :nodoc:
# Construct the -format_whitelist CLI flag for ffmpeg/ffprobe unless explicitly set to nil
[ "-format_whitelist", Array(ffmpeg_allowed_formats).join(",") ] unless ffmpeg_allowed_formats.nil?
endIn ActiveStorage::Previewer::VideoPreviewer, the patch splats *ActiveStorage.ffmpeg_format_whitelist_arguments immediately before the -i input flag. In FFmpeg's command-line parser, options affecting input demuxing must precede the -i argument to bind to the input context:
# activestorage/lib/active_storage/previewer/video_previewer.rb
def draw_relevant_frame_from(file, &block)
# BEFORE:
# draw self.class.ffmpeg_path, "-i", file.path, *Shellwords.split(ActiveStorage.video_preview_arguments), "-", &block
# AFTER: Enforce -format_whitelist prior to reading input file
draw self.class.ffmpeg_path, *ActiveStorage.ffmpeg_format_whitelist_arguments, "-i", file.path, *Shellwords.split(ActiveStorage.video_preview_arguments), "-", &block
endThe exact same argument array is injected into ActiveStorage::Analyzer::VideoAnalyzer and ActiveStorage::Analyzer::AudioAnalyzer within their respective probe_from methods. Additionally, the patch adds a warning log when ffprobe exits with a non-zero status so administrators can identify when legitimate uploads are rejected due to format restrictions:
# activestorage/lib/active_storage/analyzer/video_analyzer.rb
IO.popen([
self.class.ffprobe_path,
"-print_format", "json",
"-show_streams",
"-show_format",
"-v", "error",
*ActiveStorage.ffmpeg_format_whitelist_arguments, # Restrict allowed demuxers
file.path
]) do |output|
JSON.parse(output.read)
end.tap do
logger.warn "Video analysis failed because ffprobe exited with status #{$?.exitstatus}. The file may be corrupt, or in a format outside config.active_storage.ffmpeg_allowed_formats." unless $?.success?
end> [!NOTE]
> Assessment of Fix Completeness: Enforcing -format_whitelist at the libavformat layer is effective at preventing FFmpeg from initializing playlist and reference-based demuxers such as hls, concat, dash, or sdp. Because libavformat validates the probed format against the comma-separated whitelist before invoking the demuxer's read_header callback, playlist segment resolution is blocked entirely. However, if an application administrator overrides config.active_storage.ffmpeg_allowed_formats to nil or manually adds hls or concat to the array, the protection is bypassed.
Exploiting CVE-2026-126188 requires an application endpoint that accepts file uploads via Active Storage and subsequently performs either video preview generation or media analysis on the attachment. The attacker does not need authentication if the upload endpoint is publicly accessible, though in many applications upload functionality is restricted to authenticated users. Crucially, the attacker must also know or predict the exact filesystem path of a local media file on the server that the Rails user account has read permissions to access.
To begin the attack, the adversary constructs a minimal HLS M3U8 playlist referencing the target local file via the file:// URI scheme. The target file must be in a media or image format that FFmpeg is capable of decoding into video frames or audio/video streams (for example, .mp4, .mov, .mkv, .mp3, or image files that FFmpeg can treat as single-frame video streams):
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:10,
file:///var/app/current/storage/internal/confidential_meeting.mp4
#EXT-X-ENDLISTNext, the attacker initiates an upload request, declaring the content_type as video/mp4 so that Active Storage routes the blob to VideoPreviewer and VideoAnalyzer:
POST /rails/active_storage/direct_uploads HTTP/1.1
Host: vulnerable-rails-app.example.com
Content-Type: application/json
{
"blob": {
"filename": "presentation.mp4",
"content_type": "video/mp4",
"byte_size": 109,
"checksum": "1B2M2Y8AsgTpgAmY7PhCfg=="
}
}After uploading the M3U8 payload to the signed URL returned by the direct upload endpoint and attaching the signed blob ID to a model record, the application either queues ActiveStorage::AnalyzeJob or renders a preview URL in a view template (e.g., url_for(record.attachment.preview(resize_to_limit: [640, 480]))). When the preview URL is requested, VideoPreviewer executes ffmpeg -i /tmp/ActiveStorage-... -y -vframes 1 -f image2 -. FFmpeg parses the M3U8 playlist, reads the first frame from /var/app/current/storage/internal/confidential_meeting.mp4, encodes it as a JPEG or PNG image, and serves it back to the attacker.
The official Rails security patch includes unit tests in activestorage/test/previewer/video_previewer_test.rb and activestorage/test/analyzer/video_analyzer_test.rb that demonstrate this exact reproduction mechanism:
def create_playlist_blob(referencing:, filename:, content_type:)
playlist = <<~M3U8
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:10,
file://#{file_fixture(referencing).realpath}
#EXT-X-ENDLIST
M3U8
create_blob(data: playlist, filename: filename, content_type: content_type, identify: false)
endThe security impact of CVE-2026-126188 is limited to unauthorized local information disclosure, reflected in its CVSS v3.1 base score of 3.7 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N). The vulnerability does not permit arbitrary file read of non-media files such as /etc/passwd, database configuration files (config/database.yml), or Ruby source code, because VideoPreviewer only outputs decoded visual frames and VideoAnalyzer/AudioAnalyzer only extract structured stream metadata (duration, dimensions, codec tags) parsed by ffprobe.
However, the confidentiality impact (C:L) is tangible in multi-tenant applications or deployments that store sensitive attachments locally on disk (using the Disk storage service in config/storage.yml). Although Active Storage generates random keys for blobs stored on disk, an attacker who discovers internal filesystem paths through a separate path disclosure bug—or who targets predictable temporary files in /tmp or shared mounted volumes—can extract visual frames from other users' private videos or documents.
Furthermore, if the attacker controls the FFmpeg preview arguments or chains multiple media segments in the M3U8 playlist, they can probe for the existence of local media files on the server. When a referenced file:// path exists and contains valid media, VideoAnalyzer populates blob.metadata with width, height, and duration. When the file does not exist, ffprobe fails and leaves those metadata keys empty, providing a side-channel oracle for verifying local media file paths.
According to FIRST EPSS metrics, the vulnerability carries a low exploitation probability of 0.28% (20.62nd percentile), and it is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. The high attack complexity (AC:H) stems from the requirement to know exact local media paths and the constraint that only files decodable by FFmpeg can have their contents or metadata leaked.
To remediate CVE-2026-126188, upgrade Ruby on Rails (rails and activestorage gems) to version 8.0.5.2 (for the 8.0 release branch) or 8.1.4.1 (for the 8.1 release branch) or later. These releases enforce -format_whitelist across all ffmpeg and ffprobe invocations by default, restricting FFmpeg to the following container formats: mov, matroska, avi, mpeg, mpegts, flv, asf, ogg, mp3, wav, flac, aac, aiff, and caf.
After upgrading, audit the application's media processing requirements. If the application legitimately processes less common container formats that are excluded from the default allowlist—such as Material Exchange Format (mxf), Digital Video (dv), or WavPack (wv)—append only the required demuxer identifiers in config/application.rb:
# config/application.rb
config.active_storage.ffmpeg_allowed_formats += %w[ mxf dv ]> [!WARNING]
> Never add playlist or reference demuxers (such as hls, concat, or dash) to config.active_storage.ffmpeg_allowed_formats, and never set config.active_storage.ffmpeg_allowed_formats = nil in environments that process untrusted uploads. Doing so disables the format whitelist and reintroduces the vulnerability.
For applications that cannot immediately upgrade to Rails 8.0.5.2 or 8.1.4.1, apply an initializer patch (config/initializers/active_storage_ffmpeg_whitelist.rb) that prepends -format_whitelist to ActiveStorage.video_preview_arguments and overrides probe_from in ActiveStorage::Analyzer::VideoAnalyzer and ActiveStorage::Analyzer::AudioAnalyzer:
# config/initializers/active_storage_ffmpeg_whitelist.rb
Rails.application.config.after_initialize do
allowed_formats = "mov,matroska,avi,mpeg,mpegts,flv,asf,ogg,mp3,wav,flac,aac,aiff,caf"
ActiveStorage::Previewer::VideoPreviewer.prepend(Module.new do
private
def draw_relevant_frame_from(file, &block)
draw self.class.ffmpeg_path, "-format_whitelist", allowed_formats, "-i", file.path, *Shellwords.split(ActiveStorage.video_preview_arguments), "-", &block
end
end)
endCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
activestorage (Ruby on Rails) Rails | < 8.0.5.2 | 8.0.5.2 |
activestorage (Ruby on Rails) Rails | >= 8.1.0, < 8.1.4.1 | 8.1.4.1 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-126188 / GHSA-3f4q-8x5m-76v8 |
| Weakness Class | CWE-200: Exposure of Sensitive Information to an Unauthorized Actor |
| Affected Component | Active Storage (VideoPreviewer, VideoAnalyzer, AudioAnalyzer) |
| Attack Vector | Network (Crafted HLS .m3u8 Direct Upload with file:// Segment URIs) |
| CVSS v3.1 Score | 3.7 Low (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N) |
| EPSS Score | 0.28% (20.62nd Percentile) |
| Impact | Unauthorized disclosure of local media frames and metadata readable by the Rails process |
| Patched Versions | Rails 8.0.5.2, Rails 8.1.4.1 |
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
CVE-2026-126206 (GHSA-jcff-cj73-4w4f) is a Cross-Site Scripting (XSS) vulnerability in version 2.25.2 of the Loofah Ruby gem. The vulnerability stems from an incomplete character reference normalization routine in the `Loofah::HTML5::Scrub.allowed_uri?` method, which fails to decode the HTML5 named character reference `/` (representing the solidus character `/`). Consequently, a crafted `data:` URI such as `data:text/html,<script>alert(1)</script>` bypasses mediatype validation by triggering the WHATWG MIME parser fallback to `text/plain`, while web browsers later resolve `/` to `/` and execute the payload as `text/html`.
pgEdge RAG Server versions prior to 2.0.0 contain a race condition in the hybrid search pipeline orchestrator (CWE-362, CWE-488). The server maintains a single shared, in-memory BM25 index instance across all incoming HTTP requests on a pipeline. Concurrent queries to the unauthenticated HTTP REST API interleave non-atomic clear, document addition, and search operations on this singleton index. As a result, documents fetched using one caller's search filter can be queried and returned to another concurrent caller, bypassing multi-tenant data isolation without generating any anomalous audit trail in server or database logs.
CVE-2026-118740 is a Missing Authorization (CWE-862) vulnerability in the Microsoft Aspire Dashboard and Aspire.Dashboard.Sdk.linux-x64 packages. In versions 13.6.1 prior to 13.6.5, an architectural flaw in the Blazor routing configuration (Routes.razor) causes unmapped (404 / Not-Found) routes to bypass the AuthorizeRouteView component. By navigating to an arbitrary non-existent route, an unauthenticated remote attacker forces the application into the NotFound template, which unconditionally renders the primary MainLayout component and initializes global user interface services, interaction providers, and underlying SignalR/Blazor circuits without verifying authentication credentials.
An elevation of privilege vulnerability exists in .NET Aspire versions prior to 13.6.5 due to the insertion of sensitive information into sent data (CWE-201). When the Developer Control Plane (DCP) container tunneling feature is enabled, the orchestration engine implicitly exposes all endpoints of the Aspire Dashboard—including its administrative frontend and management APIs—to containerized workloads instead of restricting tunnel access solely to OpenTelemetry Protocol (OTLP) collection endpoints. This exposure allows an attacker operating within a compromised or untrusted container to query the dashboard frontend, extract sensitive environment variables and connection strings, and elevate privileges across the host orchestration boundary.
CVE-2026-118750 is an uncontrolled resource allocation vulnerability (CWE-770) affecting the Microsoft.AspNetCore.WebUtilities component in ASP.NET Core. Specifically, the FileBufferingReadStream class eagerly allocates large memory buffers from the shared ArrayPool<byte> inside its constructor prior to reading any incoming request payload data. An unauthenticated remote attacker can exploit this behavior by opening numerous concurrent connections and stalling or omitting request body transmission, exhausting shared memory pools and causing a server-wide Denial of Service (DoS).
CVE-2026-114155 is a local elevation of privilege vulnerability affecting Microsoft .NET on Linux and macOS platforms. The vulnerability exists within the dotnet-watch (Hot Reload) component and is caused by a Time-of-Check Time-of-Use (TOCTOU) race condition (CWE-367) during the generation and execution of temporary XML solution files (.slnx) in shared system temporary directories. A local attacker with low privileges can intercept and modify the temporary solution file before MSBuild evaluates it, achieving arbitrary code execution in the security context of the user running dotnet-watch.