-
Notifications
You must be signed in to change notification settings - Fork 0
[GENAI-2335] Implement Policy Engine for Smart Window tool execution #124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: smart-window
Are you sure you want to change the base?
Changes from all commits
3453fab
1578d0b
9d5dd1a
3f76c54
bf8d795
647e1b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
|
||
| /** | ||
| * Content-process actor for extracting page metadata (page URL, canonical, og:url). | ||
| * The parent actor validates and normalizes these URLs before seeding the security ledger. | ||
| */ | ||
| export class SmartWindowMetaChild extends JSWindowActorChild { | ||
| /** | ||
| * Receives queries from the parent process. | ||
| * | ||
| * @param {ReceiveMessageArgument} message - The message from parent | ||
| * @returns {Promise<object>} Metadata object with URLs | ||
| */ | ||
| receiveMessage(message) { | ||
| switch (message.name) { | ||
| case "SmartWindowMeta:GetMetadata": | ||
| return this.getMetadata(); | ||
| default: | ||
| return Promise.reject(new Error(`Unknown message: ${message.name}`)); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extracts metadata from the current page. | ||
| * | ||
| * @returns {object} Metadata with pageUrl, canonical, and ogUrl (raw strings) | ||
| */ | ||
| getMetadata() { | ||
| const doc = this.contentWindow?.document; | ||
|
|
||
| if (!doc) { | ||
| return { pageUrl: "", canonical: "", ogUrl: "" }; | ||
| } | ||
|
|
||
| const pageUrl = doc.location?.href || ""; | ||
|
|
||
| let canonical = ""; | ||
| try { | ||
| const canonicalLink = doc.querySelector('link[rel="canonical"]'); | ||
| canonical = canonicalLink?.getAttribute("href") || ""; | ||
| } catch { | ||
| // querySelector may fail on some documents (e.g., XML) | ||
| } | ||
|
|
||
| let ogUrl = ""; | ||
| try { | ||
| const ogUrlMeta = doc.querySelector('meta[property="og:url"]'); | ||
| ogUrl = ogUrlMeta?.getAttribute("content") || ""; | ||
| } catch { | ||
| // querySelector may fail on some documents | ||
| } | ||
|
|
||
| return { pageUrl, canonical, ogUrl }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
|
|
||
| import { | ||
| normalizeUrl, | ||
| isSameETLDPlusOne, | ||
| } from "chrome://global/content/ml/security/SecurityUtils.sys.mjs"; | ||
|
|
||
| /** | ||
| * Chrome-process actor for validating page metadata and seeding security ledger. | ||
| * Validates canonical/og:url have same eTLD+1 as page URL before seeding. | ||
| */ | ||
| export class SmartWindowMetaParent extends JSWindowActorParent { | ||
| /** | ||
| * Seeds the security ledger for the given browser/tab. | ||
| * | ||
| * @param {object} sessionLedger - The SessionLedger instance | ||
| * @param {string} tabId - The tab identifier (typically linkedPanel) | ||
| * @returns {Promise<object>} Result with seededUrls, skippedUrls, and errors | ||
| */ | ||
| async seedLedgerForTab(sessionLedger, tabId) { | ||
| const result = { | ||
| success: false, | ||
| seededUrls: [], | ||
| skippedUrls: [], | ||
| errors: [], | ||
| }; | ||
|
|
||
| try { | ||
| const metadata = await this.sendQuery("SmartWindowMeta:GetMetadata"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is the incentive to run this in the child process?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question! My understanding is that the parent process wouldn't be able to query the DOM directly (i.e., However, I should definitely look into the existing API you mentioned before that would already handle this 😅 |
||
|
|
||
| if (!metadata || !metadata.pageUrl) { | ||
| result.errors.push("No page URL available from content process"); | ||
| return result; | ||
| } | ||
|
|
||
| // Process all URLs in one place | ||
| const processed = this.#processMetadataUrls(metadata); | ||
|
|
||
| if (processed.error) { | ||
| result.errors.push(processed.error); | ||
| return result; | ||
| } | ||
|
|
||
| result.seededUrls = processed.seededUrls; | ||
| result.skippedUrls = processed.skippedUrls; | ||
|
|
||
| sessionLedger.forTab(tabId).seed(processed.urlsToSeed, metadata.pageUrl); | ||
| result.success = true; | ||
| } catch (error) { | ||
| result.errors.push({ | ||
| message: "Actor communication failed", | ||
| error: error.message || String(error), | ||
| }); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Processes page metadata URLs: normalizes page URL and validates secondary URLs. | ||
| * | ||
| * @param {object} metadata - Raw metadata from content process | ||
| * @param {string} metadata.pageUrl - The page's URL | ||
| * @param {string} [metadata.canonical] - The canonical URL from <link rel="canonical"> | ||
| * @param {string} [metadata.ogUrl] - The og:url from <meta property="og:url"> | ||
| * @returns {object} Processed result with urlsToSeed, seededUrls, skippedUrls, error | ||
| * @private | ||
| */ | ||
| #processMetadataUrls(metadata) { | ||
| const { pageUrl, canonical, ogUrl } = metadata; | ||
| const urlsToSeed = []; | ||
| const seededUrls = []; | ||
| const skippedUrls = []; | ||
|
|
||
| // Normalize page URL first | ||
| const normalizedPageUrl = normalizeUrl(pageUrl); | ||
| if (!normalizedPageUrl.success) { | ||
| return { | ||
| error: { | ||
| url: pageUrl, | ||
| reason: "Page URL normalization failed", | ||
| error: normalizedPageUrl.error, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| urlsToSeed.push(normalizedPageUrl.url); | ||
| seededUrls.push({ | ||
| original: pageUrl, | ||
| normalized: normalizedPageUrl.url, | ||
| source: "page", | ||
| }); | ||
|
|
||
| // Process secondary URLs (canonical, og:url) | ||
| const secondaryUrls = [ | ||
| { url: canonical, source: "canonical" }, | ||
| { url: ogUrl, source: "og:url" }, | ||
| ]; | ||
|
|
||
| for (const { url, source } of secondaryUrls) { | ||
| if (!url) { | ||
| continue; | ||
| } | ||
|
|
||
| const validated = this.#validateSecondaryUrl( | ||
| url, | ||
| normalizedPageUrl.url, | ||
| pageUrl, | ||
| ); | ||
|
|
||
| if (validated.success) { | ||
| urlsToSeed.push(validated.normalizedUrl); | ||
| seededUrls.push({ | ||
| original: url, | ||
| normalized: validated.normalizedUrl, | ||
| source, | ||
| }); | ||
| } else { | ||
| skippedUrls.push({ | ||
| original: url, | ||
| source, | ||
| reason: validated.reason, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| return { urlsToSeed, seededUrls, skippedUrls }; | ||
| } | ||
|
|
||
| /** | ||
| * Validates a secondary URL (canonical or og:url) against the page's eTLD+1. | ||
| * | ||
| * @param {string} url - The URL to validate (may be relative) | ||
| * @param {string} normalizedPageUrl - The normalized page URL for eTLD+1 comparison | ||
| * @param {string} baseUrl - The original page URL for resolving relative URLs | ||
| * @returns {object} Validation result with success flag and normalizedUrl or reason | ||
| * @private | ||
| */ | ||
| #validateSecondaryUrl(url, normalizedPageUrl, baseUrl) { | ||
| const normalized = normalizeUrl(url, baseUrl); | ||
|
|
||
| if (!normalized.success) { | ||
| return { | ||
| success: false, | ||
| reason: "Normalization failed", | ||
| details: normalized.error, | ||
| }; | ||
| } | ||
|
|
||
| if (!isSameETLDPlusOne(normalizedPageUrl, normalized.url)) { | ||
| return { | ||
| success: false, | ||
| reason: "Different eTLD+1 from page URL", | ||
| pageUrl: normalizedPageUrl, | ||
| secondaryUrl: normalized.url, | ||
| }; | ||
| } | ||
|
|
||
| return { success: true, normalizedUrl: normalized.url }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i feel like we probably already have an existing API for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good point. I'll research more if there's an existing API I can use for this. 🕵️