### Uh oh!
There was an error while loading. Please reload this page.
**security-research** Public
# Cross-Frame Scripting (XFS) via Background Proxy in Vimium
## Package
## Affected versions
## Patched versions
## Description
### Summary
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.
### Vulnerability Detail
– **Vulnerability Type:** Cross-Frame Scripting (XFS) / Privilege Escalation
– **Affected Component:** `background_scripts/tab_operations.js`
– **Affected Versions:** Up to 2.4.2 (Manifest V3)
### Severity
Medium – 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.
### Proof of Concept
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.
## Reproduction Steps
1. Navigate to a website that embeds a cross-origin iframe (e.g., a page with a YouTube embed or a third-party ad).
2. Open **Chrome DevTools**( `F12`).
3. Go to the **Console** tab.
4. Click the **Execution Context selector**(the dropdown in the top-left of the console, usually showing `top`).
5. Find the entry for the **cross-origin iframe** and select the **Vimium** extension context associated with it (it will be listed under the extension’s name).
6. Execute the following payload in the console:
“`
chrome.runtime.sendMessage({ handler: “openUrlInCurrentTab”, url: “javascript:alert(‘XFS Success! Target Origin: ‘ + window.location.origin + ‘\nLogged from: ‘ + document.origin)”, });
“`
1. **Success Condition**: An alert dialog will appear. Observe that `window.location.origin` reflects the **top-level page’s origin**, proving that the script was successfully proxied from the iframe into the parent frame.
### Further Analysis
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] }
“`
### Timeline
**Date reported**: 2026-07-17
**Date fixed**: 2026-07-22
**Date disclosed**: 2026-09-01
philc/vimium@4427dc6
