Vimium's background script provides an openUrlInCurrentTab message handler that allows content scripts to request a URL to be opened in the current tab. When a javascript: URL is provided, the background script uses the chrome.scripting.executeScript API to inject and execute the code.
However, the background script does not specify a frameId in the injection target. According to the Chrome Extension API documentation for Manifest V3, if frameId is omitted, the script is injected into the main frame by default.
Since any frame (including cross-origin iframes) where Vimium is active can send this message to the background script, a compromised content script in a low-privilege subframe can execute arbitrary JavaScript in the high-privilege top-level origin.
background_scripts/tab_operations.jsMedium - This is a medium-severity privilege escalation vulnerability. It does not provide an initial entry point for an attack, but it can be used to significantly increase the impact of a separate, hypothetical DOM-based XSS flaw within Vimium. If an attacker achieves code execution within a subframe's Vimium context, this flaw allows them to escalate those privileges to the top-level origin, potentially leading to Cross-Site Scripting (XSS) on the parent page.
The vulnerable code is located in background_scripts/tab_operations.js:
export async function openUrlInCurrentTab(request) { const urlStr = await UrlUtils.convertToUrl(request.url); // ... if (UrlUtils.hasJavascriptProtocol(urlStr)) { const scriptingArgs = { target: { tabId: request.tabId }, // VULNERABILITY: Omitted frameId func: (text) => { ... }, args: [urlStr], }; // ... chrome.scripting.executeScript(scriptingArgs); } }
The request object is populated from the sender in background_scripts/main.js, but sender.frameId was previously ignored during the injection phase.
F12).top).chrome.runtime.sendMessage({ handler: "openUrlInCurrentTab", url: "javascript:alert('XFS Success! Target Origin: ' + window.location.origin + '\\nLogged from: ' + document.origin)", });
window.location.origin reflects the top-level page's origin, proving that the script was successfully proxied from the iframe into the parent frame.The background script must explicitly target the frame that originated the request.
Fix: In background_scripts/main.js, sender.frameId is captured and passed to the handler. In background_scripts/tab_operations.js, the frameId is included in the target object:
target: { tabId: request.tabId, frameIds: [request.frameId] }
Date reported: 2026-07-17
Date fixed: 2026-07-22
Date disclosed: 2026-09-01
philc/vimium@4427dc6