Unpreventable top-level navigation in Electron
Description
In Electron before versions 11.0.0-beta.1, 10.0.1, 9.3.0 or 8.5.1 the will-navigate event that apps use to prevent navigations to unexpected destinations as per our security recommendations can be bypassed when a sub-frame performs a top-frame navigation across sites. The issue is patched in versions 11.0.0-beta.1, 10.0.1, 9.3.0 or 8.5.1 As a workaround sandbox all your iframes using the sandbox attribute. This will prevent them creating top-frame navigations and is good practice anyway.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A bypass in Electron's will-navigate event lets cross-origin iframes initiate unpreventable top-frame navigations, undermining app security controls.
Vulnerability
Description CVE-2020-15174 is a security bypass in the Electron framework affecting versions before 11.0.0-beta.1, 10.0.1, 9.3.0, and 8.5.1 [1][4]. The will-navigate event is intended to allow applications to prevent unwanted navigations, a key security recommendation. However, when a sub-frame (e.g., an iframe) initiates a top-frame navigation to a different site, the event can be bypassed, allowing the navigation to proceed without being intercepted or blocked [1][4].
Exploitation
Method Exploitation requires the victim application to load an iframe from a cross-origin source. The attacker-controlled iframe can then perform a top-frame navigation (e.g., using target="_top" links) to any URL, bypassing the will-navigate event handler. No authentication or special network position is needed beyond the ability to serve content in an iframe [3][4]. The proof-of-concept in the patch commit demonstrates triggering this by having an iframe click a link that navigates the top frame [3].
Impact
If exploited, an attacker can redirect the entire Electron application window to a malicious site, potentially leading to phishing, data exfiltration, or further exploitation. This undermines the security model of applications that rely on will-navigate to enforce navigation restrictions [1][4]. The vulnerability affects all major Electron release lines prior to the patched versions.
Mitigation
The vulnerability is patched in Electron versions 11.0.0-beta.1, 10.0.1, 9.3.0, and 8.5.1 [1][4]. As a workaround, developers should sandbox all iframes using the sandbox attribute, which prevents them from creating top-frame navigations entirely [4]. Users are strongly advised to update their Electron dependency to the latest patched version.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
electronnpm | >= 8.0.0-beta.0, < 8.5.1 | 8.5.1 |
electronnpm | >= 9.0.0-beta.0, < 9.3.0 | 9.3.0 |
electronnpm | >= 10.0.0-beta.0, < 10.0.1 | 10.0.1 |
Affected products
2- Range: >= 8.0.0-beta.0, < 8.5.1
Patches
118613925610brefactor: wire will-navigate up to a navigation throttle instead of OpenURL (#25108)
4 files changed · +65 −7
shell/browser/api/electron_api_web_contents.cc+0 −6 modified@@ -743,12 +743,6 @@ content::WebContents* WebContents::OpenURLFromTab( return nullptr; } - // Give user a chance to cancel navigation. - if (Emit("will-navigate", params.url)) - return nullptr; - - // Don't load the URL if the web contents was marked as destroyed from a - // will-navigate event listener if (IsDestroyed()) return nullptr;
shell/browser/electron_navigation_throttle.cc+24 −0 modified@@ -19,6 +19,30 @@ const char* ElectronNavigationThrottle::GetNameForLogging() { return "ElectronNavigationThrottle"; } +content::NavigationThrottle::ThrottleCheckResult +ElectronNavigationThrottle::WillStartRequest() { + auto* handle = navigation_handle(); + auto* contents = handle->GetWebContents(); + if (!contents) { + NOTREACHED(); + return PROCEED; + } + + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::HandleScope scope(isolate); + auto api_contents = electron::api::WebContents::From(isolate, contents); + if (api_contents.IsEmpty()) { + // No need to emit any event if the WebContents is not available in JS. + return PROCEED; + } + + if (handle->IsRendererInitiated() && handle->IsInMainFrame() && + api_contents->EmitNavigationEvent("will-navigate", handle)) { + return CANCEL; + } + return PROCEED; +} + content::NavigationThrottle::ThrottleCheckResult ElectronNavigationThrottle::WillRedirectRequest() { auto* handle = navigation_handle();
shell/browser/electron_navigation_throttle.h+2 −0 modified@@ -14,6 +14,8 @@ class ElectronNavigationThrottle : public content::NavigationThrottle { explicit ElectronNavigationThrottle(content::NavigationHandle* handle); ~ElectronNavigationThrottle() override; + ElectronNavigationThrottle::ThrottleCheckResult WillStartRequest() override; + ElectronNavigationThrottle::ThrottleCheckResult WillRedirectRequest() override;
spec-main/api-browser-window-spec.ts+39 −1 modified@@ -442,7 +442,13 @@ describe('BrowserWindow module', () => { let server = null as unknown as http.Server; let url = null as unknown as string; before((done) => { - server = http.createServer((req, res) => { res.end(''); }); + server = http.createServer((req, res) => { + if (req.url === '/navigate-top') { + res.end('<a target=_top href="/">navigate _top</a>'); + } else { + res.end(''); + } + }); server.listen(0, '127.0.0.1', () => { url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`; done(); @@ -506,6 +512,38 @@ describe('BrowserWindow module', () => { expect(navigatedTo).to.equal(url); expect(w.webContents.getURL()).to.equal('about:blank'); }); + + it('is triggered when a cross-origin iframe navigates _top', async () => { + await w.loadURL(`data:text/html,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-top"></iframe>`); + await delay(1000); + w.webContents.debugger.attach('1.1'); + const targets = await w.webContents.debugger.sendCommand('Target.getTargets'); + const iframeTarget = targets.targetInfos.find((t: any) => t.type === 'iframe'); + const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', { + targetId: iframeTarget.targetId, + flatten: true + }); + await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { + type: 'mousePressed', + x: 10, + y: 10, + clickCount: 1, + button: 'left' + }, sessionId); + await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', { + type: 'mouseReleased', + x: 10, + y: 10, + clickCount: 1, + button: 'left' + }, sessionId); + let willNavigateEmitted = false; + w.webContents.on('will-navigate', () => { + willNavigateEmitted = true; + }); + await emittedOnce(w.webContents, 'did-navigate'); + expect(willNavigateEmitted).to.be.true(); + }); }); describe('will-redirect event', () => {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-2q4g-w47c-4674ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-15174ghsaADVISORY
- github.com/electron/electron/commit/18613925610ba319da7f497b6deed85ad712c59bghsax_refsource_MISCWEB
- github.com/electron/electron/security/advisories/GHSA-2q4g-w47c-4674ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.