Skip to content

Commit 55c9c0d

Browse files
Refactor SecurityOrchestrator from singleton to instance-based pattern
- Fixes multi-window bug where closing one AI Window would reset security state for all windows. - Fixes minor bug for allowedUrls and other lint errors - Update SecurityOrchestrator instantiation in xpcshell tests - Remove deprecated files and tests (SmartWindowIntegration.sys.mjs)
1 parent 9d5dd1a commit 55c9c0d

14 files changed

Lines changed: 255 additions & 808 deletions

browser/components/smartwindow/content/smartbar.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import {
1515
getMentionSuggestions,
1616
} from "chrome://browser/content/smartwindow/mentions.mjs";
1717

18+
import { seedMentionedUrl } from "./utils.mjs";
19+
1820
// Track autofill state
1921
let autofillState = null;
2022
let deletedQuery = "";
@@ -252,6 +254,12 @@ export function attachToElement(element, options = {}) {
252254
dropdown = new MentionDropdown();
253255
currentCommand = props.command;
254256
dropdown.create(props.items, item => {
257+
// Seed the @mentioned URL into the security ledger
258+
// This grants the AI permission to access this URL
259+
if (item.id) {
260+
seedMentionedUrl(item.id);
261+
}
262+
255263
currentCommand({
256264
id: item.id,
257265
label: item.label,

browser/components/smartwindow/content/smartwindow.mjs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
44

5-
import { detectQueryType, searchBrowserHistory } from "./utils.mjs";
5+
import { detectQueryType, searchBrowserHistory, setSecurityOrchestrator } from "./utils.mjs";
66
import { attachToElement } from "chrome://browser/content/smartwindow/smartbar.mjs";
77
import {
88
generateLiveSuggestions,
@@ -14,7 +14,6 @@ import {
1414
CHAT_HISTORY_CONVERSATION_SELECTED_EVENT,
1515
} from "chrome://browser/content/smartwindow/chat-history.mjs";
1616
import { deleteInsight, getInsightSummariesForPrompt } from "./insights.mjs";
17-
import { generateId } from "chrome://global/content/ml/security/SecurityUtils.sys.mjs";
1817

1918
const { ChatHistory, ChatHistoryConversation } = ChromeUtils.importESModule(
2019
"resource:///modules/smartwindow/ChatHistory.sys.mjs"
@@ -35,6 +34,9 @@ const { TabStateFlusher } = ChromeUtils.importESModule(
3534
const { SecurityOrchestrator } = ChromeUtils.importESModule(
3635
"chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs"
3736
);
37+
const { generateId } = ChromeUtils.importESModule(
38+
"chrome://global/content/ml/security/SecurityUtils.sys.mjs"
39+
);
3840
const { embedderElement, topChromeWindow } = window.browsingContext;
3941
const gBrowser = topChromeWindow.gBrowser;
4042

@@ -119,18 +121,25 @@ class SmartWindowPage {
119121
// Only initialize if Smart Window security is enabled
120122
const sessionId = generateId("smart-window");
121123
const securityEnabled = Services.prefs.getBoolPref("browser.smartwindow.security.enabled", true);
124+
125+
this.securityOrchestrator = null;
122126

123127
if (securityEnabled) {
124-
SecurityOrchestrator.init(sessionId).then(sessionLedger => {
125-
this.sessionLedger = sessionLedger;
126-
console.log("[Security] Smart Window security enabled - orchestrator initialized");
128+
SecurityOrchestrator.create(sessionId).then(orchestrator => {
129+
this.securityOrchestrator = orchestrator;
130+
this.sessionLedger = orchestrator.getSessionLedger();
131+
setSecurityOrchestrator(orchestrator);
132+
console.warn("[Security] AI Window security enabled - orchestrator initialized");
127133
}).catch(error => {
128134
console.error("[Security] Failed to initialize orchestrator:", error);
135+
this.securityOrchestrator = null;
129136
this.sessionLedger = null;
137+
setSecurityOrchestrator(null);
130138
});
131139
} else {
132140
this.sessionLedger = null;
133-
console.log("[Security] Smart Window security DISABLED via kill switch - running in pass-through mode");
141+
setSecurityOrchestrator(null);
142+
console.warn("[Security] AI Window security DISABLED via kill switch - running in pass-through mode");
134143
}
135144

136145
gBrowser.selectedTab.conversation = new ChatHistoryConversation({
@@ -180,7 +189,10 @@ class SmartWindowPage {
180189
);
181190
}
182191

183-
SecurityOrchestrator.reset();
192+
if (this.securityOrchestrator) {
193+
this.securityOrchestrator.reset();
194+
setSecurityOrchestrator(null);
195+
}
184196
}
185197

186198
getQueryTypeIcon(type) {

browser/components/smartwindow/content/utils.mjs

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,59 @@ ChromeUtils.defineESModuleGetters(lazy, {
1111
PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
1212
PageThumbsStorage: "resource://gre/modules/PageThumbs.sys.mjs",
1313
getPlacesSemanticHistoryManager: "resource://gre/modules/PlacesSemanticHistoryManager.sys.mjs",
14-
SecurityOrchestrator:
15-
"chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs",
1614
});
1715

16+
/**
17+
* Module-level security orchestrator instance.
18+
* Set by SmartWindowPage after initialization.
19+
*
20+
* @type {import("chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs").SecurityOrchestrator|null}
21+
*/
22+
let securityOrchestrator = null;
23+
24+
/**
25+
* Sets the security orchestrator instance for this window's utils module.
26+
* Called by SmartWindowPage after creating the orchestrator.
27+
*
28+
* @param {import("chrome://global/content/ml/security/SecurityOrchestrator.sys.mjs").SecurityOrchestrator|null} orchestrator
29+
*/
30+
export function setSecurityOrchestrator(orchestrator) {
31+
securityOrchestrator = orchestrator;
32+
}
33+
34+
/**
35+
* Seeds a URL into the session ledger for the current tab.
36+
* Call this when a user explicitly @mentions a tab/URL.
37+
* This grants the AI permission to access the URL's content.
38+
*
39+
* @param {string} url - The URL to seed
40+
*/
41+
export function seedMentionedUrl(url) {
42+
if (!securityOrchestrator) {
43+
console.log("[Security] No orchestrator - skipping mention seeding");
44+
return;
45+
}
46+
47+
const sessionLedger = securityOrchestrator.getSessionLedger();
48+
if (!sessionLedger) {
49+
console.log("[Security] No session ledger - skipping mention seeding");
50+
return;
51+
}
52+
53+
const win = lazy.BrowserWindowTracker.getTopWindow();
54+
const gBrowser = win.gBrowser;
55+
const tabId = gBrowser.selectedTab.linkedPanel;
56+
57+
const tabLedger = sessionLedger.forTab(tabId);
58+
const added = tabLedger.add(url);
59+
60+
if (added) {
61+
console.log(`[Security] Seeded @mentioned URL: ${url} for tab ${tabId}`);
62+
} else {
63+
console.warn(`[Security] Failed to seed @mentioned URL: ${url} for tab ${tabId}`);
64+
}
65+
}
66+
1867
import { createEngine } from "chrome://global/content/ml/EngineProcess.sys.mjs";
1968
import { SmartAssistEngine } from "moz-src:///browser/components/genai/SmartAssistEngine.sys.mjs";
2069
import { deleteInsight, findRelatedInsight, generateInsightsFromDirectChat } from "chrome://browser/content/smartwindow/insights.mjs";
@@ -598,7 +647,7 @@ async function checkToolSecurity(toolName, toolParams, requestId) {
598647
await browser.browsingContext.currentWindowContext.getActor(
599648
"SmartWindowMeta"
600649
);
601-
const sessionLedger = lazy.SecurityOrchestrator.getSessionLedger();
650+
const sessionLedger = securityOrchestrator?.getSessionLedger();
602651

603652
if (!sessionLedger) {
604653
console.log(
@@ -633,7 +682,7 @@ async function checkToolSecurity(toolName, toolParams, requestId) {
633682
tabId = gBrowser.selectedTab.linkedPanel;
634683
}
635684

636-
const decision = await lazy.SecurityOrchestrator.evaluate({
685+
const decision = await securityOrchestrator.evaluate({
637686
phase: "tool.execution",
638687
action: {
639688
type: "tool.call",
@@ -955,12 +1004,12 @@ export async function* fetchWithHistory(messages, allowedUrls) {
9551004
error: securityCheck.reason || "Security policy denied this action",
9561005
};
9571006
} else {
958-
// Populate allowedUrls from current tab's ledger for headless extraction
1007+
// Merge session ledger URLs with any pre-existing allowedUrls for headless extraction.
9591008
// This allows fetching URLs that are part of the current page's metadata
960-
// Such as (canonical URLs, related links, etc.) even if they're not in open tabs
961-
let allowedUrls = new Set();
1009+
// (canonical URLs, related links, etc.) even if they're not in open tabs.
1010+
let mergedAllowedUrls = new Set(allowedUrls || []);
9621011
try {
963-
const sessionLedger = lazy.SecurityOrchestrator.getSessionLedger();
1012+
const sessionLedger = securityOrchestrator?.getSessionLedger();
9641013

9651014
if (sessionLedger) {
9661015
// Get current tab ID from tool params or browser
@@ -985,27 +1034,28 @@ export async function* fetchWithHistory(messages, allowedUrls) {
9851034
if (currentTabId) {
9861035
const tabLedger = sessionLedger.forTab(currentTabId);
9871036
if (tabLedger) {
988-
allowedUrls = new Set(tabLedger.getAll());
989-
console.log(
990-
`[Security] Allowing headless extraction for ${allowedUrls.size} URLs from current tab ${currentTabId}`
1037+
for (const url of tabLedger.getAll()) {
1038+
mergedAllowedUrls.add(url);
1039+
}
1040+
console.warn(
1041+
`[Security] Allowing headless extraction for ${mergedAllowedUrls.size} URLs from current tab ${currentTabId}`
9911042
);
9921043
}
9931044
}
9941045
}
9951046
} catch (error) {
9961047
console.warn(
997-
"[Security] Could not populate allowedUrls for headless extraction:",
1048+
"[Security] Could not populate mergedAllowedUrls for headless extraction:",
9981049
error
9991050
);
1000-
allowedUrls = new Set();
10011051
}
10021052

10031053
switch (toolName) {
10041054
case SEARCH_OPEN_TABS:
10051055
result = search_open_tabs(toolParams);
10061056
break;
10071057
case GET_PAGE_CONTENT:
1008-
result = await get_page_content(toolParams, allowedUrls);
1058+
result = await get_page_content(toolParams, mergedAllowedUrls);
10091059
break;
10101060
case SEARCH_HISTORY:
10111061
result = await searchBrowserHistory(toolParams);

toolkit/components/ml/jar.mn

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ toolkit.jar:
3434
content/global/ml/security/SecurityLogger.sys.mjs (security/SecurityLogger.sys.mjs)
3535
content/global/ml/security/SecurityOrchestrator.sys.mjs (security/SecurityOrchestrator.sys.mjs)
3636
content/global/ml/security/SecurityUtils.sys.mjs (security/SecurityUtils.sys.mjs)
37-
content/global/ml/security/SmartWindowIntegration.sys.mjs (security/SmartWindowIntegration.sys.mjs)
3837
content/global/ml/security/policies/tool-execution-policies.json (security/policies/tool-execution-policies.json)
3938
#ifdef NIGHTLY_BUILD
4039
content/global/ml/ort.webgpu-dev.mjs (vendor/ort.webgpu-dev.mjs)

toolkit/components/ml/security/PolicyEvaluator.sys.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export class PolicyEvaluator {
3131
* @returns {boolean} True if policy applies to this action
3232
*/
3333
static checkMatch(matchCriteria, action) {
34-
console.warn("[PolicyEvaluator] checkMatch criteria:", JSON.stringify(matchCriteria), "action:", JSON.stringify(action));
34+
console.warn(
35+
"[PolicyEvaluator] checkMatch criteria:",
36+
JSON.stringify(matchCriteria),
37+
"action:",
38+
JSON.stringify(action)
39+
);
3540
if (!matchCriteria || typeof matchCriteria !== "object") {
3641
return false;
3742
}

0 commit comments

Comments
 (0)