CVE-2026-5896
Overview
Background
- Sandboxed iframe download policy
- the HTML
sandboxattribute gates downloads behind theallow-downloadstoken; without it, content inside the frame is not permitted to initiate downloads. - Blink media controls
- the default
<video>/<audio>control UI rendered by Blink, which includes a download button element for saving the media resource. - `WebLocalFrame::IsAllowedToDownload`
- a frame-level query exposing whether the current frame’s sandbox flags permit downloads.
- `MediaControlDownloadButtonElement`
- the Blink element (
media_control_download_button_element.cc) representing the media-controls download button, whose display is decided by a predicate returningtrue/false.
Root Cause Analysis
The predicate that decides whether to show the media-controls download button (the method enclosing the edited hunk in media_control_download_button_element.cc; its name is not shown in the @@ context) evaluated its existing conditions and, if they passed, returned true without consulting the frame’s sandbox download policy. As a result a <video controls> placed inside an iframe sandboxed without allow-downloads still rendered a functional download button, letting the user save the media even though the sandbox was meant to disallow downloads.
The fix inserts a new guard: if GetDocument().GetFrame() exists, it resolves the corresponding WebLocalFrame via WebLocalFrame::FromFrameToken and returns false when !web_local_frame->IsAllowedToDownload(), placed before the final return true;. This tightens the predicate so the button is hidden whenever the sandbox forbids downloads, aligning the media-controls path with the sandbox policy. The shipped web test confirms the button is disabled in a sandbox="allow-same-origin" frame and enabled when allow-downloads is added.
IsAllowedToDownload check so the button is hidden (predicate returns false) inside download-disallowing sandboxed iframes.Attack Path
- Embed media in a restrictive sandbox
A page embeds an iframe with
sandbox="allow-same-origin"(omittingallow-downloads) containing a<video controls>element. - Default controls render the button Blink’s default media controls show the download button because the pre-patch display predicate never consulted the sandbox download flag.
- User saves the resource
Activating the functional download button lets the media be downloaded from a context the
sandboxattribute was intended to block, bypassing theallow-downloadspolicy.
Impact Assessment
allow-downloads sandbox restriction; the effect is confined to the download-policy bypass with no memory-safety or privilege consequence shown in the patch. No exploited-in-the-wild status is claimed.Changed Functions
| Function | Change | Notes |
|---|---|---|
async_testthird_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html |
modified | |
runAfterLayoutAndPaintthird_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html |
modified |
Files Changed
third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.ccthird_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
Audit Directions
- UI affordances that bypass sandbox flagsAudit other browser-provided controls (context-menu “Save as”, picture-in-picture, media/image save paths) for offering downloads without checking
IsAllowedToDownload/ the frame sandbox policy. - Policy checks missing on default UA UIReview Blink shadow-DOM user-agent controls that trigger privileged actions to confirm each re-validates the frame’s sandbox and permission state rather than assuming the embedder enforced it.
- Frame-token resolution guardsWhere a new check depends on
GetDocument().GetFrame()andWebLocalFrame::FromFrameTokenpossibly returning null, verify fail-closed behavior (null frame should not silently permit the action).
Patch
From ad95dd50a86da6f46e3ae113a487689e99f4c4cc Mon Sep 17 00:00:00 2001
From: Tommy Steimel <steimel@chromium.org>
Date: Thu, 19 Feb 2026 08:41:43 -0800
Subject: [PATCH] [Media Controls] Hide download button when sandbox flags disallow it
Currently, the default Blink media controls display a (functional)
download button, even when inside a sandboxed iframe that does not
allow downloads. This CL hides the download button when the element
is in a sandboxed iframe that disallows downloads.
Bug: 40064543
Change-Id: Ie25b48bb994ed66c8621429a3447cc279e67fbfb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7590057
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Benjamin Keen <bkeen@google.com>
Cr-Commit-Position: refs/heads/main@{#1587180}
---
diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
index fe23dd7e8..f670bf0 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
@@ -8,6 +8,7 @@
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/user_metrics_action.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
+#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -50,6 +51,14 @@
return false;
}
+ if (GetDocument().GetFrame()) {
+ WebLocalFrame* web_local_frame = WebLocalFrame::FromFrameToken(
+ GetDocument().GetFrame()->GetLocalFrameToken());
+ if (web_local_frame && !web_local_frame->IsAllowedToDownload()) {
+ return false;
+ }
+ }
+
return true;
}
diff --git a/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
new file mode 100644
index 0000000..45839b23
--- /dev/null
+++ b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>media controls download button hidden when sandboxed</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script src="../media-controls.js"></script>
+<iframe id="downloads-blocked"sandbox="allow-same-origin"></iframe>
+<iframe id="downloads-allowed"sandbox="allow-same-origin allow-downloads"></iframe>
+<script>
+async_test(function(t) {
+ const iframeBlocked = document.querySelector("#downloads-blocked");
+ const iframeAllowed = document.querySelector("#downloads-allowed");
+ const videoBlocked = iframeBlocked.contentDocument.createElement("video");
+ const videoAllowed = iframeAllowed.contentDocument.createElement("video");
+
+ [videoBlocked, videoAllowed].forEach((video) => {
+ video.controls = true;
+ video.setAttribute("preload", "none");
+ video.src = "https://someexample.example/example.mp4";
+ });
+ iframeBlocked.contentDocument.body.append(videoBlocked);
+ iframeAllowed.contentDocument.body.append(videoAllowed);
+
+ runAfterLayoutAndPaint(t.step_func_done(function() {
+ assert_false(isDownloadsButtonEnabled(videoBlocked),
+ "the download button should not show when the sandbox blocks downloads");
+ assert_true(isDownloadsButtonEnabled(videoAllowed),
+ "the download button should show when the sandbox allows downloads");
+ }));
+
+});
+</script>
Regression Test / PoC
diff --git a/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
new file mode 100644
index 0000000..45839b23
--- /dev/null
+++ b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>media controls download button hidden when sandboxed</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script src="../media-controls.js"></script>
+<iframe id="downloads-blocked"sandbox="allow-same-origin"></iframe>
+<iframe id="downloads-allowed"sandbox="allow-same-origin allow-downloads"></iframe>
+<script>
+async_test(function(t) {
+ const iframeBlocked = document.querySelector("#downloads-blocked");
+ const iframeAllowed = document.querySelector("#downloads-allowed");
+ const videoBlocked = iframeBlocked.contentDocument.createElement("video");
+ const videoAllowed = iframeAllowed.contentDocument.createElement("video");
+
+ [videoBlocked, videoAllowed].forEach((video) => {
+ video.controls = true;
+ video.setAttribute("preload", "none");
+ video.src = "https://someexample.example/example.mp4";
+ });
+ iframeBlocked.contentDocument.body.append(videoBlocked);
+ iframeAllowed.contentDocument.body.append(videoAllowed);
+
+ runAfterLayoutAndPaint(t.step_func_done(function() {
+ assert_false(isDownloadsButtonEnabled(videoBlocked),
+ "the download button should not show when the sandbox blocks downloads");
+ assert_true(isDownloadsButtonEnabled(videoAllowed),
+ "the download button should show when the sandbox allows downloads");
+ }));
+
+});
+</script>
Original Bug Report
Audio player's download functionality allows bypassing the "allow-downloads" flag of sandboxed iframes
VULNERABILITY DETAILS
A sandboxed iframe is not supposed to be able to initiate a download unless the “allow-downloads” flag is set. However, it is possible to make the user click on the “Download” option of the audio player through a clickjacking attack, which ends up downloading a file.
An arbitrary file can be made to be downloaded by performing a server-side redirect when the user tries to download the original audio file.
The “Download” option should be checking whether the iframe is sandboxed and contains the proper flags before deciding whether the download should initiate.
For the attack to work, it requires the victim to click twice inside a malicious iframe. In a real attack, there would be buttons placed over the video player to trick the user into clicking on the “three dots” and the “Download” option. The PoC just places opacity over the iframe to demonstrate that it would be possible to do that.
For reference, this issue is similar to https://crbug.com/chromium/1100761, albeit more severe as this issue doesn’t require any additional sandbox permission to work.
I have attached a video (repro.mkv) reproducing the attack.
VERSION
Chrome Version:
113.0.5672.92 (Official Build) stable (64-bit)
114.0.5735.26 (Official Build) beta (64-bit)
115.0.5762.4 (Official Build) dev (64-bit)
Operating System:
Ubuntu 20.04
REPRODUCTION CASE
- Download “index.html”, “iframe.html”, “iframe2.html”, “horse.ogg”, “file.txt”, “download.php” and place them in the same directory.
- Run the “php -S 0:8000” command in the directory the files were downloaded to.
- Access http://localhost:8000/
- Click on the “three dots” and then on the “Download” option, which will download a file even though the iframe doesn’t have the “allow-downloads” flag set.
CREDIT INFORMATION
Reporter credit: Luan Herrera (@lbherrera_)