mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
fix: respect iframe sandbox flags for external protocol navigation (#50964)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Sam Attard <sattard@anthropic.com>
This commit is contained in:
@@ -72,6 +72,7 @@
|
||||
#include "services/network/public/cpp/resource_request_body.h"
|
||||
#include "services/network/public/cpp/self_deleting_url_loader_factory.h"
|
||||
#include "services/network/public/cpp/url_loader_factory_builder.h"
|
||||
#include "services/network/public/cpp/web_sandbox_flags.h"
|
||||
#include "shell/app/electron_crash_reporter_client.h"
|
||||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/browser/api/electron_api_crash_reporter.h"
|
||||
@@ -128,6 +129,7 @@
|
||||
#include "third_party/blink/public/common/tokens/tokens.h"
|
||||
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
|
||||
#include "third_party/blink/public/mojom/badging/badging.mojom.h"
|
||||
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "v8/include/v8.h"
|
||||
@@ -926,7 +928,9 @@ void HandleExternalProtocolInUI(
|
||||
const GURL& url,
|
||||
content::WeakDocumentPtr document_ptr,
|
||||
content::WebContents::OnceGetter web_contents_getter,
|
||||
bool has_user_gesture) {
|
||||
bool has_user_gesture,
|
||||
bool is_primary_main_frame,
|
||||
network::mojom::WebSandboxFlags sandbox_flags) {
|
||||
content::WebContents* web_contents = std::move(web_contents_getter).Run();
|
||||
if (!web_contents)
|
||||
return;
|
||||
@@ -945,6 +949,30 @@ void HandleExternalProtocolInUI(
|
||||
rfh = web_contents->GetPrimaryMainFrame();
|
||||
}
|
||||
|
||||
// Sandboxed iframes without one of the appropriate sandbox-escape tokens
|
||||
// must not be able to launch external protocol handlers. This mirrors
|
||||
// chrome/browser/chrome_content_browser_client.cc; see crbug.com/1148777.
|
||||
if (!is_primary_main_frame) {
|
||||
using SandboxFlags = network::mojom::WebSandboxFlags;
|
||||
auto allow = [sandbox_flags](SandboxFlags flag) {
|
||||
return (sandbox_flags & flag) == SandboxFlags::kNone;
|
||||
};
|
||||
const bool allowed = allow(SandboxFlags::kTopNavigationToCustomProtocols) ||
|
||||
(allow(SandboxFlags::kTopNavigationByUserActivation) &&
|
||||
has_user_gesture);
|
||||
if (!allowed) {
|
||||
rfh->AddMessageToConsole(
|
||||
blink::mojom::ConsoleMessageLevel::kError,
|
||||
"Navigation to external protocol blocked by sandbox, because it "
|
||||
"doesn't contain any of: "
|
||||
"'allow-top-navigation-to-custom-protocols', "
|
||||
"'allow-top-navigation-by-user-activation', "
|
||||
"'allow-top-navigation', or 'allow-popups'. See "
|
||||
"https://chromestatus.com/feature/5680742077038592");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GURL escaped_url(base::EscapeExternalHandlerValue(url.spec()));
|
||||
auto callback = base::BindOnce(&OnOpenExternal, escaped_url);
|
||||
permission_helper->RequestOpenExternalPermission(rfh, std::move(callback),
|
||||
@@ -973,7 +1001,8 @@ bool ElectronBrowserClient::HandleExternalProtocol(
|
||||
initiator_document
|
||||
? initiator_document->GetWeakDocumentPtr()
|
||||
: content::WeakDocumentPtr(),
|
||||
std::move(web_contents_getter), has_user_gesture));
|
||||
std::move(web_contents_getter), has_user_gesture,
|
||||
is_primary_main_frame, sandbox_flags));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -4558,3 +4558,81 @@ describe('navigator.usb', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('iframe sandbox external protocols', () => {
|
||||
let server: http.Server;
|
||||
let serverUrl: string;
|
||||
let w: BrowserWindow;
|
||||
let openExternalRequests: string[];
|
||||
|
||||
before(async () => {
|
||||
server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
if (req.url === '/child') {
|
||||
res.end('<script>location.href = "magnet:sandbox-test"</script>');
|
||||
} else {
|
||||
const sandbox = new URL(req.url!, serverUrl).searchParams.get('sandbox') ?? '';
|
||||
res.end(`<iframe sandbox="${sandbox}" src="/child"></iframe>`);
|
||||
}
|
||||
});
|
||||
serverUrl = (await listen(server)).url;
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
openExternalRequests = [];
|
||||
w = new BrowserWindow({ show: false });
|
||||
w.webContents.session.setPermissionRequestHandler((_wc, permission, callback, details) => {
|
||||
if (permission === 'openExternal') {
|
||||
openExternalRequests.push((details as any).externalURL);
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
w.webContents.session.setPermissionRequestHandler(null);
|
||||
return closeAllWindows();
|
||||
});
|
||||
|
||||
it('blocks navigation to external protocol from a sandboxed iframe', async () => {
|
||||
const consoleMessage = once(w.webContents, 'console-message');
|
||||
await w.loadURL(`${serverUrl}/?sandbox=${encodeURIComponent('allow-scripts')}`);
|
||||
const [{ message }] = await consoleMessage;
|
||||
expect(message).to.match(/external protocol blocked by sandbox/);
|
||||
expect(openExternalRequests).to.be.empty();
|
||||
});
|
||||
|
||||
it('allows navigation to external protocol with allow-top-navigation-to-custom-protocols', async () => {
|
||||
const requested = new Promise<void>(resolve => {
|
||||
w.webContents.session.setPermissionRequestHandler((_wc, permission, callback, details) => {
|
||||
if (permission === 'openExternal') {
|
||||
openExternalRequests.push((details as any).externalURL);
|
||||
resolve();
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
});
|
||||
await w.loadURL(`${serverUrl}/?sandbox=${encodeURIComponent('allow-scripts allow-top-navigation-to-custom-protocols')}`);
|
||||
await requested;
|
||||
expect(openExternalRequests).to.deep.equal(['magnet:sandbox-test']);
|
||||
});
|
||||
|
||||
it('allows navigation to external protocol with allow-popups', async () => {
|
||||
const requested = new Promise<void>(resolve => {
|
||||
w.webContents.session.setPermissionRequestHandler((_wc, permission, callback, details) => {
|
||||
if (permission === 'openExternal') {
|
||||
openExternalRequests.push((details as any).externalURL);
|
||||
resolve();
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
});
|
||||
await w.loadURL(`${serverUrl}/?sandbox=${encodeURIComponent('allow-scripts allow-popups')}`);
|
||||
await requested;
|
||||
expect(openExternalRequests).to.deep.equal(['magnet:sandbox-test']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user